• Aucun résultat trouvé

2.3 La JVM HotSpot

2.3.2 Compilation dynamique

2.3.2.2 Techniques utilisées

3 4 5 6 Accéleration Criticité du code [nombre d'occurence] 10 100 1000 10000

Fig. 2.5: Accélérations provenant de diérents niveaux d'optimisation par rapport à l'inter-préteur en fonction de la criticité du code compilé. Le niveau d'optimisation est déni par le couple (coût de compilation, accélération). Le coût de compilation correspond au facteur multiplicatif séparant la durée de compilation du code et l'exécution d'une occurrence du code au niveau interpréteur. L'accélération désigne l'accélération provenant de l'exécution du code natif par rapport à l'interprétation. En particulier le niveau interpréteur à un coût de compilation de 0 et un facteur d'accélération de 1 ce qui s'écrit (x0, x1). Comme on peut l'observer chaque niveau d'exécution est optimal pour une criticité donnée (par exemple le niveau 1 est optimal pour 100 occurrences). Le second point observable est que plus le code est critique plus le niveau d'optimisation peut avoir un impact signicatif sur les performances

cution28

2.3.2.2 Techniques utilisées

Détection des points chauds La politique de compilation dynamique exploite le prin-cipe des 80/20 qui énonce que 20% du code (les points chauds) représentent 80% du temps d'exécution. La stratégie la plus simple est alors de détecter ces points chauds an de leurs aecter le niveau d'optimisation le plus élevé.

Cette détection ne doit pas impacter négativement l'exécution du code. Ainsi les techniques sont conçue pour avoir une faible empreinte sur le temps d'exécution.

La détection peut être déléguée aux niveaux d'exécution les plus bas (typiquement l'inter-préteur) qui sont alors en charge d'incrémenter des compteurs d'occurrence sur l'exécution

28Cette dernière peut être mise en ÷uvre dans HotSpot avec les options -XX:-UseTieredCompilation et -XX:CompileThreshold=1

de sections de code [100,149]. C'est par exemple la technique utilisée dans les JVM HotS-pot et J9.

Une autre technique de détection utilisée est l'échantillonnage (sampling) [192]. Cette tech-nique est similaire à l'échantillonnage utilisée par les proleurs à savoir que les piles d'ap-pel des diérents threads sont inspectées à intervalle de temps régulier par un composant indépendant puis des compteurs d'occurrence sont incrémentés. Cette technique est par exemple utilisée dans les environnements d'exécution Jikes RVM et JRockit. Des tech-niques de sampling utilisant en plus des compteurs matériels ont également été explorées [21].

