JVM, CLR et Natif, performances comparées

JVM, CLR et Natif, performances comparées

Yoran, ven, 27/06/2008 - 14:29  
Tutoriels Java

Je croyais en avoir fini avec les benchmarks, mais vu le nombre de questions et demande que j'ai reçu en privé et commentaires, il m'a fallut remettre rapidement cela. Alors il s'agit toujours des mêmes types de tests mais qui ont un peu évolué en qualité de code. Mais surtout, cela inclus beaucoup plus de plateformes (windows, ms.Net, etc.).

attention cet article est très loin d'être à jour et les données présentées ici sont juste là pour mémoire. Raison pour laquelle les commentaires sont fermées. Si cependant vous avez besoin d'informations, n'hésitez pas à passer par le formulaire de contacts.

Préambule

L'objectif ici n'est absolument pas de lancer un grand débat théologique mais de tenter de se rendre compte au mieux des performances réelles de chaque plate-forme de développement. Il n'y a pas de langage meilleur que d'autre et chacun a son usage, même le Lisp, c'est dire...

Le seul intérêt de ce type de test est avant tout de prendre conscience des faiblesses de la plateforme que l'on utilise ou d'en choisir en connaissance de cause lorsque ce choix est encore libre. Après il ne s'agit que d'un ordre d'idée. Tout benchmark est par essence faux car l'observateur influe forcement sur son sujet. De plus les tests peuvent avoir des résultats différents d'une architecture physique à l'autre, gardez cela en tête.

Protocole

Les tests sont à l'origine codés par Christopher W. Cowell-Shah puis ont été modifiés par Thomas Bruckschlegel, puis enfin par moi-même pour finalement ne plus ressembler à rien de tout cela.

Ils sont encapsulés dans le même framework que mes précédents benchmarks (rejoués 10fois, etc..).

