• Aucun résultat trouvé

2.2 La langage Java

2.2.2 Forces du Java

2.2.2.1 Coût de développement

Le développement d'application de grande échelle implique des coûts de développement importants. Les eorts sont répartis dans diérentes tâches comprenant la maintenance, les tests et benchmarks, les améliorations de type refactoring, le débogage, les évolutions fonctionnelles, ou encore le support de nouvelles architectures.

La première force de Java réside dans sa capacité à réduire ces coûts. Une étude statistique recueillant les opinions de développeurs issus des communautés Java, C et C++ a révélé, considérant tous les aspects cités précédemment, des durées de développement en moyenne 50% supérieures en C/C++ par rapport au Java [19]. Ce critère s'est avéré central dans le choix du Java pour l'éditeur Aselta Nanographics.

Les gains de productivité proviennent en premier lieu de mécanismes haut-niveau pris en charge par le langage, en particulier le polymorphisme (cf. Section 2.2.4) mais également la gestion des exceptions, le multi-threading ou les types génériques. Ces gains reposent en grande partie sur l'utilisation d'un environnement d'exécution vers lequel sont déléguées les tâches fastidieuses. Les aspects principaux intervenant dans les gains de productivités sont présentés dans les paragraphes suivants.

Gestion mémoire L'environnement d'exécution prend en charge la gestion mémoire qui est plus précisément assurée par le ramasse-miette ou GC (garbage collector). Par ailleurs Java ne dénit pas de pointeur, ce qui élimine une source importante de bugs.

Portabilité L'environnement d'exécution prend en charge la portabilité. En eet ce der-nier concentre les eorts de portabilité comme il doit être déployé nativement sur toutes les architectures supportées. Les applications Java sont elles compilées dans une représentation machine-indépendante du code : le bytecode. Elle peuvent ainsi être déployées sans eort de portabilité sur toutes les architectures possédant un environnement d'exécution Java. Une application Java peut alors être vue comme un opérande de l'environnement d'exé-cution Java. Cette caractéristique fondamentale du Java est connue sous le sigle WORA (Write Once Run Anywhere).

Dans le cas d'Inscale néanmoins la portabilité est un critère secondaire comme le logiciel est uniquement déployé sur des architectures x86-64. Par ailleurs le logiciel est livré avec le JDK de manière monolithique. Ainsi les architectures supportées par Inscale sont celles supportées par le JDK, à savoir x86, SPARC et ARM, ce qui ne couvre pas tout le spectre des architectures représentées en HPC (cf. Figure 2.1). Notons néanmoins qu'il existe un port de l'OpenJDK pour les plateformes Aix/PowerPC [133]. Par ailleurs l'usage de JNI (Java Native Interface) pour interfacer du code natif réduit également la portabilité.

Écosystème L'autre aspect permettant de réduire considérablement les coûts de déve-loppement concerne la richesse de l'écosystème Java. On entend par écosystème l'ensemble des outils facilitant le développement des applications ainsi que l'ensemble des librairies Java utilisables directement par les applications. Concernant les outils de développement, on peut citer les environnements de développement Eclipse, NetBeans ou IntelliJ IDEA ; les outils de test unitaire JUnit et TestNG ; le logiciel d'intégration continue Jenkins ou encore les moteurs de production Maven et Ant. La plupart sont utilisés dans le dévelop-pement d'Inscale.

L'écosystème Java se compose également d'un nombre important de librairies scientiques. On peut par exemple citer la librairie Apache Commons Math massivement utilisée. Elle couvre de nombreux domaines mathématiques comme les statistiques, l'algèbre linéaire, les éléments nis ou la géométrie.

Par ailleurs JNI permet d'étendre le spectre des librairies utilisables en Java aux librairies natives. Ainsi de nombreuses librairies Java, qualiées de bindings, servent d'interface vers des librairies natives.

Sûreté Les langages traditionnellement utilisés dans le HPC comme le Fortran et le C ont un support limité pour la vérication d'erreur à l'exécution et le debugging qui géné-ralement est long et fastidieux. À l'opposé Java ore lui un support très avancé. L'exemple le plus courant est que lorsqu'une application Java plante, la trace d'appel est enregistrée automatiquement ce qui permet de localiser rapidement la source d'erreur. De plus les spécications du langage renforcent la sûreté avec diérentes opérations de vérication gé-nérées implicitement. Enn, le support avancé et natif des exceptions permet d'améliorer nettement la sûreté des applications.