Compilation multi-niveaux Les techniques de compilation multi-niveaux permettent d'améliorer l'ecacité globale de la compilation dynamique en associant un niveau de com-pilation à la température du point chaud (sa contribution à la durée d'exécution globale) [78, 82]. L'idée est d'associer un faible niveau d'optimisation à un point chaud de faible température et un haut niveau d'optimisation à un point chaud de forte température, sa-chant que plus le niveau d'optimisation est faible, plus la latence de mise en ÷uvre est également faible.

Les techniques de compilation multi-niveaux utilisent un modèle évolutif : l'état d'exécu-tion d'un bloc de code évolue en foncd'exécu-tion de sa température. Ainsi un bloc de température maximale passera potentiellement par tous les états d'exécution intermédiaires avant d'at-teindre l'état d'exécution optimal.

En considérant la loi des 80/20, la compilation multi-niveaux permet de réduire la latence précédent l'exécution des points chauds au niveau d'exécution optimal (comme avant d'at-teindre ce niveau le point chaud passe par des états intermédiaires plus rapide que l'in-terpréteur). Ainsi cette technique est souvent caractérisée comme améliorant le temps de démarrage des applications (composé du warmup mais également d'autres opérations comme le chargement de classe) mais peut également permettre d'améliorer les perfor-mances asymptotiques.

La compilation multi-niveaux est mise en ÷uvre dans HotSpot avec le mode étagé com-binant deux compilateurs et possédant 5 niveaux d'exécution (cf. Section 2.3.2.3). Elle est mise en ÷uvre dans J9 qui possède 6 niveaux d'exécution qui sont interpreter, cold, warm, hot, proling et scorching [162]. Elle est également mise en ÷uvre dans Jikes RVM qui possède 3 niveaux d'exécution correspondant à 3 degrés d'optimisation du compilateur dynamique (-O0, -O1, -O2).

Unité de compilation et OSR L'unité de compilation désigne la nature du bloc de code transmis au compilateur dynamique pour être compilé. Le compromis posé par le choix des unités de compilation repose sur leur granularité. Plus le bloc de compilation est de granularité élevée, plus les possibilités d'optimisation (du fait de la connaissance du contexte) sont nombreuses. Cependant, plus la granularité est élevée plus la latence de

compilation est élevée et moins la détection des points chauds est précise.

On distingue deux familles de compilateur dynamique. Ceux dont les unités de compila-tion sont des méthodes (method-based JIT ) et ceux dont les unités de compilacompila-tion sont des traces (trace-based JIT ).

Les traces [11, 67] sont des structures contenant les chemins d'exécution empruntés avec les fréquences associées à chaque bloc. Les traces permettent d'étendre ou de réduire le scope de compilation par rapport à une méthode ce qui permet d'augmenter les possibi-lités d'optimisations ou de cibler plus précisément les points chauds (en particulier dans le cas où les points chauds sont des boucles contenues dans une méthode). Ces techniques occasionnent cependant un coût de compilation plus élevé provenant principalement de la collecte des traces [77,69].

La compilation basée sur les méthodes facilite largement la détection des points chauds et ore également assez de possibilités d'optimisation (de surcroît grâce à une politique d'inlining agressive). Elle est ainsi privilégiée dans les JVM de production comme HotSpot et J9.

Un des problèmes de la compilation basée sur les méthodes survient lorsque le code critique est contenu exclusivement dans une méthode en cours d'exécution (i.e. dans une boucle d'itération). Dans ce cas la compilation de la méthode peut être déclenchée, cependant l'exécution de la méthode se poursuit dans le mode d'exécution courant à savoir l'inter-préteur. Pour contourner ce problème, la technique de l'OSR (On-Stack Replacement) [44,159], implémentée dans HotSpot, permet à une méthode en cours d'exécution de bas-culer du mode interpréteur vers un mode compilé lorsque le runtime détecte une boucle critique. Cette transition se fait en compilant la méthode à partir d'un point d'exécution précis (un safepoint) et en transformant la pile interpréteur en pile d'exécution native. L'OSR est également utilisée dans J9 mais est appelée DLT (Dynamic Loop Transfer) [97]. Optimisations optimistes et désoptimisation L'avantage majeur de la compilation dynamique repose sur les possibilités d'optimisation permises par l'utilisation d'information dynamique. Les informations sont collectées au cours de l'exécution (on parle de proling) puis sont utilisées par le compilateur dynamique. Les optimisations basées sur les informa-tions de proling sont qualiées d'optimisainforma-tions par prole guidé ou PGO (Prole Guided Optimisation). La collecte des informations peut être, tout comme la détection des points chauds, déléguée aux niveaux d'exécution les plus faibles. L'étendue des informations pro-lées est cependant limitée entre autre par le coût occasionné par les opérations de collecte [94].

Le proling permet d'appliquer des optimisations dites "optimistes". Une optimisation op-timiste est une optimisation dont la validité repose sur l'hypothèse opop-timiste que, si une condition est vériée lors de la phase de proling, elle sera vrai pour toute l'exécution de l'application.

natif de basculer vers le mode interpréteur si la condition de validité d'une optimisation n'est pas vériée pendant l'exécution. La désoptimisation traduit une pile d'exécution na-tive en pile interpréteur, elle peut ainsi être vue comme l'opération inverse de l'OSR qui traduit une pile interpréteur en pile native. La désoptimisation peut avoir diérents eets sur la méthode native. Cette dernière peut être invalidée ou non (l'invalidation signiant que la méthode n'est plus exécutée) et/ou recompilée ou non.