• Aucun résultat trouvé

Résultats et observations

6.3 Résultats de performance

6.3.2 Résultats et observations

La Figure 6.11 montre les performances obtenues pour les diérentes implémentations de la routine floorD2I (dénie Section 6.2.1). Comme rappelé Section 6.1.2, les compa-raisons de performance avec les méthodes Java compilées par le JIT doivent prendre en compte la spécialisation du code aux données prolées. Sans cette précaution, le gain de performance obtenu avec la version intrinsiée peut être dépendant du jeu de données uti-lisé lors des tests et inexistant dans les cas d'exécution réels.

Pour la méthode floorD2I, le code généré par C2 est générique dans le cas d'un jeu de données aléatoire en entrée et spécialisé dans les autres cas. Le gain de performance pro-venant de la spécialisation du code est cependant nul, les versions spécialisées empruntant

floorD2I lcps2d

static int CONSUME;

static long

compute (double[] data ){

int acc = 0;

int n = data .length;

long t0 = System . nanoTime ();

for(int i =0;i<n ;++ i)

acc += floorD2I ( data [i ]);

long t1 = System . nanoTime ();

long dt = (t1 -t0 );

CONSUME = acc ;

return dt; }

static int CONSUME;

static long

compute (double[] data ){

int acc = 0;

int n = data .length/4;

long t0 = System . nanoTime ();

for(int i =0;i<n ;++ i) acc += lcps2d ( data [4* i],

data [4* i+1] , data [4* i+2] , data [4* i +3]);

long t1 = System . nanoTime ();

long dt = (t1 -t0 );

CONSUME = acc ;

return dt; }

Tab. 6.6: Méthodes compute mesurant les performances des méthodes floorD2I et lcps2d. La performance mesurée vaut n/dt. Concernant l'implémentation, des accumulateurs (acc) servent à prévenir l'élimination de code mort et la boucle d'itération est nécessaire pour ga-rantir la justesse de la mesure (cf. Chap. 3). Les performances sont mesurées dans diérents cas où la méthode appelée est (i) en Java pure, (ii) native ou (iii) intrinsiée

les mêmes chemins d'exécution que la version générique comme vu Section 6.2.1.1. La performance de la version Java, qui contient des branchements conditionnels, dépend du jeu de données d'entrée contrairement aux performances des autres versions (Intrinsic sous-routine, Intrinsic inliné et JNI ) basées sur du code natif optimisé sans branchements conditionnels.

Pour les versions Java spécialisées, ces diérences de performance s'expliquent par les diérents chemins d'exécution empruntés (montrés Table 6.4) possédant des longueurs dif-férentes. Pour le jeu de données aléatoire il faut ajouter la baisse de performance provenant de la prédiction de branchement.

On observe ainsi un facteur d'accélération de ×1,5 entre l'implémentation Java inliné et l'implémentation Intrinsic inliné pour des doubles positifs en entrée contre un facteur d'en-viron×2,8 pour les autres jeux de données.

La Figure 6.12 montre les performances obtenues pour les diérentes implémentations de la routine lcps2d (dénie Section 6.2.2). Pour cette méthode, les performances obtenues sont indépendantes du jeu de données et on observe un facteur d'accélération d'environ ×16entre la version Java inliné et la version Intrinsic inliné.

Impact de la granularité Comme rappelé en introduction du chapitre, la granularité des méthodes est une motivation essentielle pour l'implémentation des intrinsics considé-rant le coût d'intégration élevé via JNI. Les résultats obtenus valident ces assertions. On

Aléatoire Doubles

positifs DoublesFSN DoublesESN

Données d'entrée

0.0

0.2

0.4

0.6

0.8

1.0

1.2

1.4

1.6

1.8

Performance [Gop/s]

floorD2I

Java inliné (version générique) Java inliné (versions spécifiques) Intrinsic inliné Intrinsic sous-routine JNI

Fig. 6.11: Performance des diérentes implémentations de la méthode floorD2I pour diérents jeux de données en entrée. La performance est mesurée comme un débit d'opéra-tions (Op/s) où une opération correspond à une exécution de la méthode. Concernant les données d'entrée, FSN (resp. ESN) désigne les doubles strictement négatifs avec partie frac-tionnaire non nulle (resp. nulle). Les versions spéciques, générées par C2 pour les données d'entrée spéciques non aléatoires, n'entraînent pas de gain de performance. Pour ces jeux de données, la version générique est obtenue via l'option -XX:-BlockLayoutByFrequency. Le facteur d'accélération moyen obtenu par l'implémentation Intrinsic inliné contre l'im-plémentation Java inliné est de×2,5

peut en eet observé une diérence de performance importante entre les implémentations JNI et Intrinsic sous-routine générant toutes les deux un appel vers la sous routine conte-nant le code natif optimisé (cf. Section 6.1.3.2). Cette diérence de performance est d'un facteur ×5 pour la routine floorD2I et d'un facteur ×3 pour la routine lcps2D. Elle est plus importante pour la routine floorD2I qui possède une granularité plus faible.

Comme vu Section 6.1.3, deux techniques d'implémentation des intrinsics sont proposées donnant lieu aux versions Intrinsic sous-routine et Intrinsic inliné.

Concernant la méthode floorD2I, dont la durée d'exécution unitaire (estimée avec l'in-verse du débit en Op/s) est inférieure à la nanoseconde, la version Intrinsic sous-routine eectuant un appel de sous-routine demeure trop coûteuse pour observer un gain de perfor-mance signicatif. Les perforperfor-mances sont similaires à celles obtenues avec la version Java inliné (i.e. environ 0,6 Gop/s).

