Parfois Java surpasse C++ dans les benchmarks. Bien sûr, parfois C++ surpasse.
Voir les liens suivants:
Mais comment est-ce encore possible? Cela me fait penser que le bytecode interprété pourrait être plus rapide qu'un langage compilé.
Quelqu'un peut-il expliquer? Merci!
Tout d'abord, la plupart des machines virtuelles Java incluent un compilateur, donc le "bytecode interprété" est en fait assez rare (au moins dans le code de référence - ce n'est pas aussi rare dans la vie réelle, où votre code est généralement plus que quelques boucles triviales qui se répètent très souvent). ).
Deuxièmement, bon nombre des repères impliqués semblent être assez biaisés (que ce soit par intention ou par incompétence, je ne peux pas vraiment le dire). Par exemple, il y a quelques années, j'ai regardé une partie du code source lié à l'un des liens que vous avez publiés. Il avait un code comme celui-ci:
init0 = (int*)calloc(max_x,sizeof(int));
init1 = (int*)calloc(max_x,sizeof(int));
init2 = (int*)calloc(max_x,sizeof(int));
for (x=0; x<max_x; x++) {
init2[x] = 0;
init1[x] = 0;
init0[x] = 0;
}
Puisque calloc
fournit une mémoire déjà mise à zéro, utiliser la boucle for
pour la remettre à zéro est évidemment inutile. Cela a été suivi (si la mémoire sert) en remplissant la mémoire avec d'autres données de toute façon (et aucune dépendance à sa remise à zéro), de sorte que toute la mise à zéro était complètement inutile de toute façon. Le remplacement du code ci-dessus par un simple malloc
(comme toute personne sensée aurait pu commencer par) a amélioré la vitesse de la version C++ suffisamment pour battre la version Java (par une marge assez large, si mémoire sert).
Considérez (pour un autre exemple) le benchmark methcall
utilisé dans l'entrée de blog de votre dernier lien. Malgré le nom (et à quoi les choses pourraient ressembler), la version C++ de ceci ne mesure pas vraiment la surcharge des appels de méthode. La partie du code qui s'avère critique se trouve dans la classe Toggle:
class Toggle {
public:
Toggle(bool start_state) : state(start_state) { }
virtual ~Toggle() { }
bool value() {
return(state);
}
virtual Toggle& activate() {
state = !state;
return(*this);
}
bool state;
};
La partie critique s'avère être le state = !state;
. Considérez ce qui se passe lorsque nous modifions le code pour coder l'état en tant que int
au lieu d'un bool
:
class Toggle {
enum names{ bfalse = -1, btrue = 1};
const static names values[2];
int state;
public:
Toggle(bool start_state) : state(values[start_state])
{ }
virtual ~Toggle() { }
bool value() { return state==btrue; }
virtual Toggle& activate() {
state = -state;
return(*this);
}
};
Ce changement mineur améliore la vitesse globale d'environ marge 5: 1. Même si le benchmark était destiné à mesurer le temps d'appel de la méthode, en réalité, la plupart de ce qu'il mesurait était le temps de conversion entre int
et bool
. Je conviendrais certainement que l'inefficacité montrée par l'original est regrettable - mais étant donné la rareté avec laquelle cela semble se produire dans le code réel et la facilité avec laquelle il peut être corrigé quand/si cela se produit, j'ai du mal à penser de cela comme signifiant beaucoup.
Dans le cas où quelqu'un décide de relancer les benchmarks impliqués, je dois également ajouter qu'il y a une modification presque aussi triviale à la version Java qui produit (ou au moins à un moment donné - je n'ai pas réexécuté les tests avec une JVM récente pour confirmer qu'ils le font encore) une amélioration assez substantielle de la version Java également. La version Java a un NthToggle :: activate () qui ressemble à ceci:
public Toggle activate() {
this.counter += 1;
if (this.counter >= this.count_max) {
this.state = !this.state;
this.counter = 0;
}
return(this);
}
Changer cela pour appeler la fonction de base au lieu de manipuler this.state
Donne une amélioration de vitesse assez substantielle (mais pas suffisante pour suivre la version C++ modifiée).
Ainsi, nous nous retrouvons avec une fausse hypothèse sur les codes d'octets interprétés par rapport à certains des pires repères (que j'ai) jamais vus. Ni donne un résultat significatif.
Ma propre expérience est que, avec des programmeurs également expérimentés accordant une attention égale à l'optimisation, C++ battra Java plus souvent qu'autrement - mais (au moins entre ces deux), le langage fera rarement autant de différence que les programmeurs et conception. Les repères cités nous en disent plus sur la (in) compétence/(dés) honnêteté de leurs auteurs que sur les langues qu'ils prétendent comparer.
[Edit: Comme impliqué à un endroit ci-dessus mais jamais déclaré aussi directement que je devrais probablement l'avoir fait, les résultats que je cite sont ceux que j'ai obtenus lorsque j'ai testé cela il y a ~ 5 ans, en utilisant les implémentations C++ et Java qui étaient courant à ce moment-là. Je n'ai pas réexécuté les tests avec les implémentations actuelles. Un coup d'œil, cependant, indique que le code n'a pas été corrigé, donc tout ce qui aurait changé serait la capacité du compilateur à couvrir les problèmes dans le code.]
Cependant, si nous ignorons les exemples Java, il est réellement possible pour le code interprété de s'exécuter plus rapidement que le code compilé (bien que difficile et quelque peu inhabituel ).
Cela se produit généralement de la manière suivante: le code en cours d'interprétation est beaucoup plus compact que le code machine, ou il s'exécute sur un processeur doté d'un cache de données plus important que le cache de code.
Dans un tel cas, un petit interprète (par exemple, l'interpréteur interne d'une implémentation de Forth) peut être en mesure de tenir entièrement dans le cache de code, et le programme qu'il interprète tient entièrement dans le cache de données. Le cache est généralement plus rapide que la mémoire principale d'un facteur d'au moins 10, et souvent beaucoup plus (un facteur de 100 n'est plus particulièrement rare).
Donc, si le cache est plus rapide que la mémoire principale d'un facteur N et qu'il faut moins de N instructions de code machine pour implémenter chaque code d'octet, le code d'octet devrait gagner (je simplifie, mais je pense que l'idée générale devrait toujours être évident).
Roulé à la main C/C++ fait par un expert avec un temps illimité va être au moins aussi rapide ou plus rapide que Java. En fin de compte, Java lui-même est écrit en C/C++ afin que vous puissiez bien sûr tout faire Java le fait si vous êtes prêt à faire suffisamment d'efforts d'ingénierie).
En pratique cependant, Java s'exécute souvent très rapidement pour les raisons suivantes:
En même temps, C/C++ présente également certains avantages:
Global:
Le Java runtime isnt interprète le bytecode. Il utilise plutôt ce qu'on appelle Just In Time Compilation =. Fondamentalement, pendant que le programme est exécuté, il prend le bytecode et le convertit en code natif optimisé pour le CPU particulier.
Toutes choses étant égales par ailleurs, vous pourriez dire: non, Java ne devrait jamais être plus rapide. Vous pouvez toujours implémenter Java en C++ à partir de gratter et ainsi obtenir des performances au moins aussi bonnes. En pratique cependant:
En passant, cela me rappelle le débat sur le C dans les années 80/90. Tout le monde se demandait "C peut-il être aussi rapide que l'assemblage?". Fondamentalement, la réponse était: non sur le papier, mais en réalité, le compilateur C a créé un code plus efficace que 90% des programmeurs d'assemblage (enfin, une fois qu'il a mûri un peu).
Mais l'allocation n'est que la moitié de la gestion de la mémoire - la désallocation est l'autre moitié. Il s'avère que pour la plupart des objets, le coût direct de la récupération de place est de - zéro. En effet, un collecteur de copie n'a pas besoin de visiter ou de copier des objets morts, seulement des objets vivants. Les objets qui deviennent des ordures peu de temps après l'allocation ne contribuent donc pas à la charge de travail du cycle de collecte.
...
Les machines virtuelles Java sont étonnamment bonnes pour comprendre des choses que nous supposions que seul le développeur pouvait savoir. En laissant la JVM choisir entre l'allocation de pile et l'allocation de tas au cas par cas, nous pouvons obtenir les avantages de performance de l'allocation de pile sans que le programmeur agonise sur l'allocation sur la pile ou sur le tas.
http://www.ibm.com/developerworks/Java/library/j-jtp09275/index.html
Alors qu'un programme Java Java complètement optimisé battra rarement un programme C++ complètement optimisé, des différences dans des choses comme la gestion de la mémoire peuvent rendre beaucoup d'algorithmes idiomatiquement implémentés dans Java plus rapide que les mêmes algorithmes implémentés idiomatiquement en C++.
Comme l'a souligné @Jerry Coffin, il existe de nombreux cas où de simples modifications peuvent rendre le code beaucoup plus rapide - mais souvent, il peut prendre trop de réglages impurs dans une langue ou dans l'autre pour que l'amélioration des performances en vaille la peine. C'est probablement ce que vous verriez dans un bon benchmark qui montre Java faisant mieux que C++.
De plus, bien que ce ne soit généralement pas si important, il existe une optimisation des performances qu'un langage JIT comme Java peut faire que C++ ne peut pas. Le Java runtime peut inclure des améliorations après que le code a été compilé, ce qui signifie que le JIT peut potentiellement produire du code optimisé pour tirer parti de nouvelles (ou au moins différentes) fonctionnalités CPU Pour cette raison, un binaire de 10 ans Java peut potentiellement surpasser un binaire C++ de 10 ans.
Enfin, une sécurité de type complète dans son ensemble peut, dans de très rares cas, offrir des améliorations de performances extrêmes. Singularité , un OS expérimental écrit presque entièrement dans un langage basé sur C #, a une communication interprocessus et un multitâche beaucoup plus rapides du fait qu'il n'y a pas besoin de limites de processus matériel ou de commutateurs de contexte coûteux.
Publié par Tim Holloway sur JavaRanch:
Voici un exemple primitif: à l'époque où les machines fonctionnaient selon des cycles déterminés mathématiquement, une instruction de branche avait généralement 2 synchronisations différentes. Un pour quand la branche a été prise, un pour quand la branche n'a pas été prise. Habituellement, le cas sans succursale était plus rapide. De toute évidence, cela signifiait que vous pouviez optimiser la logique en fonction de la connaissance du cas le plus courant (sous réserve que ce que nous "savons" ne soit pas toujours ce qui est réellement le cas).
La recompilation JIT va encore plus loin. Il surveille l'utilisation réelle en temps réel et inverse la logique en fonction du cas le plus courant. Et retournez-le à nouveau si la charge de travail change. Le code compilé statiquement ne peut pas faire cela. Voilà comment Java peut parfois surpasser le code Assembly/C/C++ réglé à la main.
Source: http://www.coderanch.com/t/547458/Performance/Java/Ahead-Time-vs-Just-time
En effet, la dernière étape de génération du code machine se déroule de manière transparente à l'intérieur la JVM lors de l'exécution de votre programme Java, au lieu d'être explicite lors de la construction de votre programme C++).
Vous devriez considérer le fait que les machines virtuelles Java modernes passent beaucoup de temps à compiler le code d'octets à la volée en code machine natif pour le rendre aussi rapide que possible. Cela permet à la JVM d'effectuer toutes sortes de trucs de compilation qui peuvent être encore meilleurs en connaissant les données de profilage du programme en cours d'exécution.
Une telle chose que d'inclure automatiquement un getter, de sorte qu'un JUMP-RETURN ne soit pas nécessaire pour obtenir simplement une valeur, accélère les choses.
Cependant, la chose qui a vraiment permis des programmes rapides est un meilleur nettoyage par la suite. Le mécanisme de récupération de place dans Java est plus rapide que le manuel sans malloc en C. De nombreuses implémentations modernes sans malloc utilisent un récupérateur de place en dessous.
Réponse courte - ce n'est pas le cas. Oubliez ça, le sujet est aussi vieux que le feu ou la roue. Java ou .NET n'est pas et ne sera pas plus rapide que C/C++. Il est assez rapide pour la plupart des tâches où vous n'avez pas du tout besoin de penser à l'optimisation. Comme les formulaires et le traitement SQL, mais c'est là que ça se termine.
Pour les benchmarks ou les petites applications écrites par des développeurs incompétents, oui, le résultat final sera que Java/.NET va probablement être proche et peut-être encore plus rapide.
En réalité, des choses simples comme allouer de la mémoire sur la pile ou simplement utiliser des memzones tueront simplement Java/.NET sur place.
Le monde de la collecte des ordures utilise une sorte de memzone avec toute la comptabilité. Ajouter une memzone à C et C sera plus rapide sur place. Surtout pour les benchmarks Java vs C "code haute performance", qui vont comme ceci:
for(...)
{
alloc_memory//Allocating heap in a loop is verrry good, in't it?
zero_memory//Extra zeroing, we really need it in our performance code
do_stuff//something like memory[i]++
realloc//This is lovely speedup
strlen//loop through all memory, because storing string length is soo getting old
free//Java will do that outside out timing loop, but oh well, we're comparing apples to oranges here
}//loop 100000 times
Essayez d'utiliser des variables basées sur la pile en C/C++ (ou nouveau placement), elles se traduisent par sub esp, 0xff
, c'est une seule instruction x86, battez-la avec Java - vous ne pouvez pas ...
La plupart du temps, je vois ces bancs où Java contre C++ sont comparés, cela me fait aller comme, avec des stratégies d'allocation de mémoire incorrectes, des conteneurs auto-croissants sans réserves, plusieurs nouveaux. Ce n'est pas même proche du code C/C++ orienté performances.
Aussi une bonne lecture: https://days2011.scala-lang.org/sites/days2011/files/ws3-1-Hundt.pdf
La réalité est qu'ils ne sont que des assembleurs de haut niveau qui font exactement ce que le programmeur leur dit, exactement comment le programmeur leur dit dans l'ordre exact que le programmeur leur dit. Les différences de performances sont si faibles qu'elles sont sans conséquence pour toutes les applications pratiques.
Le langage n'est pas "lent", le programmeur a écrit un programme lent. Il est très rare qu'un programme écrit de la meilleure façon dans une langue soit supérieur (à n'importe quel but pratique) à un programme faisant la même chose en utilisant la meilleure façon de la langue alternative, à moins que l'auteur de l'étude ne cherche à broyer sa hache particulière.
Évidemment, si vous allez dans un cas Edge rare comme les systèmes embarqués durs en temps réel, le choix de la langue peut faire la différence, mais à quelle fréquence est-ce le cas? et dans ces cas, à quelle fréquence le bon choix n'est-il pas aveuglément évident?.
Voir les liens suivants ... Mais comment est-ce encore possible? Cela me fait penser que le bytecode interprété pourrait être plus rapide qu'un langage compilé.
Keith Lea vous dit qu'il y a des "défauts évidents" mais ne fait rien sur ces "défauts évidents". En 2005, ces anciennes tâches ont été abandonnées et remplacées par les tâches maintenant affichées dans le jeu de benchmarks .
Keith Lea vous dit qu'il "a pris le code de référence pour C++ et Java de la désormais obsolète Great Computer Language Shootout et a exécuté les tests" mais en fait il ne montre que les mesures de 14 sur 25 de ces tests obsolètes .
Keith Lea vous dit maintenant qu'il n'essayait pas de prouver quoi que ce soit avec le blog sept ans auparavant, mais à l'époque, il a dit: "J'en avais marre d'entendre les gens dire Java était lent, quand je sais c'est assez rapide ... "ce qui suggère à l'époque qu'il essayait de prouver quelque chose.
Christian Felde vous dit "Je n'ai pas créé le code, j'ai simplement relancé les tests." comme si cela le dégageait de toute responsabilité dans sa décision de publier les mesures des tâches et des programmes sélectionnés par Keith Lea.
Les mesures de même 25 minuscules programmes minuscules fournissent-elles des preuves définitives?
Ces mesures concernent des programmes exécutés en "mode mixte" Java non interprété Java - "N'oubliez pas comment fonctionne HotSpot.") Vous pouvez savoir facilement comment Java exécute le "bytecode interprété", car vous pouvez forcer Java à uniquement interpréter le bytecode - il suffit de chronométrer certains programmes Java exécutés avec et sans l'option -Xint.