Les tests sont lancés dans leur propre processus. Deux tests, qu'ils soient de même type sur deux plateformes, ou issus du même test répété, n'utilisent jamais les mêmes fichiers (tentative de réduction de l'effet "cache").

Chaque test est répété 10 fois de suite, le meilleur score est retenu. Ce score est ensuite inversé puis mis à l'échelle de l'architecture de référence, Java 6. Cela donne la formule score= 100* (1/temps)/(1/temps_reference). Ainsi un score de 100% indique que le test est aussi rapide que son équivalent Java 6. 200% deux fois plus rapide et 50%, deux fois plus lent.

Plateformes testées

La plate-forme physique de test est un Q6600 2go de RAM avec un Windows XP SP3 et un Mandriva 2008.1 (kernel 2.6.24). Dans les deux cas le maximum de service a été fermé pour ne pas influencer les résultats. Dans les deux cas, le disque utilisé pour les entrées-sorties est le même, un disque seagate SATA 500G formaté en EXT3.

Les plateformes de développement testées sous Linux sont :

  • g++ version 4.2.3 avec les options -pipe -fomit-frame-pointer -O3.
  • gcj 4.3.0 avec les options -pipe -fomit-frame-pointer -O3.
  • Java 1.3.1 sp19.
  • Java 1.4.2 sp13.
  • Java 5 1.5.0 sp14.
  • Java6 1.6.0 sp4 (l'architecture de référence).
  • Java7 1.7.0.
  • Mono 1.2.6.
  • IKVM 0.36.0.5.

Et pour windows :

  • g++ MinGW 3.4.5 avec les options -pipe -fomit-frame-pointer -O3.
  • gcj MinGW 3.4.5 avec les options -pipe -fomit-frame-pointer -O3.
  • Java6 1.6.0 sp6.
  • Mono 1.0.1.
  • MS dotNet 3.5.

Les Threads

C'est un nouveau test que j'ai créé pour Java et C#, pas pour CPP, je n'ai pas eu le courage alors si ça tente quelqu'un...

Basiquement cela consiste en un anneau de N threads (120 dans le test car java/windows explose à plus...). Un signal (entier équal à 1000xnombre_threads) est injecté dans le premier thread qui l'envoie à son voisin en le décrémentant. Lorsqu'un thread reçoit une valeur négative, il meurt. Le temps est donc celui qui permet de construire les 100 threads, faire circuler le messages 1000 fois et de faire nourrir tous les threads. Le test utilise massivement les blocks synchronisés pour attendre les messages et se réveiller à leur arrivée.

Le meilleur dans ce domaine reste étrangement le JDK v3. Les performances dans ce domaines avaient baissées jusqu'au jdk v5 (le pire) pour remonter jusqu'au 7 qui aujourd'hui est à peu prés aussi rapide que le v4.

Mono lui est mauvais dans ce domaine, sous Linux comme sous Windows. Et vu la simplicité du test, je doute que cela vienne des sources. Ceci dit, sous Windows, le threading est globalement plus lent que sous Linux même aidé par les 4 cores du processeur.

GCJ se débrouille aussi bien qu'un JDK standard mais IKVM lui est à la ramasse, sûrement un problème d'implémentation car ses performances devraient au moins être en phase avec Mono.

Les nombres

Les entiers "courts"

Côté JDK, le travail sur les entiers s'est amélioré avec la version 5 et cela s'est globalement maintenu ainsi. Les machines .net sont elles un peu en retrait, au niveau des anciens JDK 3 et 4.

Après les résultats sont globalement homogènes entre 80 à 110%. Le meilleur temps étant pour GCJ sous Linux et le plus mauvais pour GCJ pour Windows. Ce qui n'est pas si choquant que cela vu que les versions testées ne sont pas les mêmes.

Les entiers longs

Là c'est clairement le natif qui l'emporte avec cependant un bonne performance de MS.Net et Java sous Windows. Mono est lui comme toujours en retrait sur les deux OS.

Côté JDK, Iced-Tea remonte la pente sous Linux pour obtenir un score très proche de celui du JDK 6 sous Windows.

Les doubles

Ici nous avons trois groupes. Les plus performant qui sont étrangement les JDK 5 et plus, et MS.Net. Mono est encore en retrait (et IKVM pareil), au niveau des veilles VM (JDK 4 et moins).

Chose étonnante, le natif n'est pas si efficace que cela sur ces tests, peut-être lié à une implémentation des doubles utilisée par gcc/gcj.

Trigonométrie

Une des bêtes noires de Java même si en constante amélioration. Et GCJ n'apporte cette fois pas grand chose. Les meilleurs dans ce domaine sont en premier le CPP, ensuite les .Net.

Entrées-Sorties

Sur fichier

Là c'est pas ma peine de chipoter, .Net deux fois meilleur que Java, c'est difficile à nier. Et pourtant je vous assure que j'ai essayé d'aider le code par tous les moyens possible mais le résultat reste peu ou prou le même.

Pour le reste, même les versions natives n'arrivent pas à dégommer MS.Net et sont à peu prés au même niveau de performance que les autres. J'aurais pu croire que mon protocole était le fautif si Mono n'avait pas eu les mêmes résultats que ces voisins. J'ai d'ailleurs sorti IKVM du test tant ses performances étaient ici mauvaises.

Itératif

Boucles imbriquées

Encore une excellent performance de GCJ qui est ici dans son coeur de métier et combat fièrement au côté de GCC.

Côté JVM, j'aime bien ce test de 6 boucles imbriquées car il montre comment Hotspot se débrouille pour avec les traitements itératifs. On voit bien l'évolution de ses performances au fur et à mesure du temps. A noter qu'il a du se passer quelque chose dans cette VM du JDK 6 car les performances sont un peu moins bonnes à partir de là.

Pour la concurrence le meilleur reste définitivement gcc. Mono est une fois de plus dans le décors mais pas plus que MS.Net cette fois qui sont les grands perdants de ce tests si l'on exclue les JVM obsolètes.

Tri

Toujours des boucles, mais cette fois pour faire quelque chose de sérieux : trier un énorme tableau (1 million de lignes).

Mono y est un peu moins mauvais que d'habitude à ce jeu et l'on constate que sur ce genre de "cas réel", on est pas si loin de GCC qui reste cependant 50% plus rapide que le pool des VM.

Notez que GCJ est ici aussi efficace que GCC, ce qui est logique vu son métier de compilateur natif qui ici n'utilise aucunement les librairies sous-jacentes. C'est sur ce genre de traitement que GCJ montre tout son interêt.

Ce que l'on notre aussi c'est qu'IKVM obtiens les mêmes scores que Mono. Ce qui là aussi est logique car généralement IKVM doit faire appel à une traduction du JDK, ce qui n'est ici pas le cas. En traitement pure, c'est bien Mono qui est le goulot.

Multiplication de matrice

Cette fois on mélange le tout boucles, tableaux et calculs pour une simple multiplication de deux grosses matrices d'entiers.

Je passe encore Mono qui décidément est bien malade sous Linux comme sous Windows où pourtant j'ai utilisé la toute dernière version. Les meilleurs ici sont clairement les natifs même si GCJ déçoit un peu en peinant sûrement sur l'allocation et la gestion des grands espaces mémoire ou encore le calcul sur les entiers.

Notons enfin que MS.Net est ici un peu plus rapide que Java mais surtout que l'on a clairement perdu de la performances en montant de JDK. Ce qui est d'autant plus incompréhensible que le calcul sur entiers s'est lui amélioré et que les boucles sont elles aussi plus performantes. D'où l'intérêt de ce genre de tests plus "vraie vie".

Concaténation de chaînes

Gagnants incontestables, les natives. Après viennent les machines .Net pour Windows qui se débrouillent un peu mieux que Mono sous Linux.

Pour Java, la performance d'IKVM semble indiquer que StringBuffer n'est pas une bête de vitesse, tirant vers le bas toutes les version Java, GCJ compris.

Structures de données

Tables de Hash

Là j'ai pas mal remanier le code C++ qui était la cause des mauvais résultats dans la précédente version du benchmark. Maintenant tout devient plus logique et les version natives sont en tête.

Cependant, MS.Net sous Windows et Mono sous Linux sont dans ce domaine assez brillant et frisent la performance que GCC obtient sous Linux. Heureusement le JDK 7 semble se relever et n'est pas si loin de ces meilleurs scores.

Vecteur

Là, tous aussi bon, ou tous aussi mauvais, je ne sais pas bien dire. Mais seule MS.Net semble se distinguer ici, mais dans le mauvais sens du terme. Et c'est bien lui le problème puisque Mono sous Windows ne semble lui pas souffrir plus que cela.

Divers

Exceptions

Gagnant, GCC sous Windows qui explose la version Linux qui est pourtant plus "évoluée". GCJ, IKVM et MS.Net sont eux les tristes perdants avec des scores juste nuls. Notons enfin une amélioration des performances de JDK7 dans ce domaine qui frise les X2.

Ceci dit, un test sur les exceptions vaut ce qu'il vaut dans la vraie vie où l'on n'est pas sensé en générer un millions à la volée...

Temps de démarrage

Ca c'est un test que j'ai un peu changé dans sa philosophie. En effet, en devant passer sous Windows, je me suis heurter au problème du vidage du cache disque et je n'ai absolument rien trouvé sous ce drôle d'OS pour faire une chose aussi simple. Du coup j'ai changé mon fusil d'épaule en relançant, sans vider le cache, le test 5 fois de suite et en prenant le meilleur temps. Ce résultat donne donc quelque chose de très stable indiquant les temps de chargement d'une plateforme "encore chaude".

Et même dans ce nouveau cadre Java reste le plus lent de tous, et IKVM est encore pire. Le seul qui arrive à descendre aussi bas est Mono sous Windows... MS.Net sous Windows en revanche obtiens de très bon scores en démarrant 3 fois plus vite que Java. Je passe le cas des natives qui sont clairement les plus rapides, évidemment...

Alors oui Java teste plein de chose en plus, valide chaque classe, décompresse l'énorme rt.jar,etc.. Et encore oui, avec le système de la 6, ce temps est amélioré mais franchement, c'est toujours pas le top. Une longue seconde pour une "application" qui fait 5 kilo, c'est juste mauvais. Et pour 500mo ? Euh, ça s'appelle Eclipse :-)

Alors je ne sais pas, il est peut-être temps de larguer le concept de .jar, de faire comme Mono avec ses assemblies, d'ajouter une option qui shunte les vérifications au démarrage, bref, faut faire un truc car si aujourd'hui on trouve Java lent, c'est psychologiquement lié à ce point là. Ce n'est certes pas un point crucial d'un point de vue technique mais c'en est clairement un d'un point de vue image.

Et petite surprise, le problème ne s'est pas amélioré avec GCJ qui ne démarre qu'un peu plus vite que ses sœurettes.

Résumé

Du JDK 3 au JDK 7, les performances n'ont fait que monter pour arriver à des performances 1.5 fois inférieures à celles d'un code équivalent natif. Donc première chose, oui Java c'est plus lent que du C++, malgré tous les benchmarks comiques que j'ai pu voir dans ce domaine. Alors maintenant il serait aussi intéressant de tester C++ avec un garbage collector histoire d'avoir une vision vraiment iso-fonctionnelle des choses. Mais que ce soit toujours plus rapide n'est pas tellement surprenant. Ce qui est intéressant en revanche c'est que ce n'est pas tellement plus rapide que certains autres voudraient le faire croire. Un facteur 1.5 pour la différence de confort et de maintenabilité, je ne trouve pas cela très cher payer.

Maintenant de .Net à Java, la performance reste du côté de Java malgré les très bons score de MS.Net sur les IO. Notez que j'ai exclus du résumé les Exceptions (pas assez pertinent dans la vraie vie) et les temps de démarrage. Mono est quant à lui un peu en retrait côté performance mais pas si loin de MS.Net. IKVM quant à lui est encore jeune et de toute façon destiné à faire coopérer du code java et C#. En effet, rien ne prouve, et même au contraire, que la CLR soit plus rapide que la JVM.

Pour ce qui est de GCJ c'est un peu décevant sur la globalité et tend à prouver qu'une bonne partie de la "lenteur" de Java ne tiens pas plus à la JVM qu'aux librairies.

Enfin, désolé pour les Pro-Windows ou les Pro-Linux mais globalement sur ces tests, les scores se valent d'une plateforme à l'autre.

Conclusions

En tout cas la mouture 6 de Java est clairement une réussite en terme de performance. Et la 7 est plus que prometteuse même si Iced-Tea est encore un projet en progression. Et les conclusions des tests, même s'ils ne sont que des tests à prendre avec toutes les précautions d'usage, prouvent que pour un confort largement supérieur (question de goût ?;-), Java est loin de se laisser aussi distancer que la légende veut bien le faire croire, même par du code en C, compilé de manière optimisée.

Vus : 408
Publié par arNuméral : 54