Profilez un programme avec VisualVM

Comprenez la dégradation de la performance

Imaginez qu’un utilisateur soulĂšve un rapport de bug dĂ©clarant que le calculateur de taille de selle de dragon prenait trop de temps pour estimer la taille de selle correcte. Les dompteurs de dragons sont des personnes occupĂ©es, et un tel dĂ©lai est inacceptable au souverain rĂ©gnant sur les Huit Royaumes. 🐉 MĂȘme si vous n’avez pas peur de la colĂšre de ce souverain, il est important de demander :  « Pourquoi est-ce que notre logiciel se mettrait tout Ă  coup Ă  ralentir ? »

De nombreux facteurs peuvent avoir un impact sur les temps de trajet. Avez-vous déjà pris les transports publics et eu le choix entre différents itinéraires ? Le choix du mauvais itinéraire peut vous faire perdre une heure ! Les applications qui déterminent les itinéraires sont efficaces pour vous donner des options sur la durée que nécessiteraient les différents chemins.

Pensez maintenant Ă  votre code en exĂ©cution sur la JVM avec toutes les branches ou mĂ©thodes diffĂ©rentes qu’il pourrait prendre en fonction des donnĂ©es et arguments que vous lui donnez. Votre logiciel est Ă©galement vulnĂ©rable Ă  la dĂ©gradation des performances à chaque fois que vous ajoutez une nouvelle mĂ©thode ou branche oĂč le code pourrait devoir se rendre. Pourquoi cela ? Lorsque votre code est en exĂ©cution, la JVM effectue gĂ©nĂ©ralement un voyage de classe en classe, de mĂ©thode en mĂ©thode, et essaie d’atteindre son but. De façon interne, elle fait de son mieux pour rendre l’exĂ©cution de votre code la plus rapide possible, un peu comme ces applications qui trouvent des itinĂ©raires.

Les problĂšmes liĂ©s Ă  la performance de Java sont gĂ©nĂ©ralement causĂ©s par des dĂ©veloppeurs. Nous concevons souvent des logiciels que mĂȘme la JVM ne peut pas accĂ©lĂ©rer, car soit les algorithmes rĂ©solvent les problĂšmes de la maniĂšre la plus lente possible, soit ils comportent une autre erreur.

Lorsque vous rencontrez un problÚme de performance dans votre code, essayez de le résoudre en :

  1. Mesurant votre code pour dĂ©terminer quelle action le ralentit et oĂč cela se produit dans le code.

  2. EnquĂȘtant sur le code non performant et questionnant ce qu’il essaie d’accomplir et pourquoi.

  3. Supprimant ou simplifiant le code si cela peut ĂȘtre fait immĂ©diatement.

  4. Mesurant votre code Ă  nouveau pour voir si vous l’avez amĂ©liorĂ© !

Est-ce que vous ĂȘtes en train de dire que je ralentis peut-ĂȘtre mon application ? Comment cela est-il possible ?

Vous pouvez faire de nombreuses choses pour ralentir votre logiciel sans vous en rendre compte. Les causes les plus courantes impliquent l’écriture d’algorithmes inefficaces et de code qui empĂȘche un autre code de s’exĂ©cuter aussitĂŽt qu’il le pourrait.

Regardons plusieurs causes Ă  tour de rĂŽle !

Cause de dégradation de performance n° 1 : algorithmes inefficaces

Pour rappel, un algorithme est une sĂ©rie d’étapes qui vous permettent de dĂ©finir la façon dont vous traiterez les donnĂ©es entrantes et arriverez au rĂ©sultat escomptĂ©.

Lorsque vous Ă©crivez des mĂ©thodes et des fonctions, vous liez gĂ©nĂ©ralement une sĂ©rie de dĂ©clarations les unes aux autres dans votre langage de programmation. Ces dĂ©clarations rĂ©solvent certains problĂšmes en inspectant vos donnĂ©es et en appliquant des rĂšgles que vous jugez utiles pour rĂ©soudre une partie spĂ©cifique de votre problĂšme. En dĂ©cidant comment implĂ©menter ces rĂšgles en tant que code, vous dĂ©finissez un algorithme ou une sĂ©rie d’étapes que votre code devra exĂ©cuter.

