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.
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):
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:
Der Compiler könnte genau das tun, was Sie manuell getan haben und mConstraints
auf dem Stack zwischenspeichern.
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.
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
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.
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.
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.
Tags und Links c# performance