Hotspot-Optimierung

Hotspot-Optimierung

Bei der Hotspot-Optimierung handelt es sich um eine Optimierungs-Technik, welche bei JIT-Compilern Verwendung findet und das Laufzeitverhalten von Software während der Ausführung erheblich verbessert. Die Details dieses Verfahrens sollen hier anhand der sehr weit verbreiteten Java Virtual Machine von Sun Microsystems erläutert werden.

Inhaltsverzeichnis

Überblick

Der Sun Java Server (auch Opto oder C2) Compiler verwandelt den Java Bytecode der Java Virtual Machine (JVM) in ausführbaren Maschinencode der entsprechenden Zielplattform. Es handelt sich um einen hochoptimierenden Just-In-Time Compiler (JIT). Just-In-Time Compiler erzeugen den Maschinencode im Gegensatz zu den sonst üblichen Ahead-Of-Time Compilern (GCC, Microsoft Visual Studio etc.) erst zur Laufzeit des Programms. Damit schlägt der Übersetzungsvorgang selbst, neben der eigentlichen Laufzeit des Programms, in Form von CPU-Zyklen und längerer Laufzeit zu Buche. Da dieser (Compilier-)Vorgang allerdings nur adaptiv, d. h. bei einigen Methoden (so genannten Hotspots) angewandt wird, dieser Aufwand einmalig und insbesondere bei langlaufenden Serveranwendungen sehr kurz verglichen mit der Programm-Ausführungszeit ist, wird dieser Mehraufwand schnell durch die höhere Qualität des erzeugten Maschinencodes kompensiert. Außerdem erfolgt die Compilierung in einem gesonderten Thread, der auf SMP-Maschinen normalerweise auf einer gesonderten CPU ausgeführt wird.

Zu Beginn der Laufzeit eines Java-Programms wird der gesamte Bytecode ausschließlich interpretiert. Das geschieht in einer Interpreterschleife, die Bytecode für Bytecode abarbeitet. Die Besonderheit von Hotspot-Compilern liegt nun darin, nur häufig benutzte Codeabschnitte – so genannte „Hotspots“ – sukzessive in Maschinencode zu übersetzen. Die Hotspots werden unter anderem auch lokal, d. h. innerhalb von Methoden entdeckt, die kleinste Compiliereinheit ist aber in jedem Fall die Methode. Da die Übersetzung nur bei besonders häufig genutzten oder langlaufenden Methoden und nicht mit dem gesamten Code geschieht, ist der Mehraufwand speziell bei langlaufenden Anwendungen vernachlässigbar.

Der Vorteil dieses Verfahrens liegt in der Möglichkeit Closed-World-Optimierungen durchzuführen sowie Dynamische Optimierungen anwenden zu können. Dies bedeutet, dass die JVM immer den gesamten geladenen Bytecode kennt und anhand dessen Optimierungsentscheidungen treffen kann, welche die Semantik des Codes so verändern, dass dieser nur mehr unter den vorhandenen Einsatzbedingungen korrekt funktioniert. Wird zur Laufzeit Code geladen, welcher diese Einsatzbedingungen ändert, so führt die (Server-)Hotspot VM eine Deoptimierung durch - und garantiert somit die korrekte Funktion, indem es den für diesen Einsatz zu aggressiv optimierten Code entfernt und erneut optimiert.

Der Compile-Vorgang des Sun Hotspot Compilers setzt sich aus den folgenden Schritten zusammen:

Hotspot-Erkennung, Compile Policy

Das grundsätzliche Verfahren, wie und unter welchen Umständen eine Methode übersetzt wird, wird in der Sun JVM durch sogenannte CompilePolicies definiert. Diese in einer C++-Klassenhierarchie modellierten Policies sind im Fall des Server-Compilers in der Klasse StackWalkCompPolicy gespeichert. Somit kann durch Wechseln bzw. Anpassen der Policies der Compiliervorgang umkonfiguriert werden. Das kommt in der Praxis allerdings relativ selten vor, vom Setzen des Flags -XX:+CompileTheWorld zum Übersetzen buchstäblich aller Methoden einmal abgesehen. Der Name StackWalkCompPolicy geht auf den Algorithmus zurück, den Aufrufstack der zu untersuchenden Methoden solange nach oben abzutesten, bis die erste Methode entdeckt wird, die nicht mehr zu einem Inlining führen wird.

Im Servercompiler werden zur Erkennung der Hotspots zwei Schwellwerte herangezogen:

  • Aufrufhäufigkeit einer Methode.

