Pourquoi serait-il possible que Java soit plus rapide que C ++?

80

Parfois, Java surpasse le C ++ dans les tests de performance. Bien sûr, parfois, C ++ surpasse.

Voir les liens suivants:

Mais comment est-ce possible? Cela me dépasse de penser que le bytecode interprété pourrait être plus rapide qu'un langage compilé.

Quelqu'un peut-il s'il vous plaît expliquer? Merci!

Deets McGeets
la source
2
Vous pouvez jeter un oeil à shootout.alioth.debian.org/u32/… pour voir le genre de problèmes qui tournent plus vite sous java / c ++ ... Voyez le schéma des problèmes, et non ces problèmes spécifiques ...
c0da
2
Voir Pourquoi java a-t-il la réputation d'être lent? pour beaucoup de détails sur ce sujet.
Péter Török le
11
Il est interdit par la loi (Section 10.101.04.2c) de produire une machine virtuelle Java plus rapide qu'un binaire exécutable produit avec C ++.
Mateen Ulhaq
1
@ muntoo Qu'est-ce que tu veux dire? Section 10.101.04.2c de quoi?
Highland Mark
3
@HighlandMark Vous n'êtes pas membre du cercle. Vous ne devez pas savoir ce qui se passe à l'intérieur du cercle. Le cercle est absolu - ses lois ont préséance sur celles de la nature. Vous ne pouvez ni défier ni interroger le cercle. Je suis le cercle et le cercle c'est moi.
Mateen Ulhaq le

Réponses:

108

