2017-02-09 1 views
10

Ich habe meine Hausaufgaben gemacht und wiederholte Versicherungen, dass es keinen Unterschied in der Leistung macht, ob Sie Ihre Variablen innerhalb oder außerhalb Ihrer for-Schleife deklarieren, und es tatsächlich auf die gleiche MSIL kompiliert. Aber ich habe trotzdem damit herumgespielt und festgestellt, dass das Verschieben der Variablendeklarationen innerhalb der Schleife tatsächlich zu einem beträchtlichen und konsistenten Leistungsgewinn führt.Können in einer for-Schleife deklarierte Variablen die Leistung der Schleife beeinflussen?

Ich habe eine kleine Konsole Testklasse geschrieben, um diesen Effekt zu messen. Ich initialisiere ein statisches double[] Array Elemente, und zwei Methoden führen Schleifenoperationen darauf aus und schreiben die Ergebnisse in einen statischen double[] Array Puffer. Ursprünglich waren meine Methoden diejenigen, mit denen ich den Unterschied bemerkte, nämlich die Größenberechnung einer komplexen Zahl. Führt man diese für ein Array von 1000000 für 100 mal, artikel, bekam ich konsistent niedrigere Laufzeiten für die, in denen die Variablen (6 double Variablen) innerhalb der Schleife waren: zB 32,83 ± 0,64 ms v 43 , 24 ± 0,45 ms bei einer älteren Konfiguration mit Intel Core 2 Duo bei 2,66 GHz. Ich habe versucht, sie in anderer Reihenfolge auszuführen, aber das hat die Ergebnisse nicht beeinflusst.

static void Square1() 
    { 
     double x; 

     for (int i = 0; i < buffer.Length; i++) { 
      x = items[i]; 
      buffer[i] = x * x; 
     } 
    } 


    static void Square2() 
    { 
     for (int i = 0; i < buffer.Length; i++) { 
      double x; 
      x = items[i]; 
      buffer[i] = x * x; 
     } 
    } 

Mit diesen kamen die Ergebnisse aus den anderen Weg: weit von einem minimalen Arbeitsbeispiel und getestet zwei viel einfachere Methoden

Dann erkannte ich, dass die Größe einer komplexen Zahl Berechnung erklärt die Variable außerhalb der Schleife schien günstiger zu sein: 7,07 ± 0,43 ms für Square1() v 12,07 ± 0,51 ms für Square2().

Ich bin nicht vertraut mit ILDASM, aber ich habe die beiden Methoden zerlegt, und der einzige Unterschied scheint die Initialisierung der lokalen Variablen zu sein:

 .locals init ([0] float64 x, 
     [1] int32 i, 
     [2] bool CS$4$0000) 

in Square1() v

 .locals init ([0] int32 i, 
     [1] float64 x, 
     [2] bool CS$4$0000) 

in Square2(). In Übereinstimmung damit, was ist stloc.1 in einem ist stloc.0 in dem anderen, und umgekehrt. In der längeren komplexen Betragsberechnung MSIL-Codes sogar die Codegröße unterschieden und ich sah stloc.s i in der externen Deklaration Code, wo es stloc.0 im internen Deklarationscode war.

Also wie kann das sein? Betrachte ich etwas oder ist es ein wirklicher Effekt? Wenn dies der Fall ist, kann dies die Leistungsfähigkeit von langen Schleifen erheblich beeinflussen, daher denke ich, dass es eine Diskussion verdient.

Ihre Gedanken werden sehr geschätzt.

EDIT: Die eine Sache, die ich übersehen habe, war, es auf mehreren Computern vor dem Posten zu testen. Ich habe es jetzt auf einem i5 ausgeführt und die Ergebnisse sind fast identisch für die beiden Methoden. Ich entschuldige mich dafür, dass ich eine solche irreführende Bemerkung gepostet habe.

+1

Gute Untersuchung, Sie verdienen sicher eine Aufwertung. – NicoRiff

+2

@NicoRiff: In der Tat, es ist eine sehr gut geschriebene Frage. (Leider denke ich, dass die Antwort trivial ist.) – Bathsheba

+1

Ich kann nicht auf @ JonSkeet Antwort auf diese – NicoRiff

Antwort

5

Jeder C# -Compiler, der es wert ist, führt solche Mikrooptimierungen für Sie durch. Verlieren Sie eine Variable nur dann außerhalb eines Bereichs, wenn dies erforderlich ist.

So halten Sie double x; innerhalb der Schleife wenn möglich.

Persönlich, wenn items[i] ist Plain-Old-Data-Array-Zugriff dann würde ich buffer[i] = items[i] * items[i]; schreiben. C und C++ würden das optimieren, aber ich denke nicht, dass C# (noch); Ihre Demontage bedeutet, dass dies nicht der Fall ist.

+1

aber warum ist das? – NicoRiff

+0

Vielen Dank! Ich blieb bei der obsessiven Gewohnheit, alle meine Variable zu Beginn der Methode zu deklarieren, aber von jetzt an werde ich zweimal nachdenken. Meine Take-Home-Nachricht besteht darin, beide Anordnungen zu testen, wenn mir die Leistung wichtig ist, weil die Optimierung in beide Richtungen funktioniert. –

+1

Die wooly Antwort ist "Jahre der Erfahrung sagt Ihnen, dass lose beschränkte Variablen in einer kompletten Verwirrung einer Codebasis landen". – Bathsheba

1

Es wäre interessant zu profilieren, was der Garbage Collector für diese beiden Varianten macht.

Ich kann mir vorstellen, dass im ersten Fall die Variable x nicht erfasst wird, während die Schleife ausgeführt wird, da sie außerhalb des Gültigkeitsbereichs deklariert ist.

Im zweiten Fall werden alle Handles auf x bei jeder Iteration entfernt.

Vielleicht führen Sie Ihren Test erneut mit dem neuen C# 4.6 GC.TryStartNoGCRegion und GC.EndNoGCRegion durch, um zu sehen, ob die Leistungseinbußen vom GC stammen.

Prevent .NET Garbage collection for short period of time

+0

Danke, es ist eine ausgezeichnete Idee. Ich wollte es bereits testen lassen, aber im Moment habe ich keinen Zugriff auf .NET 4.6. SharpDevelop scheint das nicht zu unterstützen. Ich werde versuchen, meine Tools zu aktualisieren und zu der Frage zurückzukehren. –

+1

Ich bezweifle, dass dies etwas mit dem GC zu tun hat. 'double' ist ein Werttyp und wird in diesem Fall dem Stapel zugeordnet. Es erzeugt keinen Müll zum Aufräumen. – Kyle

+2

Eric Lippert gibt einige hervorragende Informationen dazu unter http://Stackoverflow.com/a/14043763/526724 –

Verwandte Themen