Dieser Schwellwert wird in der JVM-internen Variable CompileThreshold gehalten und hat auf den verschiedenen Plattformen unterschiedliche Grundeinstellungen. Auf der Intel 386 Architektur sind es bei der Server-JVM 10.000 und bei der Client-JVM 1.500 Methodenaufrufe.

  • Anzahl von Schleifendurchläufen in einer Methode. Werden Schleifen in Methoden allzu häufig durchlaufen, markiert dies eine Methode ebenfalls zur Compilierung. Die Häufigkeit eines Schleifendurchlaufs wiederum wird in so genannten Backedge-Countern für jede Schleife einzeln mitgezählt.

Im Servercompiler befinden sich bereits Vorkehrungen, in späteren Versionen eine sogenannte Two Tier Compilierung zu unterstützen. Dabei geht es darum, eine Methode zuerst zügig und ohne massive Optimierung zu compilieren und später (oder parallel) in einem weiteren Durchlauf hochoptimiert zu übersetzen. Diese „Two Tier Compilation“ wird perspektivisch zu einer Verschmelzung von Server und Client-Compiler führen.

Compilierung

Nachdem die Policy-Steuerung einzelne Methoden nun zum Kompilieren markiert hat, wird für jede der Methoden ein CompileTask Object erzeugt und in die CompileQueue eingereiht. Das, sowie die sonstige Kontrolle des gesamten Übersetzungsvorgangs, obliegt dem CompileBroker. Dieser erzeugt die einzelnen Übersetzungsjobs (CompileTask), stellt sie in die Queue und weckt zuletzt mit einem notify() inaktive Compile Threads. Die Anzahl der Compile Threads ist im Normalfall log(Anzahl der CPUs), doch mindestens 2. Im CompileBroker werden auch diverse PerfCounter-Objekte gehalten, die zur späteren Performanceüberwachung bzw. -anzeige herangezogen werden können.

Nun wird der Bytecode in verschiedenen Phasen umgewandelt. Die wichtigsten Schritte sind hierbei:

  • Parsen des Bytecodes
  • Löschen ungenutzter Knoten (Nodes)
  • Auswählen der Instruktionen.
  • Global Value Numbering (GVN)
  • Control Flow Graph (CFG) erzeugen
  • Register Allocation (Chaitin Graph Coloring)
  • Peephole-Optimierung

Die Internal Representation (IR) des Programmlaufs wird im SSA-Format gespeichert.

Optimierung

Unter anderem durch die Speicherung des Knoten-Graphs im SSA-Format ist es möglich, eine Reihe von Optimierungsverfahren anzuwenden. Hier die wichtigsten:

Inlining

Kurze Methoden (maximal 35 Bytes) werden in den Rumpf des Aufrufers eingefügt, anstatt angesprungen zu werden. Bei kurzen Methoden rentiert sich der Absprung nicht.

Loop-Unrolling

Kurze Schleifen werden sozusagen „entrollt“, das heißt, die einzelnen Schleifendurchläufe werden sequenziell ohne Rücksprung abgearbeitet. Das vergrößert ähnlich dem Inlining den Speicherverbrauch, ist aber bei wenigen Schleifendurchläufen billiger als das ständige Testen einer Sprungbedingung.

Dead Code Elimination

Ungenutzte Anweisungen werden auf Bytecodeebene entdeckt und verworfen. Obwohl diese Optimierung auf Quellcodeebene durch den Java Frontend Compiler (javac) weit mehr Anwendung findet, wird dieser Schritt auch auf Bytecodeebene angewendet.

Peephole-Optimierung

Optimierung auf Assemblerebene. Hierbei wird ein Kontext (Peephole) über einige wenige Assembler-Instruktionen erzeugt und z. B. redundante Speicherzugriffe eliminiert und Registerzugriffe optimiert.

Codegenerierung, Aktivierung

AD-Files

Der Codegenerator (Emitter) des Systems erzeugt Maschinencode auf der Basis von vorgefertigten Schablonen, die zu dem Bytecode korrespondierende Assemblercodestrecken in sogenannten Architecture Description-Files (AD-Files) speichern. In diesen Dateien werden die verschiedenen CPUs abstrakt mit ihren Assemblerbefehlen, Adressiermodi und besonders der Anzahl und Breite der Register beschrieben. Die AD-Files werden mittels eines speziellen Preprozessors in C++-Klassen übersetzt, die in den VM Buildprozess einfließen.

CompileCache

Die von dem Compiler erstellten Assemblersprache-Instruktionen werden in dem CompilerCache mit einer Referenz auf den ursprünglichen Bytecode abgelegt. Das ist notwendig, um bei einer späteren Deoptimierung wieder auf den Bytecode zugreifen zu können. Deoptimierung kann nötig werden, wenn z. B. dynamisch geladene Klassen, in denen einzelnen Methoden ge-„inlined“ wurden, also zur Laufzeit durch neue ersetzt werden.

