• Aucun résultat trouvé

3.3 Implémentation d'un micro-benchmark

3.3.2 Prise en compte des optimisations du JIT

3.3.2.2 Spécialisation du code

Propagation de constante Lorsque les entrées sont des constantes de compilation, le JIT applique de la propagation de constante consistant à remplacer une expression constante par sa valeur et à spécialiser le code pour cette valeur de qui meut mener à de l'élimination de code mort si le code contient des branchements conditionnels. Pour éviter la propagation de constante, les paramètres d'entrée du code testé sont systématiquement passés en paramètres de compute comme montré Figure 3.3.

Spécialisation du code aux données prolées Les optimisations provenant du pro-ling sont à prendre en considération car elles permettent de spécialiser le code au prol d'exécution.

Le proling permet au JIT de faire des optimisations optimistes. Ces optimisations sup-posent que les informations collectées avant compilation seront valides pour le reste de l'exécution. Le JIT génère des branches de désoptimisation (aussi qualiées de uncommon traps) qui basculent en mode interpréteur pour poursuivre l'exécution des chemins invali-dant les hypothèses de compilation.

static double CONSUME;

static long compute (double[] a){

double consume = 0;

long t0 = System . nanoTime ();

for(int i = 0; i < IT; ++i){

consume += sum (a); // inlining de sum

}

long t1 = System . nanoTime ();

CONSUME = consume ;

return (t1 - t0 ); }

Version de compute avec inlining de sum activé

static long compute (double[] a){

long t0 = System . nanoTime ();

for(int i = 0; i < IT; ++i){

sum (a); // sum n'est pas inlinee

}

long t1 = System . nanoTime ();

return (t1 - t0 ); }

Version de compute avec inlining de sum desactivé

static double sum (double[] a){

return a [0]+ a [1]; }

Méthode sum dont le contenu est testé

Fig. 3.2: Méthode compute avec boucle d'itération mesurant la performance du code contenu dans sum. La boucle d'itération est necessaire pour garantir une bonne justesse de mesure étant donnée la faible granularité de sum. Dans la version avec inlining, la va-leur retournée est consommée par compute en écrivant le champ statique CONSUME, ce qui garantit que le corps de la méthode ne sera pas éliminé après inlining mais détériore la justesse de la mesure. La seconde version suppose que sum n'est pas inlinée. Dans ce cas le code n'est pas éliminé mais le coût d'appel de la méthode détériore également la justesse de la mesure

Table 4.3 Section 3.4), la méthode est invalidée et bascule au niveau d'exécution 0 (inter-préteur). Plus précisément, lors de l'invalidation, l'entrée de la méthode est écrasée par un saut vers une routine runtime chargée de modier la cible de l'appel. Le code de la méthode peut être eacé du code-cache seulement lorsque la méthode n'est plus active c'est-à-dire que tous les sites d'appel la ciblant ont été mis à jour, la méthode est alors qualiée de zombie. La visualisation des logs de compilation permet de s'assurer que le code n'est pas invalidée au cours de l'exécution6.

Pour garantir la consistance des mesures, les données d'entrée doivent être invariantes par instance de micro-benchmark et le résultat nal associé au jeux de données correspondant.

6Dans les logs de compilation du JIT, l'information made not entrant annonce qu'une méthode com-pilée est invalidée, l'information made zombie annonce qu'une méthode n'est plus active et peut être eacée

static double CONSUME;

static long compute (){

double a = 4.;

long t0 = System . nanoTime ();

CONSUME = Math . exp (a);

long t1 = System . nanoTime ();

return (t1 - t0 ); }

Version de compute avec avec propagation de constante

static long compute (double a){

long t0 = System . nanoTime ();

CONSUME = Math . exp (a);

long t1 = System . nanoTime ();

return (t1 - t0 ); }

Version de compute sans propagation de constante

Fig. 3.3: Méthode compute mesurant la performance du code contenu dans la méthode Math.Exp. Notons que la méthode compute ne possède pas de boucle d'itération comme la granularité de Math.Exp est assez grande pour obtenir une bonne justesse dans les mesures. Dans les deux versions la méthode Math.Exp est inlinée. Dans la première version, il y a propagation de constante. L'expression Math.Exp(a) est par conséquent évaluée à la compilation et, durant l'exécution, seule l'aectation de la variable CONSUME (servant à éviter l'élimination de code mort) est mesurée ce qui produit des résultats de performance anormalement élevées (d'un facteur×4dans ce cas précis). Ainsi pour éviter la propagation de constante, les paramètres du code testé sont systématiquement passés en paramètre de compute comme montré dans la seconde version de compute

Deux types de proling aectant l'état du code généré par le JIT sont eectués au runtime : Les compteurs de branchement ;

Le proling de type.

Les compteurs de branchements sont collectés au cours de l'exécution an de mesurer les fréquences d'exécution des blocs de base. Cette information permet ensuite au JIT de réor-ganiser les blocs de base an de minimiser le nombre de branchements générés, d'améliorer la localité du code ou encore d'ordonner les branchements pour éviter les tests inutiles. Ces informations sont également utilisées an d'optimiser le code des intrinsics du compilateur dynamique.

Le proling de type permet de recueillir de l'information sur l'opérande de certains byte-codes. Les emplacements dans le code des diérents bytecodes prolés sont appelés points de proling. L'information recueillie aux points de proling est utilisée par le JIT pour générer un code spécialisé, et donc plus rapide, pour le bytecode en question. Les byte-codes bénéciant du proling de type sont listés Table 3.3. En mode non-étagé, le proling de type est eectué uniquement au niveau 0 d'exécution (interpréteur) ; en mode étagé le niveau d'exécution 3 eectue également du proling (cf. Table 2.3).

L'alternative utilisée pour empêcher la spécialisation du code aux proling de type est de le déactiver depuis les paramètres de la JVM (cf. Section 3.4). On obtient ainsi un code compilé générique. Ce code compilé générique correspond à celui généré lorsque le pro-ling de type ne dégage pas d'information permettant de faire des optimisations. Un autre moyen est de sélectionner un jeu de donné qui ne permette pas la spécialisation par son caractère aléatoire.

Bytecode Pile d'opé-randes

Opérande-objet dont le type est

prolé Bref description

invokevirtual invokeinterface argN ... arg2 arg1 rcv

rcv Appel la méthode encodée associée autype du receveur rcv avec les argu-ments arg1, arg2..., argN.

checkcast

instanceof obj obj

Permet de tester l'égalité entre le type de l'objet obj et le type constant en-codé.

aastore objindex

tab obj

Insère l'objet obj dans le tableau tab à l'index index en vériant que le type de obj correspond au type des éléments de tab.

Tab. 3.3: Les diérents bytecodes bénéciant du proling dans HotSpot. En un point d'exécution de ces bytecodes, le code généré dépendra de la distribution des types prolée pour l'opérande-objet