Premièrement, la plupart des machines virtuelles incluent un compilateur, le "code octet interprété" est en fait assez rare (du moins dans le code de référence - ce n'est pas aussi rare dans la réalité, où votre code est généralement plus que quelques boucles triviales qui se répètent extrêmement souvent ).

Deuxièmement, bon nombre de points de repère impliqués semblent assez biaisés (que ce soit par intention ou par incompétence, je ne peux pas vraiment dire). Par exemple, il y a des années, j'ai examiné une partie du code source lié à l'un des liens que vous avez publiés. Il y avait un code comme ceci:

  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 callocfournit une mémoire qui a déjà été mise à zéro, utiliser à nouveau la forboucle est évidemment inutile. Cela a été suivi (si la mémoire le permet) par le remplissage de la mémoire avec d’autres données (sans aucune dépendance quant à leur remise à zéro), de sorte que toute la remise à zéro était totalement inutile de toute façon. Remplacer le code ci-dessus par un simple malloc(comme toute personne saine d'esprit l'aurait utilisée au départ) améliorait suffisamment la vitesse de la version C ++ pour battre la version Java (avec une marge assez large, si la mémoire le permet).

Prenons (pour un autre exemple) le methcallrepère utilisé dans l'entrée de blog de votre dernier lien. Malgré son nom (et son apparence possible), la version C ++ de cet outil ne mesure pas grand-chose du tout sur le temps système d'appel de méthode. La partie du code qui s'avère critique est 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 intde la manière suivante 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 5: 1 . Bien que l' objectif ait été conçu pour mesurer le temps d'appel d'une méthode, en réalité, il s'agissait essentiellement du temps nécessaire pour effectuer la conversion entre intet bool. Je conviens certainement que l'inefficacité montrée par l'original est regrettable - mais étant donné que cela semble rarement apparaître dans un code réel, et qu'il est facile de le réparer quand / si cela se produit, j'ai du mal à penser de cela comme signifiant beaucoup.

Au cas où quelqu'un déciderait de réexécuter les tests de performance impliqués, je devrais également ajouter qu'il y a une modification presque aussi triviale à la version Java qui produit (ou au moins une fois produite - je n'ai pas réexécuté les tests avec une JVM récente pour confirmer 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.statedirectement donne une amélioration assez rapide de la vitesse (mais pas assez pour suivre la version modifiée de C ++).

Nous nous retrouvons donc avec une fausse hypothèse sur les codes d’octets interprétés par rapport à certains des pires points de repère (que j’ai jamais vus). Ni donne un résultat significatif.

Selon ma propre expérience, avec des programmeurs tout aussi expérimentés qui accordent autant d’attention à l’optimisation, C ++ battra Java le plus souvent - mais (au moins entre ces deux), le langage fera rarement autant de différence que les programmeurs et le concepteur. Les repères cités nous en disent plus sur l’inaptitude / la (mauvaise) honnêteté de leurs auteurs que sur les langues qu’ils prétendent comparer.

[Edit: Comme suggéré dans un endroit ci-dessus mais jamais indiqué aussi directement que je devrais probablement avoir, les résultats que je cite sont ceux que j’ai obtenus lorsque j’ai testé cela il y a environ 5 ans, en utilisant des implémentations C ++ et Java qui étaient courantes à cette époque. . Je n'ai pas relancé 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 à dissimuler les problèmes dans le code.]

Cependant, si nous ignorons les exemples Java, il est en fait possible que le code interprété s'exécute 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 de la machine ou 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ète interne d'une implémentation Forth) peut être entièrement inséré dans le cache de code et le programme qu'il interprète s'inscrit entièrement dans le cache de données. Le cache est généralement plus rapide que la mémoire principale d'au moins 10 fois, 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 tout de même être apparent).

Jerry Coffin
la source
26
+1, ack complet. En particulier, "le langage fera rarement autant de différence que les programmeurs et les concepteurs" - vous tomberez souvent sur des problèmes d'optimisation de l'algorithme, par exemple, améliorez big-O, ce qui donnera un boost bien supérieur à celui du meilleur compilateur.
schnaader
1
"Au cas où quelqu'un déciderait de relancer les tests de performances impliqués ..." En 2005, ces anciennes tâches avaient été abandonnées et remplacées par les tâches figurant maintenant dans le jeu de test de performances. Si quelqu'un veut relancer certains programmes alors s'il vous plaît relancer les programmes actuels pour les tâches actuelles indiquées sur le jeu des critères page d'accueil shootout.alioth.debian.org
igouy
@igouy: Certaines personnes voudront peut-être simplement confirmer / infirmer les résultats des tests qu'ils ont effectués, avec le minimum de corrections nécessaires pour au moins leur donner une relation minimale avec la réalité. En même temps, vous avez fondamentalement raison: les points de repère en question sont tellement mauvais que le simple fait de corriger les erreurs les plus évidentes ne va pas aider beaucoup.
Jerry Coffin le
Et c’est pourquoi, en 2005, elles ont été supprimées et remplacées par les tâches présentées dans le jeu de référence. Les personnes qui ne connaissent pas mieux peuvent relancer ces vieux programmes.
igouy
13
+1 Je n'aime pas les gens qui codent C ++ dans un style C ou Java, puis déclarent que Java est supérieur. disclaimer: Je n'appelle aucune langue supérieure, mais écrire du code C ++ pourri dans un style qui pourrait parfaitement convenir à une autre langue ne rend pas les deux langues comparables.
Chris dit: Réintégrer Monica le
112

Le C / C ++ réalisé à la main par un expert au temps illimité sera au moins aussi rapide ou plus rapide que Java. En fin de compte, Java lui-même est écrit en C / C ++, ce qui vous permet bien sûr de faire tout ce que Java fait si vous êtes prêt à fournir suffisamment d’ingénierie.

Cependant, dans la pratique, Java s’exécute souvent très vite pour les raisons suivantes:

  • Compilation JIT - bien que les classes Java soient stockées en tant que bytecode, celui-ci est (généralement) compilé en code natif par le compilateur JIT au démarrage du programme. Une fois compilé, il s’agit d’un code natif pur - donc théoriquement, on peut s’attendre à ce qu’il fonctionne aussi bien que le C / C ++ compilé une fois que le programme a fonctionné suffisamment longtemps (c’est-à-dire une fois que toute la compilation JIT a été effectuée)
  • Le ramassage des ordures en Java est extrêmement rapide et efficace - le GC Hotspot est probablement la meilleure implémentation de GC dans le monde. C'est le résultat de nombreuses années de travail d'experts de la part de Sun et d'autres sociétés. Quasiment tout système de gestion de mémoire complexe que vous lancez vous-même en C / C ++ sera pire. Bien sûr, vous pouvez écrire des schémas de gestion de la mémoire de base assez rapides / légers en C / C ++, mais ils ne seront pas aussi polyvalents qu'un système GC complet. Étant donné que la plupart des systèmes modernes nécessitent une gestion de la mémoire complexe, Java présente donc un avantage considérable dans les situations réelles.
  • Meilleur ciblage de la plate-forme - en retardant la compilation au démarrage de l'application (compilation JIT, etc.), le compilateur Java peut tirer parti du fait qu'il connaît le processeur exact sur lequel il s'exécute. Cela peut permettre certaines optimisations très utiles que vous ne pourriez pas réaliser dans du code C / C ++ précompilé qui doit cibler un jeu d'instructions de processeur "plus petit dénominateur commun".
  • Statistiques d'exécution - Comme la compilation JIT est effectuée à l'exécution, il peut rassembler des statistiques pendant l'exécution du programme, ce qui permet de meilleures optimisations (par exemple, en sachant la probabilité qu'une branche particulière soit prise). Cela peut permettre aux compilateurs Java JIT de produire un meilleur code que les compilateurs C / C ++ (qui doivent "deviner" la branche la plus probable à l’avance, hypothèse qui peut souvent être fausse).
  • Très bonnes bibliothèques - le runtime Java contient une multitude de bibliothèques très bien écrites et performantes (en particulier pour les applications côté serveur). Celles-ci sont souvent meilleures que ce que vous pourriez écrire vous-même ou obtenir facilement pour C / C ++.

Dans le même temps, le C / C ++ présente également certains avantages:

  • Plus de temps pour faire des optimisations avancées - La compilation C / C ++ est effectuée une fois, et peut donc passer beaucoup de temps à effectuer des optimisations avancées si vous le configurez de cette manière. Il n'y a aucune raison théorique pour laquelle Java ne pourrait pas faire la même chose, mais dans la pratique, vous voulez que Java compile le code JIT relativement rapidement. Le compilateur JIT a donc tendance à se concentrer sur des optimisations "plus simples".
  • Instructions qui ne sont pas exprimables en bytecode - alors que le bytecode Java est un objectif tout à fait générique, vous pouvez toujours faire certaines choses à un niveau bas que vous ne pouvez pas faire en bytecode (l'arithmétique de pointeur non vérifiée est un bon exemple!). En utilisant ces astuces, vous pouvez obtenir des avantages en termes de performances.
  • Moins de contraintes de «sécurité» - Java effectue un travail supplémentaire pour garantir la sécurité et la fiabilité des programmes. Des exemples sont les contrôles de bornes sur les tableaux, certaines garanties de concurrence, les contrôles de pointeur null, la sécurité de type sur les transtypages, etc.

Global:

  • Java et C / C ++ peuvent atteindre des vitesses similaires
  • C / C ++ a probablement le léger avantage dans des circonstances extrêmes (il n’est pas surprenant que les développeurs de jeux AAA le préfèrent toujours, par exemple)
  • En pratique, cela dépendra de la manière dont les différents facteurs énumérés ci-dessus s'équilibrent pour votre application particulière.
Mikera
la source
9
Ad "plus de temps pour les optimisations en C ++": c'est l' un des petits soucis de la machine virtuelle Oracle lorsque vous choisissez la machine virtuelle serveur: elle accepte un coût de démarrage plus élevé afin de permettre des performances plus élevées à long terme. La machine virtuelle client est toutefois optimisée pour optimiser le temps de démarrage. Donc, cette distinction existe même au sein de Java.
Joachim Sauer le
8
-1: Un compilateur C ++ peut prendre beaucoup plus de temps (littéralement pour une grande bibliothèque) pour créer un binaire très optimisé. Le compilateur Java JIT ne peut pas prendre autant de temps, même la version "serveur". Je doute sérieusement que le compilateur Java JIT puisse effectuer l'optimisation de tout le programme comme le compilateur MS C ++.
quant_dev
20
@ quant_dev: bien sûr, mais n'est-ce pas ce que j'ai dit dans ma réponse sous la forme d'un avantage C ++ (plus de temps pour l'optimisation avancée)? Alors pourquoi le -1?
Mikera
13
Le ramassage des ordures n'est pas un avantage de vitesse pour Java. C'est seulement un avantage de vitesse si vous êtes un programmeur C ++ qui ne sait pas ce que vous faites. Si tout ce que vous vérifiez est à quelle vitesse vous pouvez allouer, alors oui, le ramasse-miettes gagnera. Toutefois, la performance globale du programme peut toujours être améliorée en gérant manuellement la mémoire.
Billy ONeal le
4
... Mais avec C ++, vous pouvez toujours théoriquement mettre une "couche semblable à JIT" qui effectue des optimisations de branche similaires à l'exécution, tout en conservant la vitesse brute d'un programme C ++. (Théoriquement. :()
Mateen Ulhaq le
19

Le runtime Java n'interprète pas le bytecode. Au lieu de cela, il utilise ce qui s'appelle Compilation Just In Time . Fondamentalement, lorsque le programme est exécuté, il prend du bytecode et le convertit en code natif optimisé pour le processeur en question.

Grand maître b
la source
En pratique, oui. En principe, cela dépend - les premières machines virtuelles Java utilisaient des interpréteurs de code-octet, et vous pouvez probablement toujours trouver des VM interprétant des codes-octets si vous regardez suffisamment.
Steve314
10
@ Steve314: mais les ordinateurs virtuels interprétant uniquement ne seront pas ceux qui surpassent le C ++, ils ne sont donc pas vraiment pertinents pour cette question.
Joachim Sauer le
Le compilateur JIT peut également optimiser de manière dynamique l'utilisation spécifique du code, ce qui n'est pas possible avec un code compilé de manière statique.
starblue
2
@starblue, eh bien, c'est assez possible avec une compilation statique - voir l'optimisation guidée par le profil.
SK-logic
19

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 zéro et ainsi obtenir des performances au moins aussi bonnes. En pratique cependant:

  • JIT compile le code sur la machine de l'utilisateur final, ce qui lui permet d'optimiser son processeur. Bien que la compilation comporte un temps système, elle pourrait bien rapporter des applications intensives. Souvent, les programmes de la vie réelle ne sont pas compilés pour le processeur que vous utilisez.
  • Le compilateur Java est peut-être mieux à même d'optimiser automatiquement les choses qu'un compilateur C ++. Ou peut-être pas, mais dans le monde réel, les choses ne sont pas toujours parfaites.
  • Le comportement des performances peut varier en fonction d'autres facteurs, tels que le ramassage des ordures. En C ++, vous appelez généralement le destructeur immédiatement lorsque vous avez terminé avec un objet. En Java, vous libérez simplement la référence, ce qui retarde la destruction réelle. C'est un autre exemple de différence qui n'est ni ici ni là-bas, en termes de performance. Bien sûr, vous pouvez faire valoir que vous pouvez implémenter la GC en C ++ et en finir avec cela, mais la réalité est que peu de gens le souhaitent / le souhaitent / le peuvent.

En passant, cela me rappelle le débat sur le carbone dans les années 80/90. Tout le monde se demandait "C peut-il être aussi rapide que l’assemblée?". En gros, la réponse était: non sur papier, mais en réalité, le compilateur C avait créé un code plus efficace que 90% des programmeurs d’assemblage (enfin, une fois qu’il a mûri un peu).

Daniel B
la source
2
En ce qui concerne GC, ce n’est pas seulement que GC peut retarder la destruction d’objets (ce qui ne devrait pas avoir d’importance à long terme); le fait est qu'avec les GC modernes, l'allocation / désallocation d'objets de courte durée est extrêmement peu coûteuse en Java par rapport au C ++.
Péter Török le
@ PéterTörök oui, tu as raison, bon point.
Daniel B
9
@ PéterTörök Mais en C ++, les objets éphémères sont souvent placés dans la pile, ce qui est beaucoup plus rapide que n'importe quel tas GC-ed que Java peut utiliser.
quant_dev
@ quant_dev, vous avez oublié un autre effet GC significatif: la compactification. Je ne saurais donc pas trop quelle voie est la plus rapide.
SK-logic
3
@DonalFellows Qu'est-ce qui vous fait penser que je dois m'inquiéter de la gestion de la mémoire en C ++? La plupart du temps je ne le fais pas. Vous devez appliquer des modèles simples, différents de Java, mais c'est tout.
quant_dev
10

Mais l’allocation ne représente 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 égal à - zéro. En effet, un collecteur de copie n'a pas besoin de visiter ou de copier des objets morts, mais uniquement des objets vivants. Les objets qui deviennent des déchets peu de temps après l’allocation ne contribuent donc pas à une charge de travail dans le cycle de collecte.

...

Les JVM sont étonnamment douées pour comprendre des choses que nous supposions auparavant que seul le développeur pouvait connaître. En laissant la JVM choisir entre l'allocation de pile et l'allocation de segment de mémoire au cas par cas, nous pouvons obtenir les avantages en termes de performances de l'allocation de pile sans que le programmeur ne s'embarrasse à l'idée d'allouer sur la pile ou sur le tas.

http://www.ibm.com/developerworks/java/library/j-jtp09275/index.html

Landei
la source
Ceci n’est qu’une petite partie de l’ensemble, mais néanmoins pertinent.
Joachim Sauer le
2
J'aime la façon dont la substance est: java est pour les noobs, faites confiance à la magie GC, elle sait mieux.
Morg.
1
@Morg: Ou vous pouvez le lire de cette façon: Java est destiné aux personnes qui aiment faire avancer les choses au lieu de perdre leur temps avec des problèmes de gestion et de gestion manuelle de la mémoire.
Landei
4
@ Landei Je pense que votre commentaire aurait beaucoup plus de crédibilité si un code de base décent, largement utilisé et de longue durée avait été écrit en Java. Dans mon monde, les véritables systèmes d’exploitation sont écrits en C, postgreSQL est écrit en C, de même que les outils les plus importants qui seraient vraiment difficiles à réécrire. Java était (et même la version officielle) pour permettre à des personnes moins qualifiées de programmer dans les troupeaux tout en obtenant des résultats tangibles.
Morg.
1
@Morg Je trouve très étrange que vous sembliez vous concentrer uniquement sur les systèmes d'exploitation. Cela ne peut tout simplement pas être une bonne mesure, pour plusieurs raisons. Premièrement, les exigences des systèmes d’exploitation sont fondamentalement différentes de celles de la plupart des autres logiciels. Deuxièmement, vous appliquez le principe du pouce de Panda (qui veut réécrire un système d’exploitation complet dans une autre langue, qui veut écrire son propre système d’exploitation s’il existe des alternatives efficaces et même gratuites?) et un troisième logiciel utilise les fonctionnalités du système d'exploitation. Il n'est donc pas nécessaire d'écrire un pilote de disque, un gestionnaire de tâches, etc. Si vous ne pouvez pas fournir de meilleurs arguments (qui ne sont pas entièrement basés sur des systèmes d'exploitation), vous parlez comme un haineux.
Landei
5

Bien qu'un programme Java entièrement optimisé l'emporte rarement sur un programme C ++ complètement optimisé, des différences comme la gestion de la mémoire peuvent rendre beaucoup d'algorithmes implémentés idiomatiquement en Java plus rapidement que les mêmes algorithmes implémentés en C ++.

Comme @Jerry Coffin l'a souligné, il existe de nombreux cas dans lesquels de simples modifications peuvent rendre le code beaucoup plus rapide, mais il faut souvent trop de peaufiner les modifications impures dans une langue ou dans une autre pour que l'amélioration des performances en vaille la peine. C'est probablement ce que vous verriez dans un bon benchmark montrant que Java se comporte mieux que le C ++.

De plus, bien que généralement pas si significatif que cela, il existe certaines optimisations de performances qu'un langage JIT comme Java peut faire que C ++ ne peut pas. Le runtime Java peut inclure des améliorations après la compilation du code, ce qui signifie que le JIT peut potentiellement produire un code optimisé pour tirer parti des nouvelles fonctionnalités (ou du moins différentes) de la CPU. Pour cette raison, un binaire Java de 10 ans peut potentiellement surperformer un binaire C ++ de 10 ans.

Enfin, la sécurité de type complète dans son ensemble peut, dans de très rares cas, offrir des améliorations de performances extrêmes. Singularity , un système d'exploitation expérimental écrit presque entièrement dans un langage basé sur C #, offre une communication interprocessus et des tâches multitâches beaucoup plus rapides, du fait qu'il n'est pas nécessaire de définir des limites de processus matériels ni de changer de contexte coûteux.

Rei Miyasaka
la source
5

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 deux timings différents. 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. Évidemment, cela signifiait que vous pouviez optimiser la logique en fonction de la connaissance du cas le plus courant (sous réserve de la contrainte que ce que nous "savons" ne soit pas toujours 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 si la charge de travail change. Le code compilé statiquement ne peut pas faire cela. C'est ainsi que Java peut parfois surpasser les codes assembleur / C / C ++ accordés à la main.

Source: http://www.coderanch.com/t/547458/Performance/java/Ahead-Time-vs-Just- time

Thiago Negri
la source
3
Et encore une fois, c'est faux / incomplet. Les compilateurs statiques avec optimisation guidée par profil peuvent le reconnaître.
Konrad Rudolph
2
Konrad, les compilateurs statiques peuvent inverser la logique en fonction de la charge de travail actuelle? Si je comprends bien, les compilateurs statiques génèrent du code une fois et il reste le même pour toujours.
Thiago Negri le
2
Charge de travail actuelle, non. Mais charge de travail typique . L'optimisation guidée par profil analyse le fonctionnement de votre programme sous une charge typique et optimise les points chauds en conséquence, comme le fait le JET HotSpot.
Konrad Rudolph
4

En effet, la dernière étape de la génération du code machine se déroule de manière transparente dans 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 devez tenir compte du fait que les machines JVM modernes passent beaucoup de temps à compiler le code d'octet à la volée en code machine natif pour le rendre aussi rapide que possible. Cela permet à la machine virtuelle Java de faire toutes sortes d’astuces de compilation qui peuvent être encore meilleures en connaissant les données de profilage du programme en cours d’exécution.

Une chose telle que l’insertion automatique d’un getter, de sorte qu’un retour rapide ne soit pas nécessaire pour obtenir une valeur, accélère les choses.

Cependant, ce qui a vraiment permis des programmes rapides, c'est de mieux nettoyer après. Le mécanisme de récupération de place en Java est plus rapide que le manuel sans malloc en C. De nombreuses implémentations modernes sans malloc utilisent un collecteur de mémoire en dessous.


la source
Notez que cette fonctionnalité intégrée rend le démarrage de la machine virtuelle Java plus grand et plus lent jusqu'à ce que le meilleur code ait une chance de rattraper son retard.
1
"De nombreuses implémentations modernes sans malloc utilisent un collecteur de déchets en dessous." Vraiment? J'aimerais en savoir plus; Avez-vous des références?
Sean McMillan le
Je vous remercie. J'essayais de trouver un moyen de dire que la machine virtuelle Java ne contenait plus simplement un compilateur "juste à temps" compilant en code exécutable, mais un compilateur de points chauds qui profilait le code en cours d'exécution et en optimisait davantage. Un compilateur unique, comme le C ++, a du mal à trouver le résultat.
Highland Mark
@SeanMcMillan, j'ai vu une analyse il y a quelque temps sur les performances des implémentations sans malloc, où il était mentionné que le plus rapide utilisait un ramasse-miettes dessous. Je ne me souviens plus où je l'ai lu.
Était-ce le GC conservateur BDW?
Demi
4

Réponse courte - ce n'est pas. 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 ++. C'est assez rapide pour la plupart des tâches pour lesquelles vous n'avez pas du tout besoin de penser à l'optimisation. Comme les formulaires et le traitement SQL, mais c'est là que tout se termine.

Pour les tests de performance ou les petites applications écrites par des développeurs incompétents, le résultat final sera que Java / .NET sera probablement proche et peut-être même plus rapide.

En réalité, des choses simples comme l'allocation de mémoire sur une pile ou simplement l'utilisation de memzones vont tout simplement tuer le Java / .NET sur le champ.

Le monde ramassé des ordures utilise une sorte de memzone avec toute la comptabilité. Ajoutez de la memzone à C et C sera plus rapide sur place. Surtout pour les benchmarks "code haute performance" Java vs C, 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 un nouveau placement), elles se traduisent en sub esp, 0xffune instruction x86 unique. Battez-le avec Java - vous ne pouvez pas ...

La plupart du temps, je vois ces bancs où la comparaison de Java avec C ++ est comparée. Stratégies d’allocation de mémoire incorrectes, conteneurs à croissance automatique sans réserves, plusieurs nouveaux. Cela n’est même pas proche du code C / C ++ orienté performances.

Aussi une bonne lecture: https://days2011.scala-lang.org/sites/days2011/files/ws3-1-Hundt.pdf

Codeur
la source
1
Faux. Complètement faux. Vous ne pourrez pas surperformer un GC compactifiant avec votre gestion de mémoire manuelle. Compter naïvement les références ne sera jamais meilleur qu'un bon mark'n'sweep. Dès qu'il s'agit d'une gestion de mémoire compliquée, C ++ est un retard.
SK-logic le
3
@ SK-Logic: faux, avec les memzones ou l'allocation de pile, il n'y a PAS d'allocation de mémoire ou de désallocation TOUT. Vous avez un bloc de mémoire sur lequel vous écrivez. Marquez le bloc comme libre avec une variable volatile telle que la protection d'accès simultané InterlockedExchange, etc., et le prochain thread videra simplement ses données dans un bloc préalloué sans passer par le système d'exploitation pour la mémoire, s'il le voit libre. Avec la pile, c'est encore plus facile, à la seule exception que vous ne pouvez pas vider 50 Mo de la pile. Et cette vie d'objet est seulement à l'intérieur {}.
Codeur
2
@ SK-logic: Les compilateurs sont corrects en premier lieu, performances ensuite. Les moteurs de recherche, les moteurs de base de données, les systèmes de trading en temps réel et les jeux sont ce que je considère comme des performances critiques. Et la plupart de ceux-ci s'appuient sur des structures plates. Et de toute façon, les compilateurs sont principalement écrits en C / C ++. Avec des allocateurs personnalisés, je suppose. Là encore, je ne vois aucun problème à utiliser des éléments tree ou list sur rammap. Vous utilisez simplement le placement nouveau. Il n'y a pas beaucoup de complexité dans cela.
Codeur
3
@ SK-logic: Ce n'est pas beaucoup plus rapide, toutes les applications .NET / Java que j'ai vues se sont toujours avérées plus lentes et un vrai cochon. Chaque réécriture d'une application gérée dans le code SANE C / C ++ donnait une application plus propre et plus légère. Les applications gérées sont toujours lourdes. Voir VS2010 vs 2008. Même structure de données, mais VS2010 est un HOG. Les applications C / C ++ correctement écrites démarrent généralement en quelques millisecondes et ne restent pas bloquées sur les écrans de démarrage, tout en consommant beaucoup moins de mémoire. Le seul inconvénient est que vous devez coder avec le matériel, et beaucoup de gens ne savent pas comment il fonctionne. Ce ne sont que des points de repère où les chances ont réussi
Coder
2
vos preuves anecdotiques ne comptent pas. Des repères appropriés montrent la vraie différence. Il est particulièrement étrange que vous vous référiez aux applications graphiques, liées aux bibliothèques volumineuses et sous-optimales. Et, ce qui est plus important: en théorie, la limite de performance est beaucoup plus élevée pour un GC correctement implémenté.
SK-logic le
2

En réalité, ce ne sont que des assembleurs de haut niveau qui font exactement ce que le programmeur leur dit, exactement comme le programmeur le leur dit dans l'ordre exact indiqué par le programmeur. Les différences de performances sont si minimes qu'elles sont sans importance pour tous les objectifs pratiques.

La langue n'est pas "lente", le programmeur a écrit un programme lent. Il est très rare qu'un programme écrit de la meilleure façon dans une langue dépasse (à quelque fin pratique que ce soit) un programme faisant la même chose en utilisant le meilleur moyen d'une autre langue, à moins que l'auteur de l'étude ne veuille écraser son axe.

Évidemment, si vous allez dans un cas rare comme les systèmes embarqués en temps réel, le choix de la langue peut faire la différence, mais à quelle fréquence est-ce le cas? et de ces cas, combien de fois le bon choix n'est-il pas aveuglément évident?

Mattnz
la source
2
En théorie, une machine virtuelle JITting "idéale" doit surpasser le code compilé de manière statique, en ajustant ses optimisations aux informations de profilage collectées de manière dynamique. En pratique, les compilateurs JIT ne sont pas encore intelligents, mais ils sont au moins capables de produire un code de qualité similaire à celle de leurs pairs statiques plus gros et plus lents.
SK-logic
2

Voir les liens suivants ... Mais comment est-ce possible? Cela me dépasse de penser que le bytecode interprété pourrait être plus rapide qu'un langage compilé.

  1. Ces articles de blog fournissent-ils des preuves fiables?
  2. Ces articles de blog fournissent-ils des preuves définitives?
  3. Est-ce que ces articles de blog fournissent même des preuves sur le "pseudo-code interprété"?

Keith Lea vous dit qu'il y a des "imperfections évidentes" mais ne fait rien à propos de ces "imperfections évidentes". En 2005, ces anciennes tâches avaient été abandonnées et remplacées par les tâches figurant maintenant dans le jeu de test de performance .

Keith Lea vous dit qu'il "a pris le code de référence pour C ++ et Java de Great Computer Language Shootout, désormais obsolète, et qu'il a effectué les tests", mais il n'affiche les mesures que pour 14 des 25 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 avait déclaré: "J'en ai marre d'entendre les gens dire que Java était lent, quand je sais que c'est assez rapide ...", ce qui suggère à l'époque, il essayait de prouver quelque chose.

Christian Felde vous dit "Je n'ai pas créé le code, j'ai simplement relancé les tests." en tant que -si cela le dégage 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 25 programmes, même minuscules, fournissent-elles des preuves définitives?

Ces mesures concernent les programmes exécutés en "mode mixte". Java n'est pas interprété par Java - "N'oubliez pas le fonctionnement de HotSpot." Vous pouvez facilement savoir dans quelle mesure Java exécute le "code octet interprété", car vous pouvez forcer Java à n'interpréter que le code temporel - il suffit simplement que certains programmes Java s'exécutent avec et sans l'option -Xint.

Igouy
la source
-1

Cela m'amuse à quel point cette notion étrange de "code codé interprété" est omniprésente. Avez-vous déjà entendu parler de la compilation JIT? Votre argument ne peut pas être appliqué à Java.

Mais, mis à part JVM, il peut arriver qu'un code à thread direct ou même une interprétation triviale de bytecode puisse facilement surpasser un code natif fortement optimisé. L'explication est assez simple: le bytecode peut être assez compact et conviendra à votre cache minuscule lorsqu'une version de code natif du même algorithme aura plusieurs échecs de cache pour une seule itération.

SK-logic
la source
Le caractère omniprésent de l’interprétation est peut-être dû aux personnes connaissant leur informatique. La machine virtuelle java est une machine qui accepte le bytecode java et l'exécute sur une machine / non / capable d'exécuter le bytecode java de manière native, sans être capable d'écrire un programme natif équivalent sur le plan fonctionnel. Ergo c'est un interprète. Vous pouvez donner à ses techniques de mise en cache le nom de votre choix, JIT ou autre, mais il s'agit d'un interpréteur au sens de la définition d'interprète.
thiton
@thiton, il est probable que votre propre CS-fu soit un peu faible. JVM ne fait aucune sorte d'interprétation (pour les points chauds) - en tant que personne qui ose mentionner CS, vous devez savoir ce que cela signifie et comment une sémantique opérationnelle d'une interprétation est différente de l'exécution d'un code natif. Mais, probablement, vous ne connaissez tout simplement pas suffisamment CS pour distinguer la compilation de l'interprétation.
SK-logic
2
Umm, mais le bytecode, pour être exécuté, doit être converti en code natif - vous ne pouvez pas alimenter le bytecode Java dans la CPU. Donc, l'argument de taille n'est pas valide.
quant_dev
@ quant_dev, bien sûr - j'ai dit que cette affaire n'avait aucun lien avec la JVM. Vous aurez besoin d'un moteur de code-octet beaucoup plus simple pour que cet effet fonctionne.
SK-logic
-1

JIT, GC et ainsi de suite, C ++ peut être très, très facilement rendu beaucoup plus lent que Java. Cela n'apparaîtra pas dans les tests, mais la même application écrite par un développeur Java et un développeur C ++ peut être beaucoup plus rapide en Java.

  • Surcharge de l'opérateur. Tous les opérateurs simples comme "+" ou "=" peuvent appeler des centaines de lignes de code pour effectuer des contrôles de sécurité, des opérations de disque, la journalisation, le suivi et le profilage. Et ils sont si faciles à utiliser qu’une fois que vous surchargez les opérateurs, vous les utilisez naturellement et copieusement sans vous soucier de la façon dont l’utilisation s’enrichit.
  • Modèles. Ceux-ci n'affectent pas la vitesse autant que la mémoire. L'utilisation imprudente de modèles entraînera la génération de millions de lignes de code (alternatives pour le modèle de base) sans que vous ne les remarquiez jamais. Mais ensuite, les temps de chargement binaires, l'utilisation de la mémoire, l'utilisation de l'échange - tout cela agit également contre les tests de performance. Et l'utilisation de la RAM passe par le toit.

En ce qui concerne les modèles d'héritage avancés, ils sont à peu près similaires - C ++ en a que Java ne possède pas et vice-versa, mais tous introduisent également une surcharge significative et similaire. Donc, pas d'avantage spécial C ++ dans la programmation lourde d'objet.

Encore une mise en garde: le CPG peut être plus rapide ou plus lent que la gestion manuelle des allocations. Si vous affectez un grand nombre de petits objets, dans l’environnement du GC, une partie de la mémoire est généralement allouée et une partie de celle-ci est envoyée selon les besoins pour les nouveaux objets. En mode géré - chaque objet = une allocation distincte prend un temps considérable. OTOH, si vous malloc () beaucoup de mémoire à la fois et que vous assignez ensuite simplement des morceaux de celle-ci à vos objets manuellement, ou utilisez quelques instances d'objets plus grandes, vous pouvez arriver beaucoup plus rapidement.

SF.
la source
4
Je ne suis pas d'accord avec les deux points. Que vous utilisiez des opérateurs ou des méthodes est sans importance. Vous dites qu'ils vont proliférer. Nonsense - pas plus que ne le feraient les méthodes; vous devez les appeler ou non. Et les modèles ne génèrent pas plus de code que l'écriture manuelle de ce code particulier pour une utilisation multiple. Il peut y avoir plus de code qu'avec la répartition à l'exécution (fonctions virtuelles), mais cela aussi sera sans importance: la performance des lignes de cache d'instructions est primordiale dans les boucles serrées et ici, une seule instanciation de modèle sera utilisée, il n'y a donc pas de pression de mémoire pertinente. en raison de modèles.
Konrad Rudolph
La mentalité habituelle est que les méthodes sont chères, les opérateurs sont bon marché. Vous utilisez des méthodes lorsque vous devez, des opérateurs chaque fois que vous voulez gagner du temps et optimiser. Ce n'est pas une question technique, mais psychologique - ce n'est pas que les opérateurs sont "plus lourds", ils sont juste beaucoup plus faciles à utiliser et sont utilisés beaucoup plus fréquemment. (De plus, vous pouvez surcharger un opérateur communément utilisé dans un code préexistant, le rendant ainsi identique à l'original, plus un extra - et tout à coup, le code entier ralentira considérablement.
SF.
Je conteste que ce fait psychologique soit réel et que même s’il le soit, vous n’avez pas le choix : si vous avez besoin d’une fonctionnalité, vous l’utilisez, qu’elle soit encapsulée dans un opérateur ou une méthode. La psychologie n'est pas pertinente pour votre choix de la sémantique.
Konrad Rudolph le
1
Question piège. Je ne devinerais pas cela du tout, je mesurerais, agirais alors . Je n'ai jamais eu de problème avec cette tactique.
Konrad Rudolph le
1
@ KonradRudolph: Tout cela est vrai en termes de clarté et de facilité d'écriture du code, ce qui le rend sans bug et facile à gérer. Cependant, le point concernant l'efficacité de la mise en œuvre de l'algorithme reste valable: si vous êtes sur le point d'écrire obj.fetchFromDatabase("key")trois fois sur cinq lignes de code pour la même clé, vous réfléchirez à deux fois s'il faut extraire cette valeur une fois et la mettre en cache dans une variable locale. Si vous écrivez obj->"key"avec ->une surcharge surchargée pour agir comme une extraction de base de données, vous êtes beaucoup plus enclin à le laisser passer car le coût de l'opération n'est pas évident.
SF.
-2

D'une manière ou d'une autre Stack Exchange ne prend pas mes autres points de pile, donc ... pas de réponse malheureusement ...

Cependant, la deuxième réponse la plus votée est pleine de désinformation à mon humble avis.

Une application personnalisée par un expert en C / C ++ est TOUJOURS beaucoup plus rapide qu'une application Java, tout simplement. Il n'y a pas "aussi vite que Java ou plus rapide". c'est simplement plus rapide, précisément à cause des éléments que vous citez ci-dessous:

Compilation JIT : Vous attendez-vous vraiment à ce que l'optimiseur automatique ait les connaissances d'un programmeur expert et voie le lien entre l'intention et le code que le processeur va réellement exécuter? De plus, tout le JIT que vous faites est du temps perdu par rapport à un programme déjà compilé.

Garbage Collection est un outil qui désalloue simplement des ressources qu'un programmeur aurait oublié de désallouer, de manière plus ou moins efficace.

Évidemment, cela ne peut être que plus lent que ce qu'un expert (vous avez choisi le terme) que le programmeur C ferait pour gérer sa mémoire (et non, il n'y a pas de fuites dans les applications correctement écrites).

Une application C optimisée en performances connaît le processeur sur lequel elle est exécutée, elle a été compilée dessus, sinon, vous n’avez pas tout à fait pris en compte toutes les étapes en termes de performances, n’est-ce pas?

Statistiques d'exécution Cela dépasse mes connaissances, mais je soupçonne qu'un expert en C dispose de suffisamment de connaissances en prédiction de branche pour déjouer l'optimisation automatisée -

De très bonnes bibliothèques De nombreuses fonctions peu optimisées sont facilement disponibles dans les bibliothèques en Java. Il en va de même dans tous les langages. Cependant, les bibliothèques les plus optimisées sont écrites en C, en particulier pour le calcul.

La JVM est une couche d'abstraction, qui implique à la fois de bonnes choses, dont beaucoup sont au-dessus, et implique également que la solution globale est plus lente par conception.

Global:

Java ne peut jamais atteindre la vitesse de C / C ++ en raison de la manière dont il fonctionne dans une machine virtuelle avec beaucoup de protection, de fonctionnalités et d’outils.

Le logiciel optimisé, que ce soit pour l'informatique ou les jeux, est clairement défini par C ++, et il est courant de voir les implémentations C ++ remporter des concours de codage à un point tel que les meilleures implémentations Java ne peuvent être vues que sur la deuxième page.

En pratique, C ++ n'est pas un jouet et ne vous laissera pas faire avec beaucoup d'erreurs que la plupart des langages modernes peuvent gérer. Cependant, en étant plus simple et moins sûr, il est intrinsèquement plus rapide.

Et pour conclure, je voudrais dire que la plupart des gens ne donnent pas deux centimes à ce sujet, qu’en fin de compte, l’optimisation est un sport réservé à très peu de développeurs chanceux et que, sauf dans les cas où la performance est vraiment une préoccupation (Par exemple, si multiplier le matériel par 10 ne vous aidera pas - ou ne représentera que quelques millions au minimum), la plupart des gestionnaires préféreront une application non optimisée et une tonne de matériel.

Morg.
la source
Encore. Le ramassage des ordures n'est pas seulement un "outil de désallocation". GC peut compacter vos structures. GC peut gérer vos références faibles et vous aider à équilibrer votre mise en cache de cette manière. Plusieurs étages de GC rendent une allocation de tas beaucoup moins chère que votre encombrant, lent newou malloc(). Cela peut être beaucoup plus rapide en général que n'importe quelle gestion de mémoire manuelle, car vous ne seriez pas en mesure de déplacer des objets manuellement. Donc, tout votre raisonnement est clairement faux et partial. Votre connaissance des algorithmes GC et des méthodes d'optimisation JIT est trop limitée.
SK-logic
4
Cette réponse est pleine d'idées fausses sur ce que les optimiseurs modernes peuvent faire. Le code optimisé à la main n'a aucune chance contre cela. Mais alors, C ++ a également un compilateur d'optimisation.
Konrad Rudolph
Merci pour votre commentaire SK-logic, mais comme vous le dites, le GC peut être beaucoup plus rapide en général, nous parlons de ce qui sera le plus rapide dans un cas particulier, et il semble bien que la plupart des gens s'accordent pour dire que tout ce que le GC peut faire faire un programmeur peut faire, et même mieux. Bien sûr, vous pouvez déplacer manuellement des objets lorsque vous avez un accès direct à la mémoire .. lol. Ma connaissance des composants internes de la machine virtuelle Java est certainement limitée et je m'attends à ce que Java-Head me montre la lumière et ne me dise rien au hasard que le GC puisse faire des tâches que l'on ne peut pas faire manuellement (lol ... même le GC doit utilisez les instructions de la CPU;)).
Morg.
Konrad, je suis d'accord pour dire que je sous-estime largement les optimiseurs modernes ... Cependant, je trouve intéressant que le code optimisé à la main soit considéré comme inférieur au code optimisé automatiquement. Qu'attendez-vous précisément du compilateur pour voir qu'un humain ne peut pas?
Morg.
1
Droite . continue à pousser -1, cela ne changera pas le fait que C ++ est plus rapide que Java. Je ne connais peut-être pas beaucoup de choses sur les compilateurs modernes, mais cela ne change rien au point principal, qui est correct et contredit la réponse la plus votée ici. Pourquoi le C ++ serait-il une priorité pour nVidia sur leurs GPU pour HPC? Pourquoi tous les jeux seraient-ils écrits en C ++, sinon chaque moteur de base de données serait écrit en C?
Morg.
-4

J'ai vu au moins deux impressionnants mmo réalisés en Java. Dire que ce n'est pas assez rapide pour les jeux est un abus de langage. Si les concepteurs de jeux préfèrent le C ++ à d’autres langages, cela signifie simplement que ces programmeurs n’ont jamais vraiment abordé d’autres paradigmes / langages de programmation. N'importe quel langage aussi avancé que C / C ++ ou même Java peut produire du code qui pourrait techniquement répondre ou vaincre l'argument speed. Tout cela est dit et bien dit, ce que les programmeurs savent, avec quelles équipes travaillent le plus et surtout pourquoi ils utilisent ces outils. Puisque nous abordons l’aspect développement de jeux de la programmation, il doit y avoir plus d’arguments. Tout simplement le ' C’est une question d’argent et de temps pour une entreprise qui utilise des outils qui répondent aux critères d’AQ et dans le monde réel n’a aucune influence sur xx raisons qui poussent à choisir C ++ au lieu de Java ou de tout autre langage. C'est juste une décision de production en masse. Au niveau le plus élémentaire des algorithmes informatiques, nous ne jouons qu'avec des zéros et des uns, l'argument vitesse est l'un des arguments les plus stupides jamais appliqués au jeu. Si vous voulez des gains de vitesse aussi importants, supprimez complètement les langages de programmation et travaillez avec un assemblage qui constitue de loin le meilleur avantage.

Meh
la source
2
Ce mur de texte ne semble pas ajouter quoi que ce soit qui n’a pas déjà été indiqué dans les autres réponses. S'il vous plaît modifier votre réponse à être plus lisible, et s'il vous plaît assurez - vous que vos adresses de réponse points non soulevés par l'autre réponse. Sinon, veuillez supprimer votre réponse car elle n’ajoute que du bruit à ce stade.