[Devoxx FR 2012] – Réflexions à contre-courant sur les performances en Java

“Generally the one who first occupies the battlefield awaiting the enemy is at ease; the one who comes later and rushes into battle is fatigued. Therefore those skilled in warfare move the enemy, and are not moved by the enemy.

Sun Tzu

Une citation de Sun Tzu et de l’art de la guerre qui reflète fidèlement ce que j’ai aussi appris en jouant au jeu de go ou aux échecs. On ne gagne pas sur ses points faibles. Les points faibles sont faits pour tromper l’ennemi, et pour être sacrifiés lors d’une défaite volontaire sur l’autel de la victoire globale. Quel rapport avec Java ? Quel rapport avec Devoxx ? C’est assez simple, j’ai assisté aujourd’hui à plusieurs présentations, dont deux particulièrement m’ont dérangé. Elles étaient de qualité et je peux les citer.

La première, de 3h était présentée par José Paumard, professeur à Paris 13 (j’y ai fait mon DEUG 🙂 ), elle était passionnante, et comme elle a été enregistrée et qu’elle sera diffusée en ligne je vous la conseille de tout coeur. Intitulée “De Runnable et synchronized à parrallel() et atomically()”, elle abordait la problématique du multithreading. Très riche et appuyée par des exemples frappants, elle avait pour but de retracer l’historique du multithreading, depuis le besoin jusqu’aux solutions existantes, puis de prolonger ce sujet jusqu’aux prochaines implémentations attendues dans la JVM. Notre orateur nous a fait faire une descente jusqu’aux couches profondes du système (processeurs, caches L1, L2 et L3 sur l’architecture Nehalem, il fallait le faire, et il fallait le rendre limpide, ce qui a été rondement mené !).

La seconde, intitulée “Réduire la pression sur l’allocation mémoire : le prochain pas dans l’optimisation des performances de la JVM”, d’une durée de 30 minutes, nous a été présentée conjointement par deux membre du projet DirectMemory, une implémentation de cache multicouche, on-heap et off-heap. C’est ce second point qui est crucial dans cette optimisation puisqu’ils utilisent les ressources bas niveau de l’OS via l’architecture standard Java, en utilisant la mémoire du processus pour gérer un cache qui n’a du coup plus besoin de subir un garbage collecting qui peut s’avérer pénalisant dans des gros caches. Olivier Lamy et Benoît Perroud nous on donc fait une démonstration en avant première de ce projet qui, si vous voulez en savoir plus, est dans l’incubateur Apache.

Comme vous le voyez, le point commun entre ces deux présentations c’est l’amélioration des performances de nos programmes Java. Dans un cas on parle des optimisations liées au processeur, dans l’autre cas on parle d’optimisations liées à la mémoire. Dans les deux cas, et c’est le but de ce billet modestement à contre-courant comme vous allez le voir, ces présentations se sont appuyées sur des aspects dépendant de l’OS. Et là, j’ai quelque chose qui me gratte… Qui me démange… Je repense à cette sacro-sainte devise : “écrire une fois, exécuter partout”. Et je me demande si on ne s’éloigne pas un peu de ce qui fait la nature de Java en introduisant ces éléments d’optimisation. Est-ce qu’on ne s’engage pas dans une bataille pour défendre un point faible, plutôt que de l’abandonner définitivement pour axer le langage Java sur son vrai point fort qu’est la portabilité ? Quand en plus j’ai appris en soirée qu’on introduit l’héritage multiple et des corps de méthodes dans les interfaces dans le JDK 8 pour les expressions lambda, je trouve que ça fait beaucoup pour mon petit coeur d’ancien développeur C++. Pourtant on n’est pas le 1er avril.

Alors oui, je le répète (je l’avais déjà écrit plus haut mais ça mérite d’être redit), DirectMemory est implémenté entièrement sur une base Java standard. Il n’y a pas d’appels système, ou de JNA. Que du travail sur les ByteArray via des API que vous retrouverez dans votre SDK favori. Un peu comme les locks dans le cas du… multithreading. Je ne vous le fais pas dire, les sujets sont définitivement liés, on parle ici d’exposition par la JVM des couches bas niveau. Je reste perplexe même si je comprends le but. Je teinte donc ce billet d’une dose de doute. Une dose seulement, parce que si la valeur n’attend pas le nombre des années, je ne vais pas me prétendre expert de la JVM à ce point là.

