Java Performance Chapter 4 Fonctionnement du compilateur JIT

O'Reilly Japan \ -Java Performance Résumé du chapitre 4 de ce livre

Chapitre 1 Introduction \ -Qiita Chapitre 2 Approche des tests de performances \ -Qiita Chapitre 3 Java Performance Toolbox \ -Qiita ← Article précédent Chapitre 4 Fonctionnement du compilateur JIT - Qiita ← Cet article Chapitre 5 Bases de la collecte des ordures \ -Qiita ← Article suivant

4.1 Vue d'ensemble du compilateur JIT

Exemples de différences entre les langages de compilation et d'interprétation

Lors de la lecture de deux valeurs de la mémoire principale et de leur ajout ... Un bon compilateur exécute une instruction qui lit des données, puis exécute une autre instruction, puis ajoute. Vous ne pouvez pas car l'interprète ne voit qu'une seule ligne à la fois.

Les interprètes ont l'avantage de la portabilité

La nouvelle version du CPU peut exécuter presque toutes les instructions de la version précédente du CPU, mais pas l'inverse (comme les instructions AVX du processeur Sandy Bridge d'Intel). Il existe une solution telle que l'exécution du traitement où les performances sont importantes dans la bibliothèque partagée préparée pour chaque CPU.

Java est dans une position intermédiaire, et après compilation en code octet Java, il est compilé sur chaque plateforme en même temps que l'exécution.

Compilation de points chauds

Par exemple, si vous voulez exécuter du code comme b = obj1.equals (obj2) Pour savoir quelle méthode ʻequals () doit exécuter, regardez le type (classe) de ʻobj1. Lorsque ce code est exécuté, si ʻobj1 est toujours appelé java.lang.String # equals` Optimisez pour appeler la méthode directement.

Registres et mémoire principale

Quand le compilateur est-il optimisé pour enregistrer de la mémoire principale vers les registres? Peut être mentionné

public class RegisterTest {
  private int sum;
  public void calculateSum(int n) {
    for (int i = 0; i < n; i++) {
      sum += i;
    }
  }
}

Pour ce code, avant de lire la somme de la mémoire principale L'optimisation peut être effectuée en conservant la somme dans un registre, en la bouclant et en ajoutant le résultat du calcul à additionner dans la mémoire principale.

Le registre utilisé par un autre thread ne peut pas être lu (voir chapitre 9). Les registres sont utilisés de manière particulièrement agressive lorsque l'analyse d'échappement (décrite à la fin de ce chapitre) est activée.

4.2 Réglage de base (compilateur client et compilateur serveur)

Il existe un type de client et un type de serveur. Il est ainsi appelé par les arguments de ligne de commande -client et -server.

Dans la plupart des cas, -XX n'est pas utilisé comme indicateur pour spécifier le compilateur. L'exception est la compilation à plusieurs niveaux. -XX:+TieredCompilation Un compilateur serveur est requis pour la compilation hiérarchique.

Le compilateur client commence la compilation tôt, donc il est rapide dans les premiers stades. D'autre part, le compilateur serveur prend du temps à s'optimiser.

La compilation hiérarchique est une méthode dans laquelle le compilateur serveur se compile à nouveau lorsque le code devient "chaud". La compilation hiérarchique est activée par défaut dans Java 8.

La compilation hiérarchique en Java 7 est bizarre, par exemple, elle dépasse rapidement la taille du cache de code de la JVM.

4.3 Versions du compilateur Java et JIT

Il existe trois versions comme suit

--Compilateur client 32 bits (-client)

S'il s'agit d'un système d'exploitation 32 bits, la JVM doit également être une version 32 bits. Avec un système d'exploitation 64 bits, vous pouvez utiliser l'une ou l'autre JVM.

Si le tas est de 3 Go ou moins, la version 32 bits utilise moins de mémoire et est plus rapide. Il semble que le 32 bits a un coût de référence mémoire inférieur à celui du 64 bits.

