Methode Inlining in der JVM

1. Einleitung

In diesem Tutorial sehen wir uns an, welche Methode Inlining in der Java Virtual Machine enthalten ist und wie es funktioniert.

Wir werden auch sehen, wie die Informationen zum Inlining von der JVM abgerufen und gelesen werden und was wir mit diesen Informationen tun können, um unseren Code zu optimieren.

2. Was ist Inlining?

Grundsätzlich ist Inlining eine Möglichkeit, kompilierten Quellcode zur Laufzeit zu optimieren, indem die Aufrufe der am häufigsten ausgeführten Methoden durch ihre Körper ersetzt werden.

Obwohl es sich um eine Kompilierung handelt, wird sie nicht vom traditionellen Javac- Compiler ausgeführt, sondern von der JVM selbst. Genauer gesagt liegt es in der Verantwortung des Just-In-Time-Compilers (JIT) , der Teil der JVM ist. javac erzeugt nur einen Bytecode und lässt JIT die Magie ausführen und den Quellcode optimieren.

Eine der wichtigsten Konsequenzen dieses Ansatzes ist, dass, wenn wir den Code mit altem Java kompilieren, dasselbe gilt. Die Klassendatei ist auf neueren JVMs schneller. Auf diese Weise müssen wir den Quellcode nicht neu kompilieren, sondern nur Java aktualisieren.

3. Wie macht JIT das?

Im Wesentlichen versucht der JIT-Compiler, die häufig aufgerufenen Methoden zu integrieren, um den Overhead eines Methodenaufrufs zu vermeiden . Bei der Entscheidung, ob eine Methode inline ist oder nicht, werden zwei Dinge berücksichtigt.

Erstens werden Zähler verwendet, um zu verfolgen, wie oft wir die Methode aufrufen. Wenn die Methode mehr als eine bestimmte Anzahl von Malen aufgerufen wird, wird sie "heiß". Dieser Schwellenwert ist standardmäßig auf 10.000 festgelegt, kann jedoch während des Java-Starts über das JVM-Flag konfiguriert werden. Wir wollen definitiv nicht alles inline setzen, da dies zeitaufwändig wäre und einen riesigen Bytecode erzeugen würde.

Wir sollten bedenken, dass Inlining nur dann stattfinden wird, wenn wir einen stabilen Zustand erreichen. Dies bedeutet, dass wir die Ausführung mehrmals wiederholen müssen, um genügend Profilinformationen für den JIT-Compiler bereitzustellen.

Darüber hinaus garantiert „heiß“ nicht, dass die Methode inline ist. Wenn es zu groß ist, wird es vom JIT nicht inline. Die akzeptable Größe wird durch das Flag -XX: FreqInlineSize = begrenzt , das die maximale Anzahl von Bytecode-Anweisungen angibt, die für eine Methode inline sein sollen.

Es wird jedoch dringend empfohlen, den Standardwert dieses Flags nicht zu ändern, es sei denn, wir sind uns absolut sicher, welche Auswirkungen dies haben könnte. Der Standardwert hängt von der Plattform ab - für 64-Bit-Linux sind es 325.

Die JIT inlines statische , private , oder endgültige Verfahren im Allgemeinen . Und während öffentliche Methoden auch Kandidaten für Inlining sind, wird nicht jede öffentliche Methode notwendigerweise inlined sein. Die JVM muss feststellen, dass es nur eine einzige Implementierung einer solchen Methode gibt . Jede zusätzliche Unterklasse würde Inlining verhindern und die Leistung wird zwangsläufig abnehmen.

4. Heiße Methoden finden

Wir wollen sicher nicht erraten, was die GEG tut. Daher brauchen wir eine Möglichkeit, um zu sehen, welche Methoden inline oder nicht inline sind. Wir können dies leicht erreichen und all diese Informationen in der Standardausgabe protokollieren, indem wir beim Start einige zusätzliche JVM-Flags setzen:

-XX:+PrintCompilation -XX:+UnlockDiagnosticVMOptions -XX:+PrintInlining

Das erste Flag wird protokolliert, wenn die JIT-Kompilierung erfolgt. Das zweite Flag aktiviert zusätzliche Flags, einschließlich -XX: + PrintInlining , mit denen gedruckt wird, welche Methoden wo inline werden.

Dies zeigt uns die Inline-Methoden in Form eines Baumes. Die Blätter sind mit einer der folgenden Optionen versehen und markiert:

  • Inline (heiß) - Diese Methode ist als heiß markiert und inline
  • zu groß - die Methode ist nicht heiß, aber auch der generierte Bytecode ist zu groß, sodass sie nicht inline ist
  • Hot-Methode zu groß - Dies ist eine Hot-Methode, die jedoch nicht inline ist, da der Bytecode zu groß ist