En fonction de l’algorithme, vous implĂ©menterez du code diffĂ©rent qui impactera la vitesse Ă  laquelle il gĂšre diffĂ©rentes informations entrantes. Par exemple, si vous aviez une rangĂ©e de girafes en ordre de taille, comment dĂ©termineriez-vous laquelle est la plus grande ? Vous pourriez commencer avec la premiĂšre de la sĂ©rie et mesurer chaque girafe. Cela pourrait aller vite s’il y en a deux, mais ralentirait avec un troupeau plus important. Le fait de mesurer trente girafes prendrait beaucoup plus longtemps, et les perturberait probablement. Étant donnĂ© qu’elles sont dĂ©jĂ  placĂ©es en ordre, il serait plus efficace de se rendre Ă  la fin de la rangĂ©e. Cet algorithme ne ralentira jamais !

Prendre le temps de rĂ©flĂ©chir Ă  la façon dont votre code se met Ă  l’échelle pour gĂ©rer efficacement diffĂ©rentes donnĂ©es entrantes peut le maintenir performant. Ce n’est pas toujours Ă©vident ou facile, surtout si vous pensez que vous ne verrez jamais plus de deux girafes !

Nous disons que cet algorithme a une complexitĂ© temporelle d’O.(n) (Prononcez « Ordre N »).

Cause de dégradation de performance n° 2 : blocage du code

La majoritĂ© de ce que vous Ă©crivez en tant que dĂ©veloppeur est du code sĂ©quentiel. C’est-Ă -dire que vous Ă©crivez des algorithmes qui prennent une Ă©tape Ă  la fois, dans l’ordre. Pour qu’une Ă©tape dĂ©marre, la prĂ©cĂ©dente doit ĂȘtre terminĂ©e. Toute dĂ©claration que d’autres dĂ©clarations attendent est appelĂ©e une dĂ©claration bloquante.

Les logiciels sont tellement rapides que cela ne pose pas de problĂšmes en gĂ©nĂ©ral. NĂ©anmoins, imaginez ce qui pourrait se produire si votre dĂ©claration bloquante impliquait une opĂ©ration lente, comme l’ouverture d’un fichier ou l’exĂ©cution d’un algorithme inefficace, qui prend plusieurs secondes. Eh bien, la prochaine dĂ©claration devrait attendre que ce soit terminĂ©.

Attendez, est-ce que chaque étape ne dépend pas de la précédente ? Comment éviter cela ?

Dans de nombreux cas, une dĂ©claration dĂ©pend d’une dĂ©claration prĂ©cĂ©dente, mais pas toujours. Par exemple, pensez Ă  l’équation de Pythagore pour calculer la surface d’un triangle, aÂČ + bÂČ = cÂČ. Devez-vous attendre de calculer le cĂŽté a au carré avant de calculer le cĂŽté b au carrĂ© ? Si les calculs de aÂČ et bÂČ Ă©taient des opĂ©rations lentes, vous pourriez mĂȘme les effectuer en mĂȘme temps ; c’est ce qu’on appelle une solution concurrente (ou parallĂšle). 

Et maintenant, comment savoir si une mĂ©thode a Ă©tĂ© si mal Ă©crite qu’elle a ralenti votre application entiĂšre ? MĂȘme si vous ne pouvez pas utiliser d’application de calcul d’itinĂ©raire pour naviguer dans les portions moins performantes de votre code, vous pouvez surveiller la façon dont il s’exĂ©cute pour trouver quels algorithmes le ralentissent – un peu comme si vous regardiez diffĂ©rents indicateurs de circulation qui montrent les bouchons sur des trajets spĂ©cifiques. Vous utilisez un outil de profilage de code.

Qu’est-ce que le profilage ?

Le profilage correspond Ă  tenir un chronomĂštre devant vos mĂ©thodes et Ă  mesurer le temps qu’elles prennent Ă  renvoyer un rĂ©sultat. ⏱ Il vous permet de trouver les mĂ©thodes lentes dans votre code en mesurant et comparant vos appels pour trouver les moins performants. Il peut aussi capturer d’autres mĂ©triques concernant votre application. Il vous permet de trouver des problĂšmes affectant la vitesse Ă  laquelle votre application se comporte et avec quelle efficacitĂ© elle utilise sa mĂ©moire disponible. Voyons comment faire cela dans la section suivante !

Utilisez VisualVM pour profiler votre code

Pour profiler votre application, vous pouvez utiliser VisualVM, un outil de profilage Java dĂ©veloppĂ© par Oracle (les constructeurs de Java), et fourni avec leur JDK. Entre autres, il vous permet de mesurer les mĂ©thodes de vos applications Java et leur temps d’exĂ©cution. Vous pouvez utiliser VisualVM pour vous connecter Ă  toute application Java en marche.