Il demeure quand même le sentiment que ces optimisations devraient être un dernier recours. Une petite sucrerie pour le dessert une fois l’entrée (un bon modèle), et le plat principal (de bons algorithmes) achevés. J’aimerais donc profiter de ce blog pour tempérer les ardeurs de ceux qui ont un cerveau comme le mien : jeune, avide de nouveautés, avide de découvertes, au point d’avoir les pieds qui quittent un peu le plancher des vaches. Du calme. On optimise. On joue la carte des hacks de bas niveau. On fait des choses très sexy intellectuellement parlant, Devoxx oblige, mais lundi nous retournons dans nos missions. A titre personnel ça me fait retourner sur du Java 4 c’est pour dire ! D’où ce besoin de prendre un peu de recul, de garder tout ça au chaud dans un coin de la tête, d’y repenser en bloguant ou en codant dans le cadre d’une veille technologique, mais pas de foncer tête baissée. José Paumard le précise d’ailleurs très bien dans ses derniers slides : en dix ans on a gagné en optimisation linéaire un facteur 10 000 de performances grâce aux processeurs, et un facteur 43 000 grâce aux algorithmes… Ca ne fait pas tout à fait 80/20, mais un c’est un beau cas d’école quand même.

 

Nombre de vue : 22

COMMENTAIRES 13 commentaires

  1. Sofian Djamaa dit :

    J’aimerais nuancer votre opinion.

    Bien que beaucoup d’applications n’ont pas besoin de performances allant à la microseconde il existe néanmoins des besoins de temps reel. Les évolutions autour de Java permettent d’atteindre ce but : JVM sans pause pour une meilleure prédictabilité des algorithmes et parallélisme notamment. On peut arriver à un point où la JVM n’est plus un goulot d’étranglement (cf. architecture LMAX). Les goulots deviennent : scheduling des threads OS, interruptions matérielles (IRQ), lourdeur de la couche de transport, cache miss au niveau du processeur… Donc : optimisation de la gestion du cache processeur, optimisation de l’OS.

    Pourquoi rester en Java alors ? Pour des besoins de ressources (Java est un langage populaire), pour reprendre des existants et parce que l’ecosysteme Java est large.

    Write once run everywhere ne marche pas dans ce cas de figure. Je ne veux pas être portable, juste être puissant.

    Je suis d’accord qu’il faut chercher avant tout des optimisations de code et d’algorithme basique. Cependant les optimisations matérielles et OS pourraient (conditionnel) devenir nécessaire. Je suis content de voir ces présentations à Devoxx ! C’est un signe qui montre qu’on peut faire de la performance en Java !

  2. Noël dit :

    Je comprends ce point de vue. Je dis juste que ce n’est pas parce qu’on “peut” le faire qu’on “doit” le faire. Java peut gérer le multithreading correctement, et le garbage collecting, en ayant des dépendances beaucoup plus faibles à l’OS.

    ujourd’hui on ajoute des dépendances comportementales à Java. Certes on ne change pas le langage, mais on rend la JVM plus complexe (donc potentiellement plus buggée), et on fait grossir l’API en orientant certaines classes vers la connaissance des subtilités de l’OS.

    Par conséquent le choix des algorithmes par un développeur Java ne va plus simplement dépendre de la complexité de l’algorithme (vision mathématique), mais aussi de sa connaissance des mécanismes sous-jacents. Et comme avec les nouvelles classes ou les nouvelles JVM l’algorithme optimum sur un processeur ne sera plus forcément le même sur un autre processeur, ça peut encourager une mauvaise pratique qui est de piocher des choses toutes faites sur Internet, et de les appliquer à tort dans un cas qui ne s’y prête pas.

    En conclusion, puisqu’on peut faire des choses qui fonctionnent très bien avec des outils indépendants de l’OS. Comme tout simplement avec synchronized, ou en choisissant de bons paramètres de durée de vie en cache plutôt que de chercher à ne plus avoir le cache dans la heap. Si on “doit” aller plus loin sur un projet, je me demande à quel point ce n’est plus de Java dont on a besoin, mais d’un langage plus proche de la machine. Le langage Go de Google est peut-être l’avenir dans ce domaine ? Sinon il y a encore C et C++ par exemple.

  3. Nicolas dit :

    A chaque problématique ses outils? C’est du bon sens non ?

  4. Sofian Djamaa dit :

    “doit” : oui il y a des cas extrêmes de latence (en finance notamment).

    Si vous relisez mon commentaire je suis aussi dans l’optique “à chaque problématique son outil”. Je ne suis pas le seul à penser visiblement puisque l’architecture LMAX est en Java

    De plus, cette utopie du langage plus proche de la machine n’est pas toujours vraie : sur un concours de gestion de requetes en simultané, les 2 premiers sont en java (NIO) et le 3e en C.

  5. Jean dit :

    Personnellement, je distinguerais les optimisations de la JVM des changements d’API.

    La JVM étant par définition l’outil qui garantit la portabilité, je n’ai pas de problème avec une optimisation très fine à ce niveau. C’est même souhaitable, précisément pour combler la lenteur relative de Java. Le fait que le code soit plus complexe est inévitable. Plus buggé? Pourquoi? Oracle a des standards de qualité suffisamment élevés pour qu’on puisse leur faire confiance. Quant à comprendre l’implémentation de la JVM, c’est un niveau de détail qui semble trop pointu pour la grande majorité des développeurs. A mon avis (pas taper).

    Concernant le changement d’API, en revanche, je suis tout à fait d’accord.

    Enfin, pour ce qui est de l’utilisation d’autres langages plus proches de la machine (c++ et son delete pour la mémoire), comment les mettre en oeuvre sur un projet Java qui a 10 ans d’ancienneté et pas une ligne de C++? Comment gérer le portage du code à optimiser?

    Jouer sur l’algorithmique semble donc l’alternative la plus probable, voire la seule possible. Mais alors, on peut reprocher à Java de ne pas laisser définir des structures très bas niveau, comme le C++ peut le faire. Je veux dire par là qu’on est entièrement dépendant des implémentations de List, SortedSet, Map ou SortedMap.

  6. Sofian Djamaa dit :

    (désolé c’est pas evident sous mobile !)

    … C est plein de memory leaks.

    SI je veux faire de la performance en Java je ne m’en prive pas : ça gère la mémoire à ma place (on a vu de l’optimisation sur de la concurrence et du parallélisme, pas de la libération de mémoire), on est sur de l’orienté objet (pas en C/C++) donc on peut s’aider de patterns rendant le code réutilisable. J’ajoute à cela les points de mon premier commentaire.

    Pour finir je suis vraiment dans l’optique “le bon outil au bon use case”. Cependant cela ne dépend pas juste des capacités techniques. J’ai besoin de performance mais je veux un code réutilisable et j’ai du mal à trouver des ressources C++ sur le marché (oui il y a C++0x mais je doute qu’on l’enseignera à des étudiants).

    Et ceci en gardant en tête que ces besoins sont extrêmement marginaux… mais existent !

  7. Noël dit :

    @Nicolas,
    Tout à fait. C’est la première partie du problème : Java est-il le bon outil. La seconde partie est aussi quand même importante : ceux qui répondent “oui” (et peut-être ont-ils raison aux qui sont experts) ne font-ils pas perdre de vue une solution encore plus efficace au problème, plus mathématique qu’informatique.

    @Jean,
    Pour compléter mon commentaire en réponse à Nicolas, et rebondir sur le tien, la vrai valeur ajouter de Java par rapport à C++ c’est le JIT (c’est en tout cas ce que je retiens de manière quasi-certaine de ces trois jours de Devoxx). Les promesses de la compilation à chaud ne font pas de doute pour moi. Pour des programmes complexes on verra Java battre à terme les langages compilés, aussi proches du métal soient-ils, s’ils ne sont compilés qu’à froid.

    Le critère est le suivant : si l’algorithme est assez simple pour être exprimé de manière déterministe, alors on peut le coder en C (ou en langage d’assemblage) et c’est là qu’on aura les meilleures performances. Mais s’il est trop complexe pour être exprimé en un seul tenant (et c’est la majorité de nos applications), alors le JIT est là pour voler la vedette.

    Maintenant rien n’empêche un langage proche de la machine d’être compilé à chaud. J’ai demandé à deux conférenciers vendredi matin, suite à leur thème “Improving ressource utilisation – the JVM, the OS, and the hardware” (que je vous conseille fortement quand elle sortira

  8. Noël dit :

    … sur parleys.com) si un JIT hardware pouvait être imaginé. Par exemple, au lieu d’avoir une JVM on aurait un langage objet compilé en directives natives (C++, Go) et un JIT-like hardware qui serait spécialisé dans ce langage pour compiler à la volée des méthodes ou des morceaux de méthodes, comme fait JIT, et faire les inlines et autres optimisations. Derrière ça bien sûr le processeur continuerait à faire ses propres optimisations. A cette question Martijn Verburg et Ben Evans m’ont répondu que l’idée a existé mais qu’elle a fait un flop commercial… Sans plus de détails il faut que je me documente sur le sujet pour en savoir plus.

    A voir toutes les présentations bas niveau faites au Devoxx et surtout celles qui mettent en jeu le mécanisme du JIT et du bytecode je pense quand même que quand le JIT sera au point quelqu’un relancera l’idée de le faire descendre plus bas pour le rendre encore plus performant pour des cas très spécifiques, en particulier si Java continue à être le langage de pointe dans ce domaine.

    Mon opinion à l’heure actuelle c’est que ça n’est pas son rôle, et qu’il vaudrait mieux qu’un autre langage émerge (ou évolue) pour adopter cet objectif, plutôt que Java, ne serait-ce que pour garder Java comme langage haut niveau, abstrait, et portable. Rémi Forax a quant à lui indiqué qu’il trouve la partie JIT de la JVM déjà trop avancée pour que quelqu’un la rattrape comme ça. Et il s’y connaît plus que moi, donc il est fort probable que je me trompe et qu’il ait raison 😉

    L’autre option que j’entrevois c’est que Javascript devienne le Java de demain (haut niveau, abstrait, portable), tandis que Java continue à s’enfoncer dans les entrailles de la machine pour remplacer C. Pourquoi pas… Mais c’est un virage compliqué puisque Java entraîne désormais dans son sillage quasiment toute l’industrie du logiciel.

  9. Noël dit :

    Nota: quand je dis “quand le JIT sera au point” je veux dire “quand le JIT aura été poussé dans ses derniers retranchements”. Bien sûr le JIT *est* au point, mais les présentations ont bien montré qu’il reste plein de possibilités à explorer 😉

  10. Noël dit :

    Nota 2: vous m’excuserez pour les fautes d’orthographe et le manque de clarté pour l’aspect technique, il est tard, et après trois jour c’est dur d’être clair avec un commentaire… Je vous invite quand même à venir au JUG pour en discuter. Personnellement j’ai bien l’intention d’en reparler à Rémi Forax et à José Paumard.

  11. Olivier Lamy dit :

    Bonjour,
    Un outil comme DirectMemory n’est pas forcément utile dans des projets de la vie courante. Oui IMHO son utilisation doit se limiter aux applications à fort besoin de cache (beaucoup d’objets !) et ainsi laisser la heap au reste de l’application. La dé/sérialisation a un petit coût.
    La partie intéressante (IMHO encore:-) ) abordée à la fin est la partie serveur qui permet de partager en stockant à distance des objets.
    Et bien sûr merci d’être venu à la présentation (et d’avoir tenu le coup jusqu’à la fin j’espère 🙂 )

    Olivier Lamy

  12. benwit dit :

    bonjour
    Sauf erreur de ma part, j’ai plutôt noté 43000 pour les algos et 1000 pour le materiel.

  13. Noël dit :

    @benwit
    J’ai donné les nombres de tête, mais 43 pour 1 c’est encore plus impressionnant quand on sait que l’optimisation linéaire sous contraintes est un domaine de mathématiques appliquées très utilisé en informatique. Pour plus d’informations sur ce sujet, nous avons à So@t un spécialiste : Tanguy Lambert (ENSIIE 2006 comme moi, et qui a passé un Master en 3ème année sur ce sujet, pas comme moi :p).

AJOUTER UN COMMENTAIRE