Le chapitre 8 traite de la compression des pointeurs d'objets ordinaires. Une adresse 32 bits peut être utilisée même avec une JVM 64 bits. Cependant, comme le code natif utilisé lors de l'exécution utilise une adresse 64 bits, il utilise beaucoup de mémoire.

Dans les programmes qui utilisent beaucoup les types 8 octets (long, double), la JVM 32 bits est lente car les registres 64 bits du CPU ne peuvent pas être utilisés.

32bitOS a un mur de 4Gbyte (2 ^ 32)

↓ Documents officiels connexes CompressedOops

↓ affiche le compilateur Java par défaut

java -version

↓ Documents officiels connexes java

4.4 Réglage intermédiaire

Réglage du cache de code

Lorsque la JVM compile le code, un ensemble d'instructions en langage assembleur est stocké dans le cache de code. La taille du cache de code est fixe. Lorsqu'il est plein, il ne peut plus compiler et fonctionne avec l'interpréteur.

Lors de la compilation hiérarchique avec Java7, la taille par défaut manque souvent de cache de code (certains SIers ne me permettent toujours pas d'élever la version Java à 8 ou plus tard, et dans ce cas, j'ai ce genre de problème ... )

Il n'y a aucun moyen de savoir combien de cache de code votre application a besoin, vous devez donc l'exécuter et vérifier si c'est suffisant (voir ci-dessous pour savoir comment vérifier).

---XX: XX: InitialCodeCacheSize-N: Taille initiale du cache de code. Il a par défaut 2,555,904 octets dans mon environnement

---XX: XX: ReservedCodeCacheSize = N: taille maximale. Il est par défaut de 251 658 240 octets dans mon environnement

---XX: XX: CodeCacheExpansionSize = N: Taille étendue du cache de code. Il a par défaut 65536 octets dans mon environnement

momose@momose-pc:~$ java -version
openjdk version "11.0.3" 2019-04-16
OpenJDK Runtime Environment (build 11.0.3+7-Ubuntu-1ubuntu219.04.1)
OpenJDK 64-Bit Server VM (build 11.0.3+7-Ubuntu-1ubuntu219.04.1, mixed mode, sharing)
momose@momose-pc:~$ 

Que se passe-t-il si je spécifie un grand ReservedCodeCacheSize de 1 Go pour ne pas avoir à me soucier de manquer de cache de code? La JVM réserve la zone de mémoire native pour 1 Go. Cependant, il ne sera attribué qu'une fois utilisé.

↓ Matériaux de référence [\ [tips ] \ [Java ] Comment vérifier l'utilisation de la zone CodeCache \ -Akira's Tech Notes](http://luozengbin.github.io/blog/2015-09-01-%5Btips%5D%5Bjava% 5Dcodecache% E9% A0% 98% E5% 9F% 9F% E4% BD% BF% E7% 94% A8% E7% 8A% B6% E6% B3% 81% E3% 81% AE% E7% A2% BA% E8% AA% 8D% E6% 96% B9% E6% B3% 95.html)

Vous pouvez utiliser jconsole pour surveiller la taille de votre cache de code. Si vous sélectionnez Cache de code de pool de mémoire dans le panneau Mémoire, un graphique s'affiche (jusqu'à Java 8).

On dit que Java 9 ou version ultérieure sera géré dans une zone appelée Code Heap. ↓ Onglet Mémoire dans jconsole CodeHeap.png