Téléchargez VisualVM

Vous pouvez tĂ©lĂ©charger la derniĂšre version de VisualVM depuis ce lien, ou vous pouvez utiliser celle qui est fournie avec le JDK. Essayez de dĂ©marrer jvisualvm s’il est dĂ©jĂ  installĂ©.

  • Trouvez vos informations de distribution Java.
    Vous pouvez gĂ©nĂ©ralement trouver jvisualvm en affichant votre variable d’environnement JAVA_HOME et en regardant dans le dossier bin qui y est stockĂ©. Cela varie en fonction de la distribution. Essayez de regarder dans les emplacements suivants :

    • sur un Mac avec OS-X, vous trouverez votre distribution Java sous  /Library/Java/JavaVirtualMachines;

    • sous Windows, vous trouverez gĂ©nĂ©ralement vos installations Java sous  c:\Program Files\Java;

    • avec les distributions Debian Linux, vous verrez que votre dpkg a installĂ© JDK sous  /usr/lib/jvm. 

  • Localisez le fichier.
    Entrez dans le rĂ©pertoire JDK et naviguez jusqu’au rĂ©pertoire bin.
    Sous celui-ci, vous trouverez un fichier nommé  jvisualvm. Par exemple, dans une installation Windows, il ressemblera à : 

Le fichier jvisualVM apparaĂźt dans le dossier.
Dossier JDK avec VisualVM
  • DĂ©marrez le fichier en double-cliquant dessus.

Ta-da! 🎉  À prĂ©sent, vous ĂȘtes prĂȘt Ă  commencer Ă  profiler.

Profilez un programme avec VisualVM

Commençons par vĂ©rifier du code qui comporte des bugs. Pour regarder la version du projet de calculateur de tour de taille de dragon comportant un bug de performance, vous pouvez utiliser le mĂȘme rĂ©pertoire que vous avez clonĂ© en partie 2. Cette fois, consultez la branche performance-bug.

Vous pouvez utiliser le menu VCS d’IntelliJ pour consulter cela ou utiliser la commande Git CLI :

git checkout performance-bug

Ensuite, exĂ©cutez le programme DragonSaddleSizeGuesser et constatez le temps d’exĂ©cution de plusieurs secondes.

Pour enquĂȘter sur les raisons d’une telle lenteur de l’application DragonSaddleSizeGuesser, vous devez dĂ©marrer VisualVM. Le volet Applications sur la gauche affiche les applications Java en exĂ©cution que vous pouvez immĂ©diatement commencer Ă  profiler.

Sur la gauche, apparaßt le volet Applications comprenant les applications Java en exécution ou profilables. Parmi elles, se trouve VisualVM.
VisualVM - Vue des programmes JVM

Cliquez dessus pour voir toute application Java en exĂ©cution sur votre ordinateur. En cliquant sur le process VisualVM, vous pouvez explorer les volets fournis. Nous rechercherons le code lent, mais je vais tout d’abord vous donner une vue d’ensemble rapide.

Faisons cela ensemble. VisualVM comporte beaucoup de fonctionnalitĂ©s, et nous nous concentrerons uniquement sur la surveillance du temps pris par nos mĂ©thodes. Pour vous donner une vue d’ensemble rapide, j’utiliserai VisualVM pour qu’il se connecte Ă  lui-mĂȘme, car il s’agit simplement d’un autre process Java, et je vous ferai une visite guidĂ©e en deux minutes.

Vous avez vu la façon dont cliquer sur CPU sous la vue du moniteur a affiché le temps total passé dans chaque méthode ? Nous allons utiliser ceci pour trouver notre méthode lente.

Voici un dĂ©fi : pouvez-vous dĂ©marrer votre DragonSaddleSizeGuesser et vous y connecter avec VisualVM ? N’y passez pas plus de cinq minutes.

Avez-vous dĂ©jĂ  abandonnĂ© ? Je parie que vous vous demandez pourquoi le DragonSaddleSizeGuesser n’arrĂȘte pas de disparaĂźtre. Le problĂšme de notre programme, c’est que, mĂȘme avec son bug de performance, il s’agit toujours d’une application rapide qui calcule une taille de selle et arrĂȘte de s’exĂ©cuter, en gĂ©nĂ©ral en moins de 15 secondes. C’est peut-ĂȘtre trop lent pour nous, mais cela rend difficile de le trouver et s’y connecter dans VisualVM.