Par ailleurs, l'environnement d'exécution est en charge d'assurer que la classe chargée vé-rie un certains nombre de propriétés garantissant que le code qu'elle contient peut être exécuté sans risque. Cette tâche est eectuée par un vérieur de bytecode au moment du chargement de classe. Il vérie par exemple la validité du typage ou l'absence de dépasse-ment de pile. La vérication de bytecode renforce la sûreté des applications et évite certains risques de plantage.

2.2.2.2 Performance

Compte tenu de l'évolution des architectures, l'optimisation de performance peut être séparée en trois approches :

1. L'exploitation du parallélisme ; 2. L'exploitation des c÷urs de calcul ;

3. L'exploitation d'accélérateurs matériels en particulier les GPU.

De manière générale, l'utilisation d'un environnement d'exécution favorise les améliorations sur ces trois points considérant les informations résolues dynamiquement exploitables par

les diérents composants du runtime (en particulier le compilateur dynamique).

L'exploitation du parallélisme repose en Java sur un support natif et de nombreuses APIs. L'exploitation des c÷urs de calcul est elle déléguée au compilateur dynamique. Enn dif-férents outils existant permettent de faciliter l'exploitation de la puissance de calcul des GPU.

Parallélisme Java est un langage multi-tâches et fournit à ce titre un support avancé pour la gestion des threads et de la synchronisation. Pour garantir la portabilité des appli-cations concurrentes, Java dénit son propre modèle mémoire appelé JMM (Java Memory Model) [141].

Dans le cas de la JVM HotSpot les threads Java correspondent à des threads natifs. Les latences de synchronisation sont réduites grâce à des techniques comme le biased locking [147,33] où des optimisations du JIT (élimination ou fusion de verrous) [156].

Java 5 a introduit le package java.util.concurrent [99] fournissant de nouveaux mé-canismes de synchronisation de plus haut niveau que les mémé-canismes natifs incluant des ordonnanceurs de tâche, des collections concurrentes, des verrous et des barrières.

Le package a été amélioré dans Java 7 avec l'addition de l'API Fork/Join [30] permettant la mise en ÷uvre du modèle de programmation parallèle éponyme.

Dans Java 8 des améliorations basées sur l'API Fork/Join ont émergé. On peut par exemple citer les méthodes de tri parallèles parallelSort dans la classe java.util.arrays, ou les méthodes du package java.util.streams pour le traitement de données par ux comme les transformations map-reduce.

De manière générale les évolutions pour le support du parallélisme sont l'objet en Java d'une amélioration continue considérant les besoins croissant au niveau des applications [127,130].

Compilation dynamique L'avènement de la compilation dynamique a rendu des lan-gages purement interprété comme Java compétitifs avec des lanlan-gages compilés statiquement comme le C en terme de performance [25,152]. La compilation dynamique, a été introduite dans Java 2 et demeure le composant essentiel pour la réalisation des performances. La compilation dynamique constitue un atout majeur considérant le modèle d'exécution des applications HPC pour deux raisons liées :

1. La prise en compte des informations dynamiques ore un potentiel d'optimisation plus important que la compilation statique ;

2. Le compilateur dynamique se focalisant sur l'optimisation des points chauds appli-catifs, dans le cas des applications HPC, l'échelle d'exécution importante garantit la rentabilité des optimisations appliquées.

Les principes de base de la compilation dynamique ainsi que la politique de compilation dynamique utilisée dans HotSpot sont décrits plus précisément Section 2.3.2

Calcul sur GPU L'exploitation de la puissance de calcul des GPU est devenu essentielle pour les applications HPC. Le projet Sumatra de l'OpenJDK [171] a pour objectif de permettre l'exploitation de cette puissance de calcul au sein des applications Java. Le projet vise dans un premier temps une extension de la JVM HotSpot an qu'elle supporte le calcul sur GPU. Cette extension implique la capacité pour le compilateur dynamique à générer du code pour GPU mais également la capacité pour le GC de gérer la mémoire du GPU. Dans un second la mise en ÷uvre au niveau des APIs Java sera évaluée.

Un tel support existe déjà dans la version Java 8 du JDK IBM et est décrit dans [81]. Le compilateur dynamique de la machine virtuelle J9 peut aecter certaines tâches à un GPU en se basant sur des heuristiques. Les sections de code impactées sont les boucles du type parallel().forEach.

En dehors de la gestion automatisée par le runtime, les outils Rootbeer [139] et Aparapi [6] permettent d'exploiter le calcul sur GPU. Les deux fournissent une API Java ainsi qu'un compilateur statique permettant de générer des noyaux de calcul GPU à partir du code Java. Dans [35], Docampo et Al. propose un étude comparative des diérentes API Java existantes pour le calcul sur GPU en considérant à la fois la facilité de programmation et les performances.