Selon -XX: + SegmentedCodeCache de ceci (https://docs.oracle.com/javase/jp/9/tools/java.htm), Il semble que le traitement en divisant le segment empêche la fragmentation du code et améliore l'efficacité.

Selon Java9 \ (\ based on Oracle JVM) catchup \ -Qiita, il est divisé en 3 segments selon le type de code.

Seuil de compilation

Le nombre d'exécutions affecte le moment de la compilation.

Il n'y a qu'un seul cas où vous devez ajuster le seuil de compilation. Si la somme des deux valeurs suivantes dépasse le seuil, elle sera mise en file d'attente pour le compilateur. Cela s'appelle "compilation standard" (pas le nom officiel).

--Compteur d'appels: nombre d'appels de méthode

Avec une compilation standard, un traitement long en une boucle, une méthode longue, ce n'est pas bien optimisé, Si le compteur de front arrière dépasse le seuil, seule la boucle sera compilée. Cette compilation s'appelle "OSR (remplacement sur pile)".

Le seuil de compilation standard peut être spécifié avec l'indicateur -XX: CompileThreshold = N. La valeur par défaut est 1500 pour le compilateur client et 10000 pour le compilateur serveur.

Condition de seuil de compilation OSR

Valeur du compteur de bord arrière> CompileThreshold(OnStackReplacePercentage 
 - InterpreterProfilePercentage) / 100

---XX: InterpreterProfilePercentage = N vaut 33 par défaut ---XX: OnStackReplacePercentage = N vaut par défaut 933 pour le compilateur client et 140 pour le compilateur serveur

Le seuil pour le compilateur client est donc

1500 * (933 - 33) / 100 = 13500

Ainsi, dans le cas du compilateur serveur, le seuil est

10000 * (140 - 33) / 100 = 10700

Sera

Chaque fois que la JVM atteint un point de sécurité, la valeur de chaque compteur est décrémentée. Par conséquent, toutes les méthodes ne seront pas compilées à un moment donné. Il existe donc des méthodes "gluantes" qui s'exécutent assez souvent, mais qui ne se compilent pas (pas à chaud) (ce qui est aussi l'une des raisons pour lesquelles la compilation hiérarchique est si rapide).

L'indicateur -XX: + PrintCompilation (faux par défaut). Chaque fois que vous compilez

Attribut ID de compilation d'horodatage(Niveau de compilation hiérarchique)Taille du nom de la méthode désoptimisée

Un journal avec le contenu tel que s'affiche.

L'attribut est

--% : compilation OSR --s: méthode synchronisée --! : La méthode a des lancers -- b: Compiler en mode bloquant (pas de sortie dans Java actuel) --n: wrapper de méthode native généré par le compilateur

La taille est la taille du code d'octet Java S'il est désoptimisé, vous recevrez un message indiquant qu'il a été désoptimisé.

Vous pouvez également obtenir des informations sur la compilation de programmes Java déjà en cours d'exécution.

jstat -compiler ${ID de processus}

Vous pouvez également afficher la dernière version compilée toutes les 1000 millisecondes.

jstat -printcompilation ${ID de processus} 1000

La compilation OSR prend souvent du temps.

4.5 Réglage avancé

Contenu maniaque, semble être pour les ingénieurs JVM. Il est peu probable que vous implémentiez le contenu de réglage décrit ici ...

Fil de discussion du compilateur

La compilation s'exécute de manière asynchrone et le nombre de threads du compilateur change en fonction du nombre de processeurs et du type de compilateur. Le nombre de threads peut être modifié avec -XX: CICompilerCount = N. Dans le cas de la compilation hiérarchique, 1/3 est la compilation du client et le reste est la compilation du serveur.

Spécifier -XX: + BackgroundCompilation empêchera la compilation d'être asynchrone.

En ligne

Le code accessible via Getter / Setter est intégré par des compilateurs modernes. L'intégration est activée par défaut. Il peut également être désactivé avec -XX: -Inline.

Les conditions pour l'inlining sont la chaleur et la taille du bytecode.

S'il fait chaud et que la taille du code d'octet est de 325 octets (modifiable avec -XX: MaxFreqInlineSize = N) ou moins, l'inlining est effectué. Si la taille est de 35 octets ou moins (modifiable avec -XX: MaxInlineSize = N), l'inlining est effectué sans condition.

Analyse d'évasion

Optimisation lorsque -XX: + DoEscapeAnalysis (la valeur par défaut est true) est activé. Il semble faire diverses choses, mais par exemple