0.00

0.05

0.10

0.15

0.20

0.25

0.30

0.35

0.40

Performance [Gop/s]

0.023 0.385 0.338 0.112

lcps2d

Java inliné Intrinsic inliné Intrinsic sous-routine JNI

Fig. 6.12: Performance des diérentes implémentations de la méthode lcps2d (cf. Section 6.2.2). La performance est mesurée comme un débit d'opérations (Op/s) où une opération correspond à une exécution de la méthode. Le facteur d'accélération obtenu par l'implé-mentation Intrinsic inliné contre l'implél'implé-mentation Java inliné est d'environ×16,7 Concernant la méthode lcps2d, dont la durée d'exécution unitaire avoisine les 10 nano-secondes, la version Intrinsic sous-routine permet également d'observer un gain de per-formances signicatif, même si sa performance est d'environ 12% inférieure à celle de la version Intrinsic inliné.

6.4 Conclusion

Ce chapitre expose les expérimentations d'ajout d'intrinsic eectuées au sein du compi-lateur C2 de la JVM HotSpot. Ces intrinsics concernent des méthodes de faible granularité pour lesquelles le code généré par C2 sur l'architecture Intel64 a été établi comme sous optimal en analysant l'état du code généré et en exposant des optimisations utilisant des instructions spéciques.

Les résultats de performance, obtenus par micro-benchmark, valident l'inecacité de l'uti-lisation de JNI pour des méthodes de faible granularité. Ils ont montré par ailleurs des accélérations signicatives, mesurées à l'aide de micro-benchmark, provenant de l'implé-mentation des intrinsics comparativement au code généré par C2 (de×2 à×16).

La méthodologie utilisée pour implémenter les intrinsics a été exposée. Elle donne des élé-ments de réponse sur la nature des méthodes à intrinsier et quelques détails techniques sur leur implémentation.

Deux méthodes d'implémentation sont décrites. L'une, similaire à JNI mais réduisant lar-gement le coût d'invocation, permet une implémentation simple des intrinsics mais néan-moins sous-optimale. L'autre, plus élaborée, permet d'inliner le code natif optimisé dans le contexte d'appel et de bénécier d'optimisation locale. Elle est à privilégier pour des méthodes de très faible granularité ou ayant un fort potentiel d'optimisation à l'inlining. Cette étude montre que l'implémentation d'instrinsics côté utilisateur est un moyen e-cace, moyennant une bonne connaissance de la JVM HotSpot, d'améliorer les performances de code applicatif critique, particulièrement dans un contexte HPC où la performance est une contrainte majeure.

La prise en compte des informations prolées et du contexte d'exécution dans la génération du code des intrinsics permettrait de générer un code encore plus performant pour certaines méthodes. Par ailleurs, le code généré par le JIT pourrait également être amélioré sans re-court aux intrinsics, mais avec une amélioration du back-end de C2 an qu'il puisse générer les instructions spéciques plus adaptées. Néanmoins même si des opportunités d'optimi-sation sont identiées coté utilisateur du runtime, ce type d'implémentation doit se faire côté développeur du runtime comme il nécessite des compétences métier plus approfondies. De ce point de vue, le compilateur C2 possède encore un fort potentiel d'amélioration.

Chapitre 7

État de l'art : une analyse

comparative

Ce chapitre propose un état de l'art focalisé essentiellement sur les techniques d'opti-misation de code applicatif Java pour architectures généralistes du type x86-64.

On regarde en particulier les techniques pouvant être mises en ÷uvre côté utilisateurs du langage Java et de la JVM (user-end). On ignore les techniques hardwares i.e. l'utilisation d'architectures matérielles permettant d'exécuter du bytecode nativement [49, 150]. Dans [87], Kazi et Al. ont montré un panorama global des diérentes techniques d'exécution de programme Java avec la performance comme critère central de comparaison.

Les diérentes techniques sont comparées en regardant quatre critères majeurs liés au développement d'application d'application déployée en production :

1. Prix/licence. Il s'agit d'un critère industriel qui touche à la rentabilité ou à la propriété intellectuelle. Il peut être bloquant concernant l'utilisation de JVM pro-priétaires haute-performances comme la JVM Zing [5] ou l'utilisation de librairies sous licence contraignante [158] comme la licence GPL ;

2. Pérennité/robustesse. Il s'agit d'un critère central qui garantit que la solution employée est robuste et pérenne. Ce critère regarde par exemple si la solution est toujours maintenue et distingue les projets de recherche, pouvant être de nature expérimentale, des solutions intégrées en production ;

3. Diculté d'intégration. Ce critère prend en considération la compatibilité de la solution avec le code existant et les diverses dicultés techniques d'intégration pou-vant survenir ;

4. Ecacité. Ce critère regarde l'impact sur les performances de la solution employée. Le critère prend notamment en compte le caractère local ou global du gain i.e. si ce dernier impacte une section de code en particulier ou bien toute l'application. Il peut être relatif i.e. dépendre d'un certain contexte d'exécution ou non.

Les techniques d'optimisations statiques de code applicatif discutées Section 7.1 ; Les techniques d'optimisations dynamiques du code applicatif discutées Section 7.2.

7.1 Optimisations statiques de code applicatif

Les optimisations statiques sont, comme leur nom l'indique, les optimisations apportées à l'application mises en ÷uvre statiquement. Elles sont divisées en plusieurs catégories :

Les optimisations de code Java par une approche dite JIT-Friendly, discutées Section