Schneller zugriff auf Klassenmethode



  • Hallo.

    Ich will eigentlich wissen, ob man eine Funktion über einen Zeiger unsafe aufrufen kann. Ich habe folgenden Fall:

    In einer Schleife durchlaufe ich mehrere Millionen Elemente und möchte bei jedem Element eine abstrakte Funktion aus einer weit verzweigten Klasse aufrufen.
    Das ganze könnte so aussehen:

    foreach(Node N in NodeStack){
    N.Unterklasse.Abstrakte_Unterklassen[3].Funktion()
    }
    

    Ich habe gehört (ich lasse mich gerne eines Besseren belehren oder vllt. bezieht sich das auf ältere Compiler), dass der Zugriff auf abstrakte Klassen eher langsam ist und daher im HP Computing vermieden werden sollte. Daher nutze ich abstrakte Klassen mehr als Ordner, wo ich Daten ablege und von wo aus ich diese bearbeiten kann. Wenn ich große Arrays aus einer Klasse bearbeite, greife ich nie über die Klasse darauf zu sonder hole mir vorher den Zeiger auf das erste Element und greife dann über den Index darauf zu. Die Daten sind statisch auf dem Heap abgelegt.

    Ich würde mir jetzt gerne eine Kopie einer Funktion oder noch besser eine Referenz auf eine Funktion holen, bevor ich diese dann in einer Schleife Millioinenfach aufrufe. Ist das möglich, dass ich mir die Referenz einer Funktion hole und diese darüber aufrufe?



  • Hmm vll. via reflection.. was aber wahrscheinlich auch nich performant seind wird!?

    var func = typeof(Root).GetMethod("Func");
    var foo= new Foo();
    var bar= new Bar();
    
         func.Invoke(foo);
         func.Invoke(bar);
    
        abstract class  Root 
        {
            public  abstract void Func();
        }
    
        class Foo : Root 
        {
            public override void Func()
            {
               ...
            }
        }
    
        class Bar : Root
        {
            public override void Func()
            {
                ...
            }
        }
    


  • CJens schrieb:

    In einer Schleife durchlaufe ich mehrere Millionen Elemente und möchte bei jedem Element eine abstrakte Funktion aus einer weit verzweigten Klasse aufrufen.

    Haben die Millionen Elemente alle dieselbe Implementierung der abstrakten Methode, oder sind die alle unterschiedlich? (Überhaupt, wenn du mit mehreren Millionen Instanzen arbeitest, gibt es vielleicht geeignetere Repräsentationsformen als Objekte mit virtuellen Methoden.)

    CJens schrieb:

    Ich habe gehört

    Mach stattdessen lieber eine Messung. Wenn sich zeigt, daß der virtuelle Methodenaufruf ein Flaschenhals ist, dann optimiere weiter.

    CJens schrieb:

    Wenn ich große Arrays aus einer Klasse bearbeite, greife ich nie über die Klasse darauf zu sonder hole mir vorher den Zeiger auf das erste Element und greife dann über den Index darauf zu.

    Du meinst, eine Referenz auf das Array? Oder wirklich einen Zeiger (i.e. unsafe )?

    CJens schrieb:

    Ich würde mir jetzt gerne eine Kopie einer Funktion oder noch besser eine Referenz auf eine Funktion holen, bevor ich diese dann in einer Schleife Millioinenfach aufrufe. Ist das möglich, dass ich mir die Referenz einer Funktion hole und diese darüber aufrufe?

    Daraus schließe ich mal, daß alle Objekte dieselbe Implementierung der virtuellen Funktion verwenden, und daß du vor dem Eintritt in die Schleife schon herausfinden kannst, welche das ist.

    Wenn du zur Laufzeit über den Ausführungspfad entscheiden kannst, dann kannst du mittels Reflection.Emit selber JIT-Compiler spielen. Das ist allerdings nur sinnvoll, wenn du Code generieren kannst, der deutlich effizienter ist als der vom JIT-Compiler erzeugte Code, und wenn der Code lang genug läuft, damit der JIT-Overhead nicht ins Gewicht fällt. Bei .NET Native-Targets (d.h. UWP-Anwendungen) fällt das allerdings flach.

    Virtuelle Funktionsaufrufe kannst du prinzipiell vermeiden, indem du die implementierende Klasse als sealed kennzeichnest. Wenn du dann die Methode über eine Objektreferenz vom Typ der implementierenden Klasse aufrufst, geht das ohne Umweg über die VMT. Aber in deinem Fall müßtest du dafür jedes einzelne Objekt zum richtigen Typ casten, was vermutlich auch nicht billiger ist als ein VMT-Call.

    Ein Aufruf über einen Funktionszeiger ist übrigens genau so teuer wie ein Aufruf per VMT. Das Problem ist in beiden Fällen, daß die Zieladresse dynamisch ist und die CPU deshalb den Aufrufpfad nicht trivial prefetchen kann. Allerdings vermute ich, daß der Branch-Predictor in modernen CPUs einen Fall wie deinen (zahlreiche konsekutive Aufrufe derselben Implementierung einer virtuellen Methode) recht gut vorhersagen kann.

    Interface-Methodenaufrufe sind bekanntlich etwas teurer als gewöhnliche virtuelle Methodenaufrufe. Allerdings generiert der JIT-Compiler Code, der in der Lage ist, sich an die meistaufgerufene Implementierung anzupassen, was die Kosten für den Fall, daß du meistens dieselbe Implementierung aufrufst, deutlich reduziert (siehe hier).

    unsafe würde ich an deiner Stelle aus dem Spiel lassen, wenn es um Performance geht und wenn du nicht genau weißt, was du tust.


Log in to reply