public class Factorial {
    private BigInteger factorial;
    private int n;
    public Factorial(int n) {
        this.n = n;
    }
    public synchronized BigInteger getFactorial() {
        if (factorial == null)
            factorial = ...;
        return factorial;
    }
}

Contre

ArrayList<BigInteger> list = new ArrayList<BigInteger>();
for (int i = 0; i < 100; i++) {
    Factorial factorial = new Factorial(i);
    list.add(factorial.getFactorial());
}

Une optimisation avancée est effectuée (dans de rares cas, il peut y avoir un bug)

Désoptimisé

La non-optimisation n'est plus un entrant, elle se transforme en zombie et le GC tourne

Lorsqu'il n'est plus un participant

Il y en a deux, et la première consiste à effectuer une optimisation en s'associant à la classe d'implémentation pour une interface spécifique, mais si cette prémisse est cassée, elle n'est pas optimisée. La seconde est l'implémentation de la compilation hiérarchique, qui est désoptimisée en la marquant comme n'étant pas un entrant une fois la compilation par le compilateur serveur terminée.

Code zombie

Si made zombie apparaît dans le journal de compilation, le code non-participant sera abandonné et le GC fonctionnera.

↓ Matériaux de référence Java-JA13-Architect-evans.pdf

4.7 Niveau de compilation hiérarchique

--0: Code exécuté par l'interpréteur --1: Code compilé par le compilateur client en mode simple --2: Code compilé par le compilateur client en mode restreint ―― 3: Code compilé par le compilateur client en mode complet --4: Code compilé par le compilateur serveur

Il fonctionne au niveau 0 dans l'état initial, et dans la plupart des cas, il semble être compilé au niveau 3 d'abord, puis au niveau 4. On dit que les niveaux 1 et 2 sont utilisés lorsque la file d'attente du compilateur est pleine (elle se compile à grande vitesse car elle n'utilise pas de profileur). Naturellement, le code non optimisé remonte au niveau 0.

4.8 Résumé

--La compilation hiérarchique la plus solide

De plus, le modificateur final semble ** n'avoir aucun effet sur les performances **.

Recommended Posts

Java Performance Chapter 4 Fonctionnement du compilateur JIT
Fonctionnement du compilateur JVM JIT
Java Performance Chapitre 1 Introduction
Java Performance Chapitre 3 Boîte à outils Java Performance
Java Performance Chapter 2 Approche des tests de performances
[Java] Fonctionnement de Spring DI
Java Performance Chapitre 5 Bases de la récupération de place
[Java] Comment utiliser la classe File
[Java] Comment utiliser la méthode toString ()
Etudier comment utiliser le constructeur (java)
[Traitement × Java] Comment utiliser la boucle
[Java] Comment régler la date sur 00:00:00
[Java] Comment obtenir le répertoire actuel
[Traitement × Java] Comment utiliser la classe
Comment obtenir la date avec Java
[Traitement × Java] Comment utiliser la fonction
[Java] Comment utiliser la classe Calendar
[Note] Java: créez un projet simple tout en apprenant comment fonctionne le fichier de paramètres.
Découvrez comment HashMap fonctionne en Java
[Java] (pour MacOS) Méthode de définition du chemin de classe
Comment utiliser la méthode replace () (Java Silver)
[Java] Comment obtenir l'URL redirigée finale
[Java] Mémo sur la façon d'écrire la source
Efficace Java Chapitre 2
Effective Java Chapitre 6 34-35
Effective Java Chapitre 4 15-22
Java efficace Chapitre 3
Comment fonctionne jul-to-slf4j
[Java] Comment obtenir l'URL de la source de transition
[Java] Comment omettre le constructeur privé dans Lombok
Comment écrire Scala du point de vue de Java
[Java] Comment extraire le nom du fichier du chemin
[Java] Comment obtenir la valeur maximale de HashMap
[Android Studio] [Java] Comment réparer l'écran verticalement