Verbessert das Kopieren einer Mitgliedsvariablen in eine lokale Stapelvariable die Leistung in C #?

8

Ich schreibe ziemlich oft Code, der Member-Variablen in eine lokale Stack-Variable kopiert, in der Annahme, dass er die Performance verbessert, indem er die Zeiger-Dereferenzierung entfernt, die immer beim Zugriff auf Member-Variablen auftreten muss.

Ist das gültig?

Zum Beispiel

%Vor%

Ich denke, dass DoSomethingPossiblyFaster tatsächlich schneller ist als DoSomethingPossiblySlower.

Ich weiß, dass dies eine Mikrooptimierung ist, aber es wäre nützlich, eine definitive Antwort zu haben.

Bearbeiten Nur um ein bisschen Hintergrund dazu hinzuzufügen. Unsere Anwendung muss viele Daten aus Telekommunikationsnetzen verarbeiten, und diese Methode wird wahrscheinlich für einige unserer Server etwa 1 Milliarde Mal pro Tag aufgerufen. Ich bin der Ansicht, dass jedes bisschen hilft, und manchmal versuche ich dem Compiler nur ein paar Hinweise zu geben.

    
Nick Randell 22.11.2011, 15:46
quelle

4 Antworten

4

Wenn der Compiler / JIT diese oder eine ähnliche Optimierung für Sie noch nicht durchführt (dies ist ein großes if), dann sollte DoSomethingPossiblyFaster schneller sein als DoSomethingPossiblySlower . Der beste Weg, dies zu erklären, ist eine grobe Übersetzung des C # -Codes zu einem geraden C.

Wenn eine nicht statische Elementfunktion aufgerufen wird, wird ein versteckter Zeiger auf this an die Funktion übergeben. Sie haben ungefähr die folgenden Punkte und ignorieren den Versand virtueller Funktionen, da sie für die Frage irrelevant sind (oder äquivalent dazu, dass Manager versiegelt ist):

%Vor%

Der Unterschied besteht darin, dass in DoSomethingPossiblyFaster , mConstraints auf dem Stapel lebt und der Zugriff nur eine Ebene der Zeigerumleitung erfordert, da sie einen festen Abstand vom Stapelzeiger hat. In DoSomethingPossiblySlower , wenn der Compiler die Optimierungsmöglichkeit verpasst, gibt es eine zusätzliche Zeigerindirektion. Der Compiler muss einen festen Offset vom Stack-Pointer lesen, um auf this zuzugreifen, und dann einen festen Offset von this lesen, um mConstraints zu erhalten.

Es gibt zwei mögliche Optimierungen, die diesen Treffer negieren könnten:

  1. Der Compiler könnte genau das tun, was Sie manuell getan haben und mConstraints auf dem Stack zwischenspeichern.

  2. Der Compiler könnte this in einem Register speichern, so dass er ihn nicht bei jeder Schleifeniteration aus dem Stapel holen muss, bevor er deneferenziert. Dies bedeutet, dass das Holen von mConstraints von this oder vom Stapel im Grunde die gleiche Operation ist: Eine einzelne Dereferenzierung eines festen Versatzes von einem Zeiger, der sich bereits in einem Register befindet.

dsimcha 22.11.2011, 17:56
quelle
16

Was ist mehr lesbar ? Das sollte normalerweise Ihr wichtigster Motivationsfaktor sein. Müssen Sie sogar eine for -Schleife anstelle von foreach verwenden?

As mConstraints ist readonly Ich würde möglicherweise erwarten, dass der JIT-Compiler das für Sie tut - aber wirklich, was machst du in der Schleife? Die Chancen, dass dies signifikant ist, sind ziemlich gering. Ich würde immer den zweiten Ansatz einfach aus Lesbarkeit wählen - und ich würde foreach bevorzugen, wo immer es möglich ist. Ob der JIT-Compiler diesen Fall optimiert, hängt stark vom JIT selbst ab - das kann zwischen Versionen, Architekturen und sogar der Größe der Methode oder anderen Faktoren variieren. Es kann keine Antwort geben, da es immer möglich ist, dass ein alternativer JIT anders optimiert wird.

