Ich habe ein Dokument durchgearbeitet, in dem es um Just-in-Time-Compiler- Optimierungstechniken (JIT) für Java geht. Eine davon war "Loop Inversion". Und das Dokument sagt:
Sie ersetzen eine reguläre
while
Schleife durch einedo-while
Schleife. Und diedo-while
Schleife wird innerhalb einerif
Klausel gesetzt. Dieser Ersatz führt zu zwei Sprüngen weniger.
Wie funktioniert die Schleifeninversion und wie optimiert sie unseren Codepfad?
NB: Es wäre großartig, wenn jemand anhand eines Beispiels für Java-Code erklären könnte, wie JIT ihn für nativen Code optimiert und warum er in modernen Prozessoren optimal ist.
java
jvm
jit
machine-instruction
Ich versuche es
quelle
quelle
Antworten:
Arbeitsablauf:
Arbeitsablauf:
Wenn Sie diese beiden vergleichen, können Sie leicht erkennen, dass letztere möglicherweise überhaupt keine Sprünge ausführen, vorausgesetzt, es gibt genau einen Schritt durch die Schleife, und im Allgemeinen ist die Anzahl der Sprünge um eins geringer als die Anzahl der Iterationen. Ersterer muss zurückspringen, um die Bedingung zu überprüfen, und nur dann aus der Schleife springen, wenn die Bedingung falsch ist.
Sprünge auf modernen Pipeline-CPU-Architekturen können sehr teuer sein: Da die CPU die Ausführung der Überprüfungen vor dem Sprung beendet, befinden sich die Anweisungen über diesen Sprung hinaus bereits in der Mitte der Pipeline. Diese gesamte Verarbeitung muss verworfen werden, wenn die Verzweigungsvorhersage fehlschlägt. Die weitere Ausführung wird verzögert, während die Pipeline erneut gestartet wird.
Erläuterung der erwähnten Verzweigungsvorhersage : Für jede Art von bedingtem Sprung verfügt die CPU über zwei Anweisungen, die jeweils eine Wette auf das Ergebnis enthalten. Zum Beispiel würden Sie am Ende einer Schleife eine Anweisung mit der Aufschrift " Sprung, wenn nicht Null, Wetten auf Nicht Null " einfügen, da der Sprung bei allen Iterationen mit Ausnahme der letzten ausgeführt werden muss. Auf diese Weise beginnt die CPU, ihre Pipeline mit den Anweisungen zu pumpen, die dem Sprungziel folgen, anstatt denen, die der Sprunganweisung selbst folgen.
Wichtige Notiz
Bitte nehmen Sie dies nicht als Beispiel für die Optimierung auf Quellcode-Ebene. Das wäre völlig falsch, da der JIT-Compiler, wie bereits aus Ihrer Frage hervorgeht, die Umwandlung von der ersten in die zweite Form routinemäßig und völlig eigenständig durchführt.
quelle
do-while
Quellcode generierte Bytecode ist irrelevant, da wir das eigentlich nicht schreiben. Wir schreiben diewhile
Schleife und lassen den Compiler und JIT verschwören, um sie für uns zu verbessern (über Schleifeninversion), falls / nach Bedarf.Dies kann eine Schleife optimieren, die immer mindestens einmal ausgeführt wird.
Eine reguläre
while
Schleife springt dann immer mindestens einmal zum Anfang und am Ende einmal zum Ende. Ein Beispiel für eine einfache Schleife, die einmal ausgeführt wird:Eine
do-while
Schleife hingegen überspringt den ersten und letzten Sprung. Hier ist eine äquivalente Schleife wie oben, die ohne Sprünge ausgeführt wird:quelle
boolean b = true; while(b){ b = maybeTrue();}
boolean b;do{ b = maybeTrue();}while(b);
Lassen Sie uns durch sie gehen:
Die
while
Version:n
und springen zu,done();
wenn die Bedingung nicht erfüllt ist.n
.done()
.Die
do-while
Version:(Denken Sie daran, dass wir dies im Quellcode nicht tun [was zu Wartungsproblemen führen würde], der Compiler / JIT erledigt dies für uns.)
n
und springen zu,done();
wenn die Bedingung nicht erfüllt ist.n
.done()
.So zum Beispiel, wenn
n
beginnt zu sein9
, wir überhaupt nicht in der Sprung -do-while
Version, während in derwhile
Version müssen wir an den Anfang zurückspringen, tun Sie den Test, und dann bis zum Ende springen zurück , wenn wir es sehen , ist nicht wahr .quelle
Die Schleifeninversion ist eine Technik zur Leistungsoptimierung, die die Leistung verbessert, da der Prozessor mit weniger Anweisungen das gleiche Ergebnis erzielen kann. Dies sollte vor allem die Leistung unter Randbedingungen verbessern.
Dieser Link bietet ein weiteres Beispiel für die Schleifeninversion. In wenigen Architekturen, in denen Dekrementieren und Vergleichen als ein einziger Befehlssatz implementiert ist, ist es sinnvoll, eine for-Schleife mit Dekrementierungs- und Vergleichsoperation in eine Weile umzuwandeln.
Wikipedia hat ein sehr gutes Beispiel und ich erkläre es hier noch einmal.
wird vom Compiler in konvertiert
Wie übersetzt sich dies in Leistung? Wenn der Wert von i 99 ist, muss der Prozessor kein GOTO ausführen (was im ersten Fall erforderlich ist). Dies verbessert die Leistung.
quelle