Inlining de méthode dans la JVM

1. Introduction

Dans ce didacticiel, nous examinerons la méthode d'inlining de la machine virtuelle Java et son fonctionnement.

Nous verrons également comment obtenir et lire les informations liées à l'inlining depuis la JVM et ce que nous pouvons faire avec ces informations afin d'optimiser notre code.

2. Quelle est la méthode Inlining?

Fondamentalement, l' inlining est un moyen d'optimiser le code source compilé au moment de l'exécution en remplaçant les appels des méthodes les plus souvent exécutées par ses corps.

Bien que la compilation soit impliquée, elle n'est pas effectuée par le compilateur javac traditionnel , mais par la JVM elle-même. Pour être plus précis, c'est la responsabilité du compilateur Just-In-Time (JIT) , qui fait partie de la JVM; javac ne produit qu'un bytecode et laisse JIT faire la magie et optimiser le code source.

L'une des conséquences les plus importantes de cette approche est que si nous compilons le code en utilisant l'ancien Java, il en va de même. class sera plus rapide sur les JVM plus récentes. De cette façon, nous n'avons pas besoin de recompiler le code source, mais uniquement de mettre à jour Java.

3. Comment le JIT fait-il?

Essentiellement, le compilateur JIT essaie d'intégrer les méthodes que nous appelons souvent afin d'éviter la surcharge d'un appel de méthode . Il prend deux choses en considération pour décider si une méthode doit être intégrée ou non.

Premièrement, il utilise des compteurs pour suivre le nombre de fois où nous invoquons la méthode. Lorsque la méthode est appelée plus d'un certain nombre de fois, elle devient «chaude». Ce seuil est fixé à 10 000 par défaut, mais nous pouvons le configurer via l'indicateur JVM lors du démarrage de Java. Nous ne voulons certainement pas tout intégrer car cela prendrait du temps et produirait un énorme bytecode.

Nous devons garder à l'esprit que l'inlining n'aura lieu que lorsque nous atteindrons un état stable. Cela signifie que nous devrons répéter l'exécution plusieurs fois pour fournir suffisamment d'informations de profilage pour le compilateur JIT.

De plus, être «à chaud» ne garantit pas que la méthode sera intégrée. S'il est trop gros, le JIT ne le mettra pas en ligne. La taille acceptable est limitée par l' indicateur -XX: FreqInlineSize = , qui spécifie le nombre maximal d'instructions de bytecode à insérer pour une méthode.

Néanmoins, il est fortement recommandé de ne pas modifier la valeur par défaut de cet indicateur à moins que nous ne soyons absolument certains de savoir quel impact il pourrait avoir. La valeur par défaut dépend de la plate-forme - pour Linux 64 bits, elle est de 325.

Le JIT inline statique , privé , ou finales méthodes en général . Et si les méthodes publiques sont également candidates à l'inlining, toutes les méthodes publiques ne le seront pas nécessairement. La machine virtuelle Java doit déterminer qu'il n'existe qu'une seule implémentation d'une telle méthode . Toute sous-classe supplémentaire empêcherait l'inlining et les performances diminueront inévitablement.

4. Recherche de méthodes chaudes

Nous ne voulons certainement pas deviner ce que fait le JIT. Par conséquent, nous avons besoin d'un moyen de voir quelles méthodes sont intégrées ou non. Nous pouvons facilement y parvenir et consigner toutes ces informations sur la sortie standard en définissant des indicateurs JVM supplémentaires au démarrage:

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

Le premier drapeau enregistrera lorsque la compilation JIT aura lieu. Le deuxième indicateur active des indicateurs supplémentaires, y compris -XX: + PrintInlining , qui affichera quelles méthodes sont incorporées et où.

Cela nous montrera les méthodes intégrées sous la forme d'un arbre. Les feuilles sont annotées et marquées de l'une des options suivantes:

  • inline (hot) - cette méthode est marquée comme hot et est inline
  • trop gros - la méthode n'est pas chaude, mais aussi son bytecode généré est trop gros, donc il n'est pas en ligne
  • méthode chaude trop grande - c'est une méthode chaude, mais elle n'est pas en ligne car le bytecode est trop grand

Nous devons prêter attention à la troisième valeur et essayer d'optimiser les méthodes avec l'étiquette «méthode chaude trop grande».

Généralement, si nous trouvons une méthode chaude avec une instruction conditionnelle très complexe, nous devrions essayer de séparer le contenu de l' instruction if et d'augmenter la granularité afin que le JIT puisse optimiser le code. La même chose vaut pour le commutateur et lucratif instructions de boucle.

Nous pouvons conclure qu'une méthode manuelle en ligne est quelque chose que nous n'avons pas besoin de faire pour optimiser notre code. La JVM le fait plus efficacement, et nous rendrions peut-être le code long et difficile à suivre.

4.1. Exemple

Voyons maintenant comment nous pouvons vérifier cela dans la pratique. Nous allons d'abord créer une classe simple qui calcule la somme des N premiers entiers positifs consécutifs:

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; } }

Ensuite, une méthode simple utilisera la classe pour effectuer le calcul:

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

Enfin, nous appellerons la méthode un certain nombre de fois et verrons ce qui se passe:

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

Dans la première exécution, nous allons l'exécuter 1000 fois (moins que la valeur seuil de 10000 mentionnée ci-dessus). Si nous recherchons la sortie de la méthode CalculateSum () , nous ne la trouverons pas. Ceci est attendu car nous ne l'avons pas appelé assez de fois.

Si nous changeons maintenant le nombre d'itérations à 15000 et recherchons à nouveau la sortie, nous verrons:

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

On voit que cette fois la méthode remplit les conditions de l'inlining et la JVM l'inline.

Il convient de mentionner à nouveau que si la méthode est trop grande, le JIT ne la mettra pas en ligne, quel que soit le nombre d'itérations. Nous pouvons vérifier cela en ajoutant un autre indicateur lors de l'exécution de l'application:

-XX:FreqInlineSize=10

Comme nous pouvons le voir dans la sortie précédente, la taille de notre méthode est de 12 octets. L' indicateur -XX: FreqInlineSize limitera la taille de la méthode éligible pour l'inlining à 10 octets. Par conséquent, l'inlining ne devrait pas avoir lieu cette fois. Et en effet, nous pouvons le confirmer en jetant un autre regard sur la sortie:

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

Bien que nous ayons changé la valeur de l'indicateur ici à des fins d'illustration, nous devons insister sur la recommandation de ne pas modifier la valeur par défaut de l' indicateur -XX: FreqInlineSize sauf si c'est absolument nécessaire.

5. Conclusion

Dans cet article, nous avons vu quelle méthode d'inlining est dans la JVM et comment le JIT le fait. Nous avons décrit comment nous pouvons vérifier si nos méthodes sont éligibles pour l'inlining ou non et avons suggéré comment utiliser ces informations en essayant de réduire la taille des méthodes longues fréquemment appelées qui sont trop grandes pour être intégrées.

Enfin, nous avons illustré comment nous pouvons identifier une méthode chaude dans la pratique.

Tous les extraits de code mentionnés dans l'article se trouvent dans notre référentiel GitHub.