Wenn Sie denken, dass Sie sich in einem Fall befinden, in dem es wirklich wichtig ist , sollten Sie einen Benchmark erstellen - gründlich, mit so realistischen Daten wie möglich. Nur dann sollten Sie Ihren Code vom am besten lesbaren Formular entfernen. Wenn Sie "ziemlich oft" Code wie diesen schreiben, scheint es unwahrscheinlich, dass Sie sich selbst einen Gefallen tun.

Auch wenn der Lesbarkeitsunterschied relativ klein ist, würde ich sagen, dass er immer noch präsent und signifikant ist - während ich sicherlich erwarten würde, dass der Leistungsunterschied vernachlässigbar ist.

    
Jon Skeet 22.11.2011 15:49
quelle
3

Sie kennen die Antwort, die Sie bekommen werden, oder? "Zeit es."

Es gibt wahrscheinlich keine definitive Antwort. Erstens könnte der Compiler die Optimierung für Sie tun. Zweitens, selbst wenn dies nicht der Fall ist, ist die indirekte Adressierung auf der Assembly-Ebene möglicherweise nicht wesentlich langsamer. Drittens hängt es von den Kosten der Herstellung der lokalen Kopie ab, verglichen mit der Anzahl der Schleifeniterationen. Dann müssen Caching-Effekte berücksichtigt werden.

Ich liebe es zu optimieren, aber dies ist ein Ort, den ich definitiv sagen würde, warte, bis du ein Problem hast, dann experimentiere. Dies ist eine mögliche Optimierung, die bei Bedarf hinzugefügt werden kann, nicht eine dieser Optimierungen, die im Voraus geplant werden müssen, um später einen massiven Welleneffekt zu vermeiden.

Edit: (auf eine definitive Antwort)

Kompilieren Sie beide Funktionen im Freigabemodus und untersuchen Sie die IL mit IL Dasm zeigt, dass an beiden Stellen die "PossiblyFaster" -Funktion die lokale Variable verwendet, es gibt eine Anweisung weniger | ldloc.0 vs
ldarg.0; ldfld class Constraint[] Manager::mConstraints

Natürlich ist dies immer noch eine Ebene entfernt vom Maschinencode - Sie wissen nicht, was der JIT-Compiler für Sie tun wird. Aber es ist wahrscheinlich, dass "PossiblyFaster" marginal schneller ist.
Ich rate jedoch immer noch nicht, die zusätzliche Variable hinzuzufügen, bis Sie sicher sind, dass diese Funktion die teuerste Sache in Ihrem System ist.

    
AShelly 22.11.2011 15:55
quelle
1

Ich habe dies profiliert und eine Reihe interessanter Ergebnisse gefunden, die wahrscheinlich nur für mein spezifisches Beispiel gelten, aber ich dachte, es wäre es wert, hier erwähnt zu werden.

Am schnellsten ist der X86-Freigabemodus. Das dauert eine Iteration meines Tests in 7,1 Sekunden, während der entsprechende X64-Code 8,6 Sekunden dauert. Dies führte 5 Iterationen durch, wobei jede Iteration die Schleife 19,2 Millionen Mal verarbeitete.

Der schnellste Ansatz für die Schleife war:

%Vor%

Der zweitschnellste Ansatz, der mich massiv überraschte, war der folgende

%Vor%

Ich vermute, das lag daran, dass mConstraints in einem Register für die Schleife gespeichert wurde.

Dies wurde langsamer, als ich die Option readonly für mConstraints entfernte.

Also, meine Zusammenfassung davon ist, dass lesbar in dieser Situation auch Leistung bringt.

    
Nick Randell 23.11.2011 10:57
quelle

Tags und Links