Benchmarking et Optimisations en Java

Optimisations dynamiques

Cette partie traite des optimisations dynamiques effectuées par HotSpot.

Deoptimization

La JVM peut décider, au cours de l'exécution, d'arrêter d'utiliser une méthode compilée, et de retourner à sa version interprétée avant de la recompiler.

Dans quel cas ?


Enseignement : ces deux cas soulignent la nécessité de détecter la compilation JIT dans son benchmark.

On-Stack Replacement

La JVM HotSpot est désormais dôté d'un mécanisme qui permet de compter les itérations d'un bloc de code et de le remplacer par une version compilée au milieu de l'appel.

Voici un exemple qui permet de mieux comprendre cette fonctionnalité :

OSR

  1. L'interpréteur commence à interpréter le contenu de la méthode main.
  2. A 10 000 itérations s'amorce la compilation, mais l'interprétation suit son cours en attendant.
  3. La compilation est terminée, l'interprétation est toujours en cours.
  4. Le compteur atteint 14 000 itérations, arrêt de l'interprétation, OSR intervient !
  5. La méthode main est recompilée pour pouvoir entrer en milieu de boucle.
  6. La version compilée s'exécute.
  7. main() est terminée.

A première vue, cela peut sembler génial mais le bonheur est de courte durée lorsqu'on apprend que le code généré par OSR n'est pas optimal.

En effet, avec OSR, nous sommes garantis de ne pas profiter des optimisations suivantes :


Ainsi, si OSR est utilisé, on ne mesurera pas le temps d'exécution optimal.

Enseignement : il est donc préconisé de déplacer les boucles dans différentes méthodes afin que ces dernières soient intégralement compilées (gain de temps appréciable et visibilité améliorée).

Dead Code Elimination

Cette optimisation consiste à éliminer purement et simplement les morceaux de code inutiles.

C'est d'ailleurs une des seules optimisations qui peut être réalisée en amont par le compilateur javac, mais la JVM suit également une politique très agressive à ce sujet.

Ces éliminations sans concession sont bien sûr sources de résultats eronnés.

Soit le code suivant :

dce

Nous voyons ici que la variable result est utilisée dans l'affichage à la fin du traitement. Si nous l'enlevons, le temps mesuré va considérablement chuter, quitte à devenir insignifiant, preuve que la JVM a suffisamment été intelligente pour supprimer les lignes inutiles.

Enseignement : il faut toujours s'assurer que le résultat d'un traitement soit utilisé pour l'affichage final.