Cet article a été initialement publié par Ampere Computing
J'ai vu un article de blog récent sur GPROFNG, un nouvel outil d'analyse des performances GNU. Ce blog utilise un programme de multiplication vectoriel matriciel écrit dans le langage C comme exemple. Je suis un programmeur Java ™ et j'utilise des outils conçus pour la programmation C compilée statiquement compilée est souvent difficile à analyser les applications Java car les programmes Java sont compilés au moment de l'exécution. Dans cet article de blog, je montrerai que GPROFNG est facile à utiliser et est très utile pour plonger dans le comportement dynamique des applications Java.
La première étape consiste à écrire un programme de multiplication matriciel. J'ai écrit un programme complet de matrice de multiplication de matrice car il n'est pas plus difficile que les vecteurs de multiplication matricielle. Il existe trois méthodes principales: une méthode calcule la multiplication et l'opération d'addition la plus interne, une méthode combine le fonctionnement de multiplication et d'addition en un seul élément du résultat, et une méthode itère sur chaque élément du résultat.
J'enveloppe les calculs dans un programme de test simple pour calculer à plusieurs reprises le produit matriciel pour garantir que le temps est reproductible. (Voir note 1.) Le programme imprime le temps où chaque multiplication matricielle commence (par rapport au moment où la machine virtuelle Java est démarrée) et le temps nécessaire pour chaque multiplication matricielle. Ici, j'ai exécuté le programme de test pour multiplier deux matrices 8000 × 8000. Le programme de test a répété le calcul 11 fois et pour mieux mettre en évidence le comportement ultérieur, dormir pendant 920 millisecondes entre les répétitions:
$ numactl --cpunodebind=0 --membind=0 -- \ java -XX:+UseParallelGC -Xms31g -Xmx31g -Xlog:gc -XX:-UsePerfData \ MxV -m 8000 -n 8000 -r 11 -s 920
Figure 1: Exécution du programme de multiplication de la matrice
Veuillez noter que la deuxième répétition prend 92% du temps de la première répétition, tandis que la dernière répétition ne prend que 89% du temps de la première répétition. Ces changements dans le temps d'exécution confirment que les programmes Java ont besoin de temps pour se réchauffer.
La question est: puis-je utiliser gprofng pour voir ce qui se passe entre les premier et les dernières répétitions, entraînant des améliorations des performances?
Une façon de répondre à cette question est d'exécuter le programme et de faire collecter des informations en cours d'exécution. Heureusement, c'est facile: j'ai juste besoin de préfixer la ligne de commande pour collecter ce que GProfng appelle des informations "expérimentales":
$ numactl --cpunodebind=0 --membind=0 -- \ gprofng collect app \ java -XX:+UseParallelGC -Xms31g -Xmx31g -Xlog:gc --XX:-UsePerfData \ MxV -m 8000 -n 8000 -r 11 -s 920
Figure 2: Exécution du programme de multiplication de la matrice sous gprofng
La première chose à noter est que, comme tout outil d'analyse des performances, la collecte d'informations d'analyse des performances peut coûter l'application. Par rapport aux courses non analysées précédentes, le GPROFNG ne semble pas provoquer des frais généraux significatifs.
Je peux ensuite demander comment le temps est passé dans Gprofng toute l'application. (Voir note 2.) Pour toute la course, les 24 méthodes les plus populaires de GProfng sont:
$ gprofng display text test.1.er -viewmode expert -limit 24 -functions
Figure 3: 24 façons de montrer le gprofng
le plus chaudLa vue de fonction ci-dessus donne le temps de processeur exclusif et d'inclusion pour chaque méthode, exprimée en pourcentage de secondes et temps total du processeur. La fonction nommée est une pseudo-fonction générée par gprofng, avec la valeur totale de divers indicateurs. Dans ce cas, je vois que le temps de processeur total de l'ensemble de l'application est de 1,201 secondes.
Les méthodes d'application (celles de la classe MXV) y sont toutes, occupant la majeure partie du temps du CPU, mais il existe d'autres méthodes, y compris le compilateur d'exécution JVM (compilation :: compilation), et d'autres fonctions qui ne font pas partie de Le programme de multiplication matricielle. Cet affichage de l'ensemble de l'exécution du programme capture les codes d'allocation (MXV.Allocate) et d'initialisation (MXV.Initialize), qui ne m'intéressent pas beaucoup car ils font partie du programme de test, ne sont utilisés que pendant le démarrage et sont multipliés Avec la matrice, cela n'a guère d'importance.
Je peux utiliser gprofng pour suivre les parties de l'application qui m'intéresse. Une grande caractéristique de GPROFNG est qu'après avoir collecté des expériences, je peux appliquer le filtre aux données collectées. Par exemple, regardez ce qui se passe au cours d'un intervalle de temps spécifique, ou ce qui se passe lorsqu'une méthode spécifique est sur la pile d'appels. À des fins de démonstration et pour faciliter le filtrage, j'ai ajouté un appel stratégique à Thread.Sleep (MS) pour faciliter l'écriture de filtres en fonction des phases du programme (divisé par un second intervalle). C'est pourquoi la sortie du programme de la figure 1 ci-dessus est d'environ une seconde entre chaque répétition, même si chaque multiplication matricielle ne prend que 0,1 seconde environ.
gprofng est scriptable, j'ai donc écrit un script pour extraire une seule seconde de l'expérience GProfng. La première seconde concerne le démarrage de la machine virtuelle Java.
Figure 4: La méthode la plus chaude pour filtrer la première seconde. Dans cette seconde, la multiplication de la matrice a été retardée manuellement afin que je puisse montrer le démarrage JVM
Je peux voir que le compilateur commence à l'exécution (par exemple, compilation :: compile_java_method, 16% du temps CPU), même si une méthode de l'application n'a pas encore commencé à s'exécuter. (L'appel de multiplication de la matrice a été retardé par l'appel de sommeil que j'ai inséré.)
La première seconde est une seconde, au cours de laquelle les méthodes d'allocation et d'initialisation s'exécutent avec diverses méthodes JVM, mais le code de multiplication matriciel n'a pas encore commencé.
Figure 5: La méthode la plus chaude de la deuxième seconde. L'allocation et l'initialisation de la matrice rivalisent avec le démarrage JVM
Maintenant que le démarrage JVM et l'allocation et l'initialisation du tableau ont été terminés, il y a la première répétition du code de multiplication de la matrice dans la troisième seconde, comme le montre la figure 6. Mais notez que le code de multiplication de la matrice est en concurrence avec le compilateur Java Runtime (par exemple CompileBroker :: invoke_compiler_on_method, 8% sur la figure 6) qui compile les méthodes car le code de multiplication matriciel est chaud.
Malgré cela, le code de multiplication de la matrice (par exemple, "inclut" le temps dans la méthode MXV.Main, 91%) obtient toujours la majeure partie du temps du CPU. Le temps d'inclusion indique que la multiplication de la matrice (par exemple, MXV.Multiply) prend 0,100 secondes de processeur, ce qui est cohérent avec le temps réel rapporté par l'application de la figure 2. (Il faut un peu de temps pour collecter et signaler le temps réel, qui est en dehors du temps du CPU gprofng compte pour mxv.Multiply.)
Figure 6: La méthode la plus chaude de la troisième seconde, indiquant que le compilateur d'exécution est en concurrence avec la méthode de multiplication matricielle
Dans cet exemple particulier, la multiplication matricielle ne rivalise pas vraiment pour le temps du CPU, car les tests s'exécutent sur un système multiprocesseur qui a un grand nombre de cycles d'inactivité et le compilateur d'exécution s'exécute en tant que thread séparé. Dans des cas plus restrictifs, comme sur les machines partagées avec des charges lourdes, 8% du temps passé par le compilateur d'exécution peut être un problème. D'un autre côté, le temps passé dans le compilateur d'exécution se traduit par une implémentation de méthode plus efficace, donc si je calcule de nombreuses multiplications matricielles, ce serait un investissement que je suis prêt à faire.
À la cinquième seconde, le code de multiplication de la matrice a la machine virtuelle Java.
Figure 7: Toutes les méthodes s'exécutent pendant la cinquième seconde, indiquant que seule la méthode de multiplication de la matrice est active
Veuillez noter l'allocation de 60% / 30% / 10% entre MXV.OneCell, MXV.MultiplyAdd et Mxv.Multiply en termes de secondes CPU exclusives. La méthode MXV.MultiplyAdd ne calcule que la multiplication et l'ajout: mais c'est la méthode la plus interne dans la multiplication matricielle. MXV.OneCell a une boucle pour appeler MXV.MultiplyAdd. Je peux voir que les frais généraux et les appels de boucle (conditions d'évaluation et le transfert de contrôle) fonctionnent relativement plus que les opérations arithmétiques directes dans MXV.Multiplyadd. (MXV.OneCell a un temps exclusif de 0,060 seconde CPU, tandis que MXV.MultiplyAdd est de 0,030 seconde de processeur, reflétant cette différence.) La fréquence d'exécution de la boucle externe ne l'a pas encore compilée. , mais la méthode utilise 0,010 seconde de CPU.
La multiplication matricielle se poursuit jusqu'à la neuvième seconde, lorsque le compilateur d'exécution JVM recommence et constate que Mxv.Multiply est devenu très chaud.
Dans la dernière répétition, le code de multiplication de la matrice utilise pleinement la machine virtuelle Java.
Figure 9: La dernière répétition du programme de multiplication de la matrice, montrant la configuration finale du code
J'ai montré la facilité d'arriver facilement à l'exécution des applications Java en utilisant GPROFNG pour l'analyse des performances. En utilisant la fonction de filtrage de GProfNG pour vérifier les expériences par Time Slice, j'ai pu vérifier les étapes du programme d'intérêt. Par exemple, l'exclusion de la phase d'allocation et d'initialisation de l'application et ne montrant qu'une répétition du programme lorsque le compilateur d'exécution exécute sa magie me permet de mettre en évidence les performances qui s'améliorent à mesure que le code chaud est progressivement compilé.
Pour les lecteurs qui souhaitent connaître GPROFNG, voici un article de blog avec des vidéos d'introduction sur GPROFNG, y compris des instructions sur la façon de l'installer sur Oracle Linux.
Merci à Ruud van der Pas, Kurt Goebel et Vladimir Mezentsev pour leurs conseils et leur soutien technique, et à Elena Zannoni, David Banman, Craig Hardy et Dave Neary pour m'avoir encouragé à écrire ce billet de blog.
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!