Stubs

Der durch den Compiliervorgang erzeugte Maschinencode kann ohne Inanspruchnahme von Services der VM nicht funktionieren. In einer Methode muss z. B. immer wieder ein Name Lookup in der Symboltabelle vorgenommen werden, eine Referenz aufgelöst werden oder andere Dienstleistungen der JVM in Anspruch genommen werden. Deswegen werden in den Maschinencode immer wieder sogenannte Stubs eingefügt, durch die der compilierte Code quasi Kontakt zur Außenwelt aufnimmt.

Aktivieren des Compilats

Grundsätzlich kann die fertiggestellte Methode entweder zur Laufzeit des Bytecodes ausgetauscht werden oder aber zum Zeitpunkt des nächsten Aufrufs verwendet werden. Das Unterbrechen einer laufenden Methode und Austauschen gegen die kompilierte Version nennt man On-Stack-Replacement (OSR). Um diese hochdynamische Operation zu bewerkstelligen, werden im Bytecode so genannte Savepoints eingefügt. An diesen Savepoints befindet sich die Virtual Machine in einem definierten, synchronisierten Zustand. Nun wird der Bytecode gegen das Compilat getauscht und der genau entsprechende CPU-Stack synthetisch erzeugt.

Siehe auch

  • Compiler Optimization in der englischen Wikipedia

Literatur

  • Alfred V. Aho, Ravi Sethi, Jeffrey D. Ullman: Compiler. Principles, Techniques and Tools. ISBN 0-201-10194-7 (Das Dragon Book)

Weblinks


Wikimedia Foundation.

Игры ⚽ Поможем написать курсовую

Schlagen Sie auch in anderen Wörterbüchern nach:

  • Hotspot — Hot Spot (englisch heiße Stelle) steht für: eine lokal begrenzte Pyodermie (Hautentzündung) Hotspot der Biodiversität, Region mit hoher, stark bedrohter Artenvielfalt Hotspot (Datenbanken) Hot Spot (Fotovoltaik), ein Bereich eines… …   Deutsch Wikipedia

  • HotSpot — Entwickler Sun Microsystems Aktuelle Version 20 Betriebssystem plattformübergreifend Programmier­sprache C/C++ …   Deutsch Wikipedia

  • Dynamische Optimierung — Bei der Dynamischen Optimierung handelt es sich um eine Optimierungs Technik, welche das Laufzeitverhalten von Software während der Ausführung erheblich verbessert. Hierbei nutzt man unter anderem die Tatsache aus, dass die Werte bestimmter… …   Deutsch Wikipedia

  • JFC — Java Objektorientierte Programmiersprache Basisdaten Paradigmen: Objektorientierte Programmiersprache Aktuelle  …   Deutsch Wikipedia

  • Java (Framework) — Java Objektorientierte Programmiersprache Basisdaten Paradigmen: Objektorientierte Programmiersprache Aktuelle  …   Deutsch Wikipedia

  • Java (Softwaretechnologie) — Java Objektorientierte Programmiersprache Basisdaten Paradigmen: Objektorientierte Programmiersprache Aktuelle  …   Deutsch Wikipedia

  • Programmiersprache Java — Java Objektorientierte Programmiersprache Basisdaten Paradigmen: Objektorientierte Programmiersprache Aktuelle  …   Deutsch Wikipedia

  • Hot spot — (englisch heiße Stelle) steht für: eine lokal begrenzte Pyodermie (Hautentzündung) Hotspot der Biodiversität, Region mit hoher, stark bedrohter Artenvielfalt Hotspot (Datenbanken) Hot Spot (Fotovoltaik), ein Bereich eines Solarzellenmoduls… …   Deutsch Wikipedia

  • JavaVM — Java Logo Die Java Virtual Machine (abgekürzt Java VM oder JVM) ist der Teil der Java Laufzeitumgebung (JRE) für Java Programme, der für die Ausführung des Java Bytecodes verantwortlich ist. Hierbei wird im Normalfall jedes gestartete Java… …   Deutsch Wikipedia

  • Java VM — Java Logo Die Java Virtual Machine (abgekürzt Java VM oder JVM) ist der Teil der Java Laufzeitumgebung (JRE) für Java Programme, der für die Ausführung des Java Bytecodes verantwortlich ist. Hierbei wird im Normalfall jedes gestartete Java… …   Deutsch Wikipedia

Share the article and excerpts

Direct link
Do a right-click on the link above
and select “Copy Link”