2014-09-01 7 views
5

Wenn Sie eine Methode haben oder Foo<T> eingeben, dann kann die CLR mehrere Versionen für verschiedene T kompilieren. Ich weiß, dass alle Referenztypen die gleiche Version haben. Wie funktioniert es für Strukturen? Wird Code manchmal für verschiedene Strukturen freigegeben oder nie geteilt? Ich könnte mir vorstellen, dass Code beispielsweise für alle Strukturen derselben Größe gemeinsam genutzt wird.Wann ist Code für verschiedene Instanzen von Generika in der CLR freigegeben?

Ich bin interessiert, weil ich über das folgende Beispiel ich Frage:

interface IBar 
{ 
    void DoBar(); 
} 

struct Baz : IBar 
{ 
    public void DoBar(){ ... } 
} 

struct Quux : IBar 
{ 
    public void DoBar(){ ... } 
} 

Nun, wenn ich folgendes tun:

public void ExecuteBar<T>(T bar) where T:IBar 
{ 
    bar.DoBar(); 
} 

ExecuteBar(new Baz()); 
ExecuteBar(new Quux()); 

Wird diese erzeugen zwei Versionen von ExecuteBar, die jeweils mit einem direkter (nicht virtueller) Anruf direkt an Bar.DoBar() und Quux.DoBar()? Oder wird der Versand zur Laufzeit ausgeführt?

Antwort

4

Es gibt keine direkte Antwort auf diese Frage, es hängt stark von dem Jitter ab, den Sie verwenden und welche Art von Code in der generischen Methode vorhanden ist.

Ein Ausgangspunkt ist, dass der Jitter eine eindeutige Methode für jeden einzelnen Werttyp generiert. Selbst für Strukturen, die ansonsten völlig identisch sind. Etwas, was Sie mit dem Debugger sehen können. Verwenden Sie Debug + Windows + Disassembly, um den generierten Maschinencode anzuzeigen. Einzelschritt in eine Methode, benutze das Debug + Windows + Register Fenster, das EIP/RIP Register zeigt dir den Speicherort der Methode im Speicher an.

Aber generische Methoden wie diese sind immer noch für die Inline-Optimierung geeignet. Sehr wichtig für perf, es macht die gesamte Methode verschwinden und der Code in der Methode wird in die aufrufende Methode injiziert. In diesem Fall verschwindet die Unterscheidung zwischen generischen Methoden . Dies kann normalerweise nicht für Schnittstellenimplementierungsmethoden gelten. Es passiert jedoch für Ihren Beispielcode, wenn Sie die Methodenkörper leer lassen. Mit unterschiedlichen Ergebnissen für den x86- und den x64-Jitter.

Sie können nur wirklich sagen, was Sie bekommen, indem Sie den generierten Maschinencode betrachten. Stellen Sie sicher, dass Sie dem Optimierungsprogramm erlauben, seine Aufgabe auszuführen, Extras + Optionen, Debugging, Allgemein, deaktivieren Sie das Kontrollkästchen "JIT-Optimierung unterdrücken".Und natürlich, stellen Sie sicher, dass Sie nie von einer genauen Antwort auf diese Frage abhängen. Implementierungsdetails wie diese können sich ohne vorherige Ankündigung ändern.

+0

Korrigieren. Also denke ich, dass auf der Ebene, an der sich der Fragesteller interessiert, die Antwort lautet: Bei der ersten Alternative werden zwei verschiedene Methoden erstellt, eine für jeden der Werttypen, und der Versand ist nicht virtuell. –

+0

Danke! @ Jeppe: Ja, tatsächlich. Es kann der Fall sein, dass der Code durch Inlining weiter optimiert wird, und das wäre noch effizienter, aber ich interessierte mich für das Basisleistungsmodell von Schnittstellenaufrufen auf Strukturen, deren Typ explizit als Parameter an eine generische Methode oder Klasse übergeben wird . Ich weiß, dass Sie sich nicht auf dieses Verhalten verlassen sollten, aber es kann als Ausgangspunkt für die Optimierung dienen. Wenn ein Versand den Code verlangsamt, kann es sinnvoll sein, eine Klasse in eine Struktur zu ändern, um den Versand zu vermeiden. – Jules

1

Die generischen Definitionen werden nicht direkt verwendet. Stattdessen werden zur Laufzeit konstruierte Typen erstellt.

Für jedes Werttypargument wird ein Konstrukt erstellt. Ein eindeutiges Konstrukt wird für alle Referenztypen-Argumente erstellt.

Afaik, teilen verschiedene Typen nicht die gleichen asm Anweisungen.

0

Ich habe Ihren Code in LINQPad und die erzeugte IL zeigt, dass es nur eine Version von ExcuteBar ist die IBar.DoBar nennt()

ExecuteBar: 
IL_0000: nop   
IL_0001: ldarga.s 01 
IL_0003: constrained. 01 00 00 1B 
IL_0009: callvirt UserQuery+IBar.DoBar 
IL_000E: nop   
IL_000F: ret 

Methode ExecuteBar2 (IBar bar) sieht ähnlich aus:

ExecuteBar2: 
IL_0000: nop   
IL_0001: ldarg.1  
IL_0002: callvirt UserQuery+IBar.DoBar 
IL_0007: nop   
IL_0008: ret 

Edit:

class C : IBar { 
    public void DoBar(){} 
} 

Referenztyp als Argument in die gleiche Methode übergeben wird (s) (Baz und Quux sind eingerahmt)

+0

In der IL-Ebene, sicher. Beachten Sie jedoch das eingeschränkte Präfix. Meine Frage ist, was die CLR damit macht, nicht was der C# -Compiler damit macht. – Jules