Heureusement pour vous, il existe un plug-in dans VisualVM qui vous permet de profiler une application Ă  partir du moment oĂč elle dĂ©marre. Laissez-moi vous montrer comment l’installer :

Comme vous l’avez vu, nous avons suivi le process suivant :

  • PremiĂšrement, cliquez sur Tools et sĂ©lectionnez l’option Plugins.

  • Maintenant, dĂ©filez vers le bas et sĂ©lectionnez le plug-in Startup Time Profiler.

  • DĂ©filez vers le bas et acceptez les modalitĂ©s et conditions.

  • Cliquez sur Installer !

Cela a maintenant ajouté une icÎne supplémentaire dans VisualVM avec un petit chronomÚtre.

Le petit chronomĂštre correspond Ă  l'icĂŽne du profiler Startup Time.
IcĂŽne du profiler Startup Time

Comment utiliser ceci pour trouver la méthode qui ralentit mon application ?

Pour trouver la mĂ©thode qui a ralenti notre DragonSaddleSizeGuesser, nous devons l’exĂ©cuter avec VisualVM. Contrairement Ă  l’exemple prĂ©cĂ©dent, nous allons paramĂ©trer VisualVM pour qu’il attende notre programme, afin qu’il puisse commencer le profilage dĂšs le moment oĂč il dĂ©marre.

Faisons-le ensemble :

Nous savons dĂ©sormais quelle mĂ©thode est notre coupable, mais comment l’avons-nous dĂ©couvert ? DĂ©taillons ce qu’il s’est passĂ© ici.

Nous avons cliquĂ© sur l’icĂŽne chronomĂštre de notre profileur startup et sĂ©lectionnĂ© la bonne JVM pour notre application. Nous utilisons JDK 8, car c’est la version utilisĂ©e par DragonSaddleSizeGuesser.

  1. Nous avons sélectionné un port de 5130, qui aide notre JVM à envoyer des données à VisualVM.

  2. Sous Start profiling from classes, nous avons entré le nom de paquet com.openclassrooms.debugging.**. Le double « * » fait que VisualVM profile toutes les méthodes des classes et sous-classes de ce paquet.

  3. VisualVM nous a alors donné quelques commandes à copier en utilisant le bouton Copy to Clipboard (Copier vers le presse-papiers).

  4. Nous avons ensuite ouvert une console et nous nous sommes placĂ©s Ă  le rĂ©pertoire build/libs de notre projet (si le rĂ©pertoire n’existe pas, exĂ©cuter la commande gradlew build).

  5. Ainsi, nous pouvions exécuter notre application via le JAR qui est généré grùce à la commande : java <option copiée de VisualVM> -noverify -jar dragon-saddle-size-checker-1.0-SNAPSHOT.jar.

  6. VisualVM nous a prĂ©venus que l’application avait dĂ©jĂ  terminĂ© et a demandĂ© si nous voulions voir le rĂ©sultat. En sĂ©lectionnant Oui, nous avons pu prendre notre temps pour enquĂȘter sur le problĂšme.

Nous avons appris que la méthode que nous recherchons est :

com.openclassrooms.debugging.DragonSaddleSizeEstimator.enterAProgrammaticTrance()

.

Comment savons-nous que c’est bien la cause ?

Dans VisualVM, nous avons cliqué sur  Hot Spots, et cela nous a montré une vue contenant ce qui suit :

Il y a plusieurs colonnes, de gauche Ă  droite : Hot Spots Method, Self Time, Total Time, Invocations.
Hot Spots de VisualVM

La vue  Hot Spots  montre une liste ordonnĂ©e de mĂ©thodes, classĂ©es selon la quantitĂ© de temps passĂ© dans la mĂ©thode. Comme un prĂ©sentateur mĂ©tĂ©o qui vous dit qu’il fait 40° C Ă  Tenerife ☀, si vous regardez la ligne supĂ©rieure du tableau ci-dessus et que vous regardez sous Self Time (Temps personnel), vous pouvez voir que nous avons un point critique (ou hot spot) de performance de 10 531 millisecondes. La premiĂšre colonne vous montre qu’il s’agit du temps total passĂ© dans la mĂ©thode enterAProgrammaticTrance().

Si vous regardez la valeur entre parenthĂšses, elle montre que 99,8 % du temps de notre programme a Ă©tĂ© consacrĂ© Ă  cette mĂ©thode. La derniĂšre colonne, Invocations, montre qu’elle n’a Ă©tĂ© appelĂ©e qu’une fois.

Comme vous pouvez le voir dans le cas demain(String arg[]), Ă  la deuxiĂšme ligne ci-dessus, le fait de regarder le temps total d’une mĂ©thode peut vous induire en erreur. Il inclut tous les appels de mĂ©thodes amenant jusqu’à un appel du coupable rĂ©el.

main(String arg[])a le temps total le plus lent, car il s’agit de la premiĂšre mĂ©thode appelĂ©e lorsque l’application dĂ©marre. Ce n’est pas la mĂ©thode qui a en fait introduit notre bug de performance, mais celle qui est Ă  l’origine lointaine de son appel !

Et maintenant, qu’est-ce que cette mĂ©thode pourrait ĂȘtre en train de faire ? Souvenez-vous que ce programme a Ă©tĂ© interprĂ©tĂ© Ă  partir de sorts anciens d’alchimistes. En bref, n’attendez pas trop de logique derriĂšre certaines de nos rĂšgles business. Lisons les commentaires et regardons le code.

private void enterAProgrammaticTrance() {
    // Slows down the magic
    Instant before = Instant.now();
    Instant end = before.plus(Duration.ofSeconds(10));
    while (Instant.now().isBefore(end)) {
        for (int j = 0; j < Integer.MAX_VALUE; j++) {
            for (int i = 0; i < Integer.MAX_VALUE; i++) {
                // TODO - implement
            }
        }
    }
}

Vous voyez ce qu’il fait ? Ou plutît, ce qu’il ne fait pas ?

Le code a une boucle while à la ligne 6, qui vérifie constamment si 10 secondes se sont écoulées depuis le calcul de Instant end à la ligne 4. Il y a deux grandes boucles for imbriquées aprÚs cela aux lignes 7 et 8, mais elles ne font rien ! Il y a un commentaire TODO inutile à la ligne 9.

Fondamentalement, cela tourne juste en boucle pendant 10 secondes !

Ce que ce code devrait faire n’est pas clair non plus. En demandant Ă  nos experts au sujet des dragons (SME pour « subject matter experts »), on nous dit qu’ils n’ont pas besoin d’une rĂšgle business comme celle-ci. Nous pouvons retirer ce code sans nuire Ă  l’application. Il vaut mieux obtenir des estimations de selle en moins de 10 secondes que d’entrer dans une transe programmatique. 

Essayez par vous-mĂȘme !

Nous avons besoin d’une solution !

  • Essayez de reproduire nos Ă©tapes ci-dessus. Vous devriez voir le  enterAProgrammaticTrance  dans la vue  Hot Spots.

  • Retirez cette mĂ©thode et son appel.

  • Une fois ceci effectuĂ©, vous pouvez essayer de profiler vous-mĂȘme, comme nous l’avons fait ci-dessus.

  • Lorsque vous vous trouvez dans la vue Hot Spots, vous devriez remarquer que  enterAProgrammaticTrance()  ne se trouve plus dans la partie supĂ©rieure.

Consultez la brancheperformance-bug-fixpour voir la solution :

git checkout performance-bug-fix

Lorsque j’exĂ©cute l’application avec la vue Hot Spots de VisualVM, la mĂ©thode lente a visiblement Ă©tĂ© retirĂ©e et la mĂ©thode principale est la nouvelle mĂ©thode la plus lente avec environ 19 ms.

La méthode principale indique un temps total de 19,5 ms.
VisualVM montrant que le hot spot précédent a été retiré 

En résumé

  • Le profilage vous permet de recueillir et d’examiner des mĂ©triques sur la performance des classes et des mĂ©thodes dans votre programme. 

  • En utilisant VisualVM, vous pouvez profiler vos applications et capturer des vues classĂ©es de hot spots de performance dans votre code.

  • Avec le plug-in Startup Profiler, vous pouvez inspecter des programmes de courte durĂ©e.

Dans le chapitre suivant, nous allons explorer un autre excellent outil : JConsole !

Ever considered an OpenClassrooms diploma?
  • Up to 100% of your training program funded
  • Flexible start date
  • Career-focused projects
  • Individual mentoring
Find the training program and funding option that suits you best