2010-02-07 7 views
30

Ein paar Tage zurück, während ich eine Antwort für this question hier auf Überlauf schrieb, wurde ich ein bisschen vom C# -Compiler überrascht, wer nicht tat, was ich erwartete, es zu tun. Schauen Sie sich den folgenden Code-Schnipsel:C# -Compiler optimiert nicht unnötige Umwandlungen

Erstens:

object[] array = new object[1]; 

for (int i = 0; i < 100000; i++) 
{ 
    ICollection<object> col = (ICollection<object>)array; 
    col.Contains(null); 
} 

Zweitens:

object[] array = new object[1]; 

for (int i = 0; i < 100000; i++) 
{ 
    ICollection<object> col = array; 
    col.Contains(null); 
} 

Der einzige Unterschied im Code zwischen den beiden Schnipsel ist die Besetzung zu ICollection < object>. Da object [] die ICollection < object> interface explizit implementiert, erwartete ich, dass die beiden Snippets auf dieselbe IL kompiliert werden und daher identisch sind. Wenn ich jedoch Leistungstests mit ihnen durchführte, bemerkte ich, dass das Letztere etwa 6 mal so schnell war wie das Erste.

Nach dem Vergleich der IL aus beiden Schnipsel, bemerkte ich, dass beide Methoden identisch waren, mit Ausnahme einer ILA-Anweisung im ersten Snippet.

Überrascht von diesem frage ich mich jetzt, warum der C# -Compiler hier nicht 'schlau' ist. Die Dinge sind nie so einfach wie es scheint, warum ist der C# -Compiler hier ein bisschen naiv?

+0

Welche Compiler-Optionen verwenden Sie? – Richard

+0

Im Freigabemodus kompiliert (Optimierungscode ist aktiviert). – Steven

Antwort

32

Meine Vermutung ist, dass Sie einen kleinen Fehler im Optimierer entdeckt haben. Es gibt alle Arten von Sonderfällen Code für Arrays. Danke, dass du mich darauf aufmerksam gemacht hast.

+18

Nur Eric kann eine Antwort posten, die sagt "oops, wir haben einen Boo-boo gemacht" und erhält 10 upvotes. Ausgezeichnet! ;) – Aaronaught

4

Dies ist eine grobe Schätzung, aber ich denke, es geht um die Beziehung des Arrays zu seiner generischen IEnumerable.

In .NET Framework Version 2.0, die Array-Klasse implementiert die System.Collections.Generic.IList, System.Collections.Generic.ICollection, und System.Collections.Generic.IEnumerable generic Schnittstellen. Die Implementierungen werden den Arrays zur Laufzeit zur Verfügung gestellt und sind daher für die Dokumentation Build Tools nicht sichtbar . Als Ergebnis wird die generischen Schnittstellen erscheinen nicht in der Deklarationssyntax für die Array- Klasse, und es gibt keine Referenz Themen für Schnittstellenelemente, die durch Gießen ein Array den generischen Schnittstellentypen (explizite nur zugänglich sind Schnittstellenimplementierungen). Der Schlüssel Sache zu beachten, wenn Sie eine Array auf eine dieser Schnittstellen zu werfen ist , dass Mitglieder, die hinzufügen, einfügen oder Elemente entfernen NotSupportedException.

Siehe MSDN Article.

Es ist nicht klar, ob dies mit .NET 2.0+ zu tun hat, aber in diesem speziellen Fall würde es Sinn machen, warum der Compiler Ihren Ausdruck nicht optimieren kann, wenn er nur zur Laufzeit gültig wird.

+1

Ihre Vermutung ist gar nicht so schlecht, denn das scheint nur bei Arrays zu passieren. Wenn Sie die Zeilen ändern "Objekt [] Array = neues Objekt [1];" zu "Sammlung Array = neue Sammlung ();" der Compiler gelingt es, den Cast weg zu optimieren. Ich bin jedoch nicht vollständig zufrieden, da der Artikel besagt, dass Implementierungen Arrays zur Laufzeit bereitgestellt werden, der C# -Compiler tatsächlich weiß, dass T [] ICollection implementiert. Ansonsten die Aussage "ICollection col = array;" würde nicht kompilieren. Wir wissen immer noch nicht, was genau hier vor sich geht. – Steven

2

Dies sieht nicht mehr als nur eine verpasste Chance im Compiler aus, die Besetzung zu unterdrücken.Es funktioniert, wenn Sie es folgendermaßen schreiben:

was darauf hindeutet, dass es zu konservativ wird, weil Casts Ausnahmen auslösen können. Es funktioniert jedoch, wenn Sie in die nicht generische ICollection umwandeln. Ich würde daraus schließen, dass sie es einfach übersehen haben.

Es gibt ein größeres Optimierungsproblem bei der Arbeit hier, der JIT-Compiler wendet die Schleifen-invariante Huboptimierung nicht an.

object[] array = new object[1]; 
ICollection<object> col = (ICollection<object>)array; 
for (int i = 0; i < 100000; i++) 
{ 
    col.Contains(null); 
} 

die eine Standard-Optimierung in der C/C++ zum Beispiel Code-Generator ist: Es sollte der Code wie folgt neu geschrieben. Dennoch kann der JIT-Optimierer nicht viele Zyklen mit der Art von Analyse brennen, die zum Auffinden solcher möglicher Optimierungen erforderlich ist. Der glückliche Aspekt dabei ist, dass optimierter managed Code immer noch gut debuggbar ist. Und dass es für den C# -Programmierer immer noch eine Rolle gibt, performanten Code zu schreiben.

+0

Wenn man über das JIT spricht, hat auch das JIT den Cast nicht optimiert. Dass der JIT dies nicht tut, ist sehr erklärlich, wie Sie bereits sagten: "Der JIT-Optimierer kann nicht viele Zyklen verbrauchen". – Steven