Wir sollten auf den dritten Wert achten und versuchen, Methoden mit dem Label „heiße Methode zu groß“ zu optimieren.

Wenn wir eine heiße Methode mit einer sehr komplexen bedingten Anweisung finden, sollten wir im Allgemeinen versuchen, den Inhalt der if- Anweisung zu trennen und die Granularität zu erhöhen, damit die JIT den Code optimieren kann. Das gleiche gilt für den Schalter und for- Schleifenanweisungen.

Wir können daraus schließen, dass ein manuelles Inlining nicht erforderlich ist, um unseren Code zu optimieren. Die JVM erledigt dies effizienter, und wir würden den Code möglicherweise lang und schwer zu befolgen machen.

4.1. Beispiel

Lassen Sie uns nun sehen, wie wir dies in der Praxis überprüfen können. Wir werden zuerst eine einfache Klasse erstellen, die die Summe der ersten N aufeinanderfolgenden positiven ganzen Zahlen berechnet :

public class ConsecutiveNumbersSum { private long totalSum; private int totalNumbers; public ConsecutiveNumbersSum(int totalNumbers) { this.totalNumbers = totalNumbers; } public long getTotalSum() { totalSum = 0; for (int i = 0; i < totalNumbers; i++) { totalSum += i; } return totalSum; } }

Als nächstes verwendet eine einfache Methode die Klasse, um die Berechnung durchzuführen:

private static long calculateSum(int n) { return new ConsecutiveNumbersSum(n).getTotalSum(); }

Schließlich werden wir die Methode mehrmals aufrufen und sehen, was passiert:

for (int i = 1; i < NUMBERS_OF_ITERATIONS; i++) { calculateSum(i); }

Im ersten Lauf werden wir es 1.000 Mal ausführen (weniger als der oben erwähnte Schwellenwert von 10.000). Wenn wir die Ausgabe nach der berechneSumme () -Methode durchsuchen , werden wir sie nicht finden. Dies wird erwartet, da wir es nicht oft genug angerufen haben.

Wenn wir jetzt die Anzahl der Iterationen auf 15.000 ändern und die Ausgabe erneut durchsuchen, werden wir sehen:

664 262 % com.baeldung.inlining.InliningExample::main @ 2 (21 bytes) @ 10 com.baeldung.inlining.InliningExample::calculateSum (12 bytes) inline (hot)

Wir können sehen, dass diesmal die Methode die Bedingungen für das Inlining erfüllt und die JVM sie inliniert.

Es ist bemerkenswert, noch einmal zu erwähnen, dass die JIT die Methode unabhängig von der Anzahl der Iterationen nicht einbindet, wenn sie zu groß ist. Wir können dies überprüfen, indem wir beim Ausführen der Anwendung ein weiteres Flag hinzufügen:

-XX:FreqInlineSize=10

Wie wir in der vorherigen Ausgabe sehen können, beträgt die Größe unserer Methode 12 Bytes. Das Flag -XX: FreqInlineSize begrenzt die für Inlining zulässige Methodengröße auf 10 Byte. Folglich sollte das Inlining diesmal nicht stattfinden. Und tatsächlich können wir dies bestätigen, indem wir uns die Ausgabe noch einmal ansehen:

330 266 % com.baeldung.inlining.InliningExample::main @ 2 (21 bytes) @ 10 com.baeldung.inlining.InliningExample::calculateSum (12 bytes) hot method too big

Obwohl wir den Flag-Wert hier zur Veranschaulichung geändert haben, müssen wir die Empfehlung hervorheben, den Standardwert des Flags -XX: FreqInlineSize nicht zu ändern, sofern dies nicht unbedingt erforderlich ist.

5. Schlussfolgerung

In diesem Artikel haben wir gesehen, welche Methode Inlining in der JVM enthalten ist und wie die JIT dies tut. Wir haben beschrieben, wie wir prüfen können, ob unsere Methoden für Inlining geeignet sind oder nicht, und vorgeschlagen, wie diese Informationen verwendet werden können, indem versucht wird, die Größe häufig aufgerufener langer Methoden zu verringern, die zu groß sind, um inliniert zu werden.

Schließlich haben wir gezeigt, wie wir eine heiße Methode in der Praxis identifizieren können.

Alle im Artikel erwähnten Codefragmente finden Sie in unserem GitHub-Repository.