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 :
Mesurant votre code pour déterminer quelle action le ralentit et où cela se produit dans le code.
Enquêtant sur le code non performant et questionnant ce qu’il essaie d’accomplir et pourquoi.
Supprimant ou simplifiant le code si cela peut être fait immédiatement.
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 à :
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.
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.
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.
Nous avons sélectionné un port de 5130, qui aide notre JVM à envoyer des données à VisualVM.
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.
VisualVM nous a alors donné quelques commandes à copier en utilisant le bouton Copy to Clipboard (Copier vers le presse-papiers).
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).
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.
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 :
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 vueHot 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-fix
pour 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.
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 !