web-dev-qa-db-fra.com

Qu'est-ce qui étaye l'affirmation selon laquelle C ++ peut être plus rapide qu'une JVM ou CLR avec JIT?

Un thème récurrent sur SE que j'ai remarqué dans de nombreuses questions est l'argument constant que C++ est plus rapide et/ou plus efficace que les langages de niveau supérieur comme Java. Le contre-argument est que la JVM ou CLR moderne peut être tout aussi efficace grâce au JIT et ainsi de suite pour un nombre croissant de tâches et que le C++ n'est de plus en plus efficace que si vous savez ce que vous faites et pourquoi le faire certaines choses mériteront une augmentation des performances. C'est évident et c'est parfaitement logique.

J'aimerais connaître une explication de base (s'il y a une telle chose ...) quant à pourquoi et comment certaines tâches sont plus rapides en C++ que la JVM ou CLR ? Est-ce simplement parce que C++ est compilé en code machine alors que la JVM ou le CLR ont toujours la surcharge de traitement de la compilation JIT au moment de l'exécution?

Lorsque j'essaie de rechercher le sujet, tout ce que je trouve, ce sont les mêmes arguments que j'ai décrits ci-dessus sans aucune information détaillée quant à la compréhension exacte de la façon dont C++ peut être utilisé pour le calcul haute performance.

120
Anonymous

C'est une question de mémoire (pas de JIT). L'avantage JIT par rapport à C est principalement limité à l'optimisation des appels virtuels ou non virtuels via l'inline, ce que le CPU BTB travaille déjà dur à faire.

Dans les machines modernes, l'accès à RAM est vraiment lent (par rapport à tout ce que fait le CPU), ce qui signifie que les applications qui utilisent les caches autant que possible (ce qui est plus facile lorsque moins de mémoire est utilisée) peuvent être jusqu'à cent fois plus rapides que ceux qui ne le font pas. Et il existe de nombreuses façons dont Java utilise plus de mémoire que C++ et rend plus difficile l'écriture d'applications qui exploitent pleinement le cache:

  • Il y a une surcharge de mémoire d'au moins 8 octets pour chaque objet, et l'utilisation d'objets au lieu de primitives est requise ou préférée à de nombreux endroits (à savoir les collections standard).
  • Les chaînes se composent de deux objets et ont un surcharge de 38 octets
  • UTF-16 est utilisé en interne, ce qui signifie que chaque caractère ASCII nécessite deux octets au lieu d'un (la JVM Oracle a récemment introduit une optimisation pour éviter cela pour ASCII pur) = chaînes).
  • Il n'y a pas de type de référence agrégé (c'est-à-dire des structures), et à son tour, il n'y a pas de tableaux de types de référence agrégés. Un Java, ou tableau d'objets Java, a une très faible localité de cache L1/L2 par rapport aux structures C et aux tableaux).
  • Les génériques Java utilisent l'effacement de type, qui a une mauvaise localisation de cache par rapport à l'instanciation de type.
  • L'allocation d'objet est opaque et doit être effectuée séparément pour chaque objet, il est donc impossible pour une application de disposer délibérément ses données d'une manière compatible avec le cache et de les traiter toujours comme des données structurées.

Quelques autres facteurs liés à la mémoire mais pas au cache:

  • Il n'y a pas d'allocation de pile, donc toutes les données non primitives avec lesquelles vous travaillez doivent être sur le tas et passer par la récupération de place (certains JIT récents font l'allocation de pile en arrière-plan dans certains cas).
  • Comme il n'y a pas de types de référence agrégés, il n'y a pas de passage de pile de types de référence agrégés. (Pensez à passer efficacement des arguments vectoriels)
  • La collecte des ordures peut endommager le contenu du cache L1/L2, et les arrêts du monde GC interrompent l'interactivité.
  • La conversion entre les types de données nécessite toujours une copie; vous ne pouvez pas prendre un pointeur sur un tas d'octets que vous avez obtenu d'un socket et les interpréter comme un flottant.

Certaines de ces choses sont des compromis (ne pas avoir à faire de gestion manuelle de la mémoire vaut la peine d'abandonner beaucoup de performances pour la plupart des gens), certains sont probablement le résultat d'essayer de garder Java simple, et certains sont des erreurs de conception (mais peut-être seulement avec du recul, à savoir UTF-16 était un encodage de longueur fixe lorsque Java a été créé) , ce qui rend la décision de le choisir beaucoup plus compréhensible).

Il convient de noter que bon nombre de ces compromis sont très différents pour Java/JVM et pour C #/CIL. Le .NET CIL a des structures de type référence, l'allocation/passage de pile, des tableaux de structures emballés et des génériques instanciés par type.

201
Michael Borgwardt

Est-ce simplement parce que C++ est compilé dans le code Assembly/machine alors que Java/C # a toujours le temps de traitement de la compilation JIT au moment de l'exécution?

En partie, mais en général, en supposant un compilateur JIT à la pointe de la technologie absolument fantastique, le bon code C++ encore a tendance à mieux fonctionner que Java code pour DEUX raisons principales:

1) Les modèles C++ offrent de meilleures fonctionnalités pour écrire du code à la fois générique ET efficace . Les modèles fournissent au programmeur C++ une abstraction très utile avec une surcharge d'exécution ZERO. (Les modèles sont essentiellement du type de canard à la compilation.) En revanche, le meilleur que vous obtenez avec Java sont essentiellement des fonctions virtuelles. Les fonctions virtuelles ont toujours une surcharge d'exécution, et ne peuvent généralement pas être en ligne.

En général, la plupart des langages, y compris Java, C # et même C, vous font choisir entre efficacité et généralité/abstraction. Les modèles C++ vous offrent les deux (au prix de temps de compilation plus longs).

2) Le fait que la norme C++ n'a pas grand-chose à dire sur la disposition binaire d'un programme C++ compilé donne aux compilateurs C++ beaucoup plus de latitude qu'un compilateur Java, permettant de meilleures optimisations (à parfois plus difficile à déboguer.) En fait, la nature même de la spécification de langage Java impose une pénalité de performances dans certains domaines. Par exemple, vous ne pouvez pas avoir un tableau contigu de Objets en Java. Vous ne pouvez avoir qu'un tableau contigu d'objet pointeurs (références), ce qui signifie que l'itération sur un tableau en Java entraîne toujours le coût de l'indirection Cependant, la sémantique des valeurs de C++ active les tableaux contigus. Une autre différence est le fait que C++ permet d'allouer des objets sur la pile, alors que Java ne le fait pas, ce qui signifie qu'en pratique, puisque la plupart Les programmes C++ ont tendance à allouer des objets sur la pile, le coût d'allocation est souvent proche de zéro.

Un domaine où C++ peut être en retard Java est toute situation où de nombreux petits objets doivent être alloués sur le tas. Dans ce cas, le système de récupération de place de Java entraînera probablement de meilleures performances que le standard new et delete en C++ car le GC Java permet la désallocation en masse. Mais encore une fois, un programmeur C++ peut compenser cela en utilisant un pool de mémoire ou un allocateur de dalles, alors que un programmeur Java n'a aucun recours face à un modèle d'allocation de mémoire pour lequel le runtime Java n'est pas optimisé).

Voir aussi cette excellente réponse pour plus d'informations sur ce sujet.

68
Charles Salvia

Ce que les autres réponses (6 jusqu'à présent) semblent avoir oublié de mentionner, mais ce que je considère très important pour y répondre, est l'une des philosophies de conception très basiques de C++, qui a été formulée et utilisée par Stroustrup dès le premier jour:

Vous ne payez pas pour ce que vous n'utilisez pas.

Il existe d'autres principes de conception sous-jacents importants qui ont grandement façonné le C++ (comme cela, vous ne devriez pas être forcé dans un paradigme spécifique), mais Vous ne payez pas pour ce que vous n'utilisez pas est juste là parmi les plus importants.


Dans son livre The Design and Evolution of C++ (habituellement dénommé [D&E]), Stroustrup décrit le besoin qu'il avait qui l'a poussé à concevoir C++ en premier lieu. Dans mes propres mots: pour sa thèse de doctorat (quelque chose à voir avec les simulations de réseaux, IIRC), il a implémenté un système dans SIMULA, qu'il a beaucoup aimé, car le langage était très bon pour lui permettre d'exprimer ses pensées directement en code. Cependant, le programme résultant a fonctionné beaucoup trop lentement, et pour obtenir un diplôme, il a réécrit la chose dans BCPL, un prédécesseur de C.Écriture du code dans BCPL qu'il décrit comme une douleur, mais le programme résultant a été assez rapide pour livrer résultats, ce qui lui a permis de terminer son doctorat.

Après cela, il voulait un langage qui permettrait de traduire les problèmes du monde réel en code aussi directement que possible, mais aussi de permettre au code d'être très efficace.
Dans cette optique, il a créé ce qui allait devenir C++.


Ainsi, le but cité ci-dessus n'est pas simplement l'un des principes de conception sous-jacents fondamentaux, il est très proche de la raison d'être pour C++. Et il peut être trouvé à peu près partout dans le langage: les fonctions ne sont que virtual lorsque vous le souhaitez (car l'appel de fonctions virtuelles s'accompagne d'une légère surcharge) les POD ne sont initialisés automatiquement que lorsque vous le demandez explicitement, sauf exceptions vous coûte des performances lorsque vous les lancez (alors que c'était un objectif de conception explicite de permettre la configuration/le nettoyage des stackframes très bon marché), aucun GC ne fonctionne chaque fois qu'il en a envie, etc.

C++ a explicitement choisi de ne pas vous offrir certaines commodités ("dois-je rendre cette méthode virtuelle ici?") En échange de performances ("non, je ne le fais pas, et maintenant le compilateur peut inline et optimiser le diable de l'ensemble! "), et sans surprise, cela a en effet entraîné des gains de performances par rapport aux langages plus pratiques.

46
sbi

Connaissez-vous le Google research paper sur ce sujet?

De la conclusion:

Nous constatons qu'en ce qui concerne les performances, C++ l'emporte largement. Cependant, cela a également exigé les efforts de réglage les plus étendus, dont beaucoup ont été effectués à un niveau de sophistication qui ne serait pas disponible pour le programmeur moyen.

Ceci est au moins partiellement une explication, dans le sens de "parce que les compilateurs C++ du monde réel produisent du code plus rapide que Java compilateurs par des mesures empiriques").

29
Doc Brown

Ce n'est pas un double de vos questions, mais la réponse acceptée répond à la plupart de vos questions: ne revue moderne de Java

Pour résumer:

Fondamentalement, la sémantique de Java dicte qu'il s'agit d'un langage plus lent que C++.

Donc, selon le langage avec lequel vous comparez C++, vous pouvez obtenir ou non la même réponse.

En C++ vous avez:

  • Capacité de faire de l'inline intelligent,
  • génération de code générique ayant une forte localité (modèles)
  • des données aussi petites et compactes que possible
  • occasions d'éviter les indirections
  • comportement mémoire prévisible
  • optimisations du compilateur possibles uniquement en raison de l'utilisation d'abstractions de haut niveau (modèles)

Ce sont les caractéristiques ou les effets secondaires de la définition du langage qui le rendent théoriquement plus efficace sur la mémoire et la vitesse que n'importe quel langage qui:

  • utiliser indirection massivement (langages "tout est une référence/un pointeur géré"): l'indirection signifie que le CPU doit sauter en mémoire pour obtenir les données nécessaires, augmentant les pannes de cache CPU, ce qui signifie ralentir le traitement - C utilise également les indirections a beaucoup même s'il peut avoir de petites données en C++;
  • générer des objets de grande taille dont les membres sont accessibles indirectement: c'est une conséquence d'avoir des références par défaut, les membres sont des pointeurs, donc lorsque vous obtenez un membre, vous risquez de ne pas obtenir de données proches du cœur de l'objet parent, ce qui déclenche à nouveau des échecs de cache.
  • utiliser un collecteur de garbarges: il rend simplement impossible la prévisibilité des performances (par conception).

L'intégration agressive C++ du compilateur réduit ou élimine un grand nombre d'indirections. La capacité de générer un petit ensemble de données compactes le rend convivial pour le cache si vous ne répartissez pas ces données dans toute la mémoire au lieu de les regrouper (les deux sont possibles, C++ vous laisse le choix). RAII rend le comportement de la mémoire C++ prévisible, éliminant de nombreux problèmes en cas de simulations en temps réel ou semi-réel, qui nécessitent une vitesse élevée. Les problèmes de localisation, en général, peuvent être résumés par ceci: plus le programme/les données sont petits, plus l'exécution est rapide. Le C++ offre diverses manières de vous assurer que vos données sont là où vous le souhaitez (dans un pool, un tableau ou autre) et qu'elles sont compactes.

Évidemment, il existe d'autres langages qui peuvent faire de même, mais ils sont juste moins populaires car ils ne fournissent pas autant d'outils d'abstraction que C++, ils sont donc moins utiles dans de nombreux cas.

23
Klaim

Il s'agit principalement de mémoire (comme l'a dit Michael Borgwardt) avec un peu d'inefficacité JIT ajoutée.

Une chose non mentionnée est le cache - pour utiliser pleinement le cache, vous devez disposer vos données de manière contiguë (c'est-à-dire toutes ensemble). Maintenant, avec un système GC, la mémoire est allouée sur le tas GC, ce qui est rapide, mais à mesure que la mémoire est utilisée, le GC se déclenche régulièrement et supprime les blocs qui ne sont plus utilisés, puis compacte le reste ensemble. Maintenant, mis à part la lenteur évidente à déplacer les blocs utilisés ensemble, cela signifie que les données que vous utilisez peuvent ne pas être collées ensemble. Si vous disposez d'un tableau de 1 000 éléments, à moins que vous ne les ayez tous alloués en même temps (puis mis à jour leur contenu plutôt que d'en supprimer et d'en créer de nouveaux - qui seront créés à la fin du tas), ceux-ci seront dispersés partout dans le tas, nécessitant ainsi plusieurs hits de mémoire pour les lire tous dans le cache CPU. Une application C/C++ allouera probablement la mémoire pour ces éléments, puis vous mettrez à jour les blocs avec les données. (ok, il existe des structures de données comme une liste qui se comportent plus comme les allocations de mémoire du GC, mais les gens savent qu'elles sont plus lentes que les vecteurs).

Vous pouvez voir cela en fonctionnement simplement en remplaçant tous les objets StringBuilder par String ... Stringbuilders fonctionne en pré-allouant la mémoire et en la remplissant, et c'est une astuce de performance connue pour les systèmes Java/.NET.

N'oubliez pas que le paradigme `` supprimer les anciennes et allouer de nouvelles copies '' est très utilisé en Java/C #, simplement parce que les gens sont informés que les allocations de mémoire sont très rapides en raison du GC, et donc que le modèle de mémoire dispersée est utilisé partout ( (sauf pour les constructeurs de chaînes, bien sûr), donc toutes vos bibliothèques ont tendance à gaspiller de la mémoire et à en utiliser beaucoup, dont aucune ne bénéficie de la contiguïté. Blâmez le battage médiatique autour de GC pour cela - ils vous ont dit que la mémoire était libre, lol.

Le GC lui-même est évidemment un autre succès - lorsqu'il s'exécute, non seulement il doit balayer le tas, mais il doit également libérer tous les blocs inutilisés, puis il doit exécuter tous les finaliseurs (bien que cela ait été fait séparément auparavant) la prochaine fois avec l'application arrêtée) (je ne sais pas si c'est toujours un tel succès, mais tous les documents que je lis disent n'utiliser des finaliseurs que si vraiment nécessaire) et ensuite il doit déplacer ces blocs en position pour que le tas soit compacté et mettre à jour la référence au nouvel emplacement du bloc. Comme vous pouvez le voir, c'est beaucoup de travail!

Les résultats Perf pour la mémoire C++ se résument aux allocations de mémoire - lorsque vous avez besoin d'un nouveau bloc, vous devez parcourir le tas à la recherche du prochain espace libre suffisamment grand, avec un tas fortement fragmenté, ce n'est pas aussi rapide qu'un GC. `` alloue simplement un autre bloc à la fin '' mais je pense que ce n'est pas aussi lent que tout le travail du compactage GC, et peut être atténué en utilisant plusieurs tas de blocs de taille fixe (autrement appelés pools de mémoire).

Il y a plus ... comme le chargement d'assemblages hors du GAC qui nécessite une vérification de sécurité, des chemins de sonde (allumez sxstrace et regardez simplement ce qu'il fait!) Et une autre ingénierie générale qui semble être beaucoup plus populaire avec Java/.net que C/C++.

7
gbjbaanb

"Est-ce simplement parce que C++ est compilé dans le code Assembly/machine alors que Java/C # a toujours le temps de traitement de la compilation JIT au moment de l'exécution?" Fondamentalement, oui!

Remarque rapide cependant, Java a plus de frais généraux que la simple compilation JIT. Par exemple, il vérifie beaucoup plus pour vous (c'est ainsi qu'il fait des choses comme ArrayIndexOutOfBoundsExceptions et NullPointerExceptions). Le ramasse-miettes est une autre surcharge importante.

Il y a une comparaison assez détaillée ici .

6
vaughandroid

Gardez à l'esprit que ce qui suit ne fait que comparer la différence entre la compilation native et JIT, et ne couvre pas les spécificités d'un langage ou d'un framework particulier. Il pourrait y avoir des raisons légitimes de choisir une plate-forme particulière au-delà de cela.

Lorsque nous affirmons que le code natif est plus rapide, nous parlons du cas d'utilisation typique du code compilé en mode natif par rapport au code compilé JIT, où l'utilisation typique d'une application compilée JIT doit être exécutée par l'utilisateur, avec des résultats immédiats (par exemple, pas d'attente sur le compilateur en premier). Dans ce cas, je ne pense pas que quiconque puisse prétendre avec franchise, que le code compilé JIT peut correspondre ou battre le code natif.

Supposons que nous ayons un programme écrit dans un langage X, et que nous pouvons le compiler avec un compilateur natif, et encore avec un compilateur JIT. Chaque flux de travail comporte les mêmes étapes impliquées, qui peuvent être généralisées comme (Code -> Représentation intermédiaire -> Code machine -> Exécution). La grande différence entre deux est les étapes qui sont vues par l'utilisateur et celles qui sont vues par le programmeur. Avec la compilation native, le programmeur voit tout sauf l'étape d'exécution, mais avec la solution JIT, la compilation en code machine est vue par l'utilisateur, en plus de l'exécution.

L'affirmation que A est plus rapide que B fait référence au temps nécessaire au programme pour s'exécuter, tel que vu par l'utilisateur. Si nous supposons que les deux morceaux de code fonctionnent de manière identique dans la phase d'exécution, nous devons supposer que le flux de travail JIT est plus lent pour l'utilisateur, car il doit également voir le temps T de la compilation en code machine, où T> 0. Donc , pour que le flux de travail JIT puisse exécuter la même chose que le flux de travail natif, pour l'utilisateur, nous devons réduire le temps d'exécution du code, de sorte que l'exécution + la compilation en code machine soient inférieures à la phase d'exécution uniquement. du flux de travail natif. Cela signifie que nous devons mieux optimiser le code dans la compilation JIT que dans la compilation native.

Cependant, cela est plutôt irréalisable, car pour effectuer les optimisations nécessaires pour accélérer l'exécution, nous devons passer plus de temps à l'étape de la compilation vers le code machine, et donc, chaque fois que nous économisons à la suite du code optimisé est réellement perdu, car nous l'ajoutons à la compilation. En d'autres termes, la "lenteur" d'une solution basée sur JIT n'est pas simplement due au temps supplémentaire pour la compilation JIT, mais le code produit par cette compilation fonctionne plus lentement qu'une solution native.

Je vais utiliser un exemple: inscrire l'allocation. Étant donné que l'accès à la mémoire est plusieurs milliers de fois plus lent que l'accès aux registres, nous souhaitons idéalement utiliser des registres dans la mesure du possible et avoir le moins d'accès à la mémoire possible, mais nous avons un nombre limité de registres et nous devons renverser l'état en mémoire lorsque nous en avons besoin. un registre. Si nous utilisons un algorithme d'allocation de registres qui prend 200 ms à calculer, et par conséquent nous économisons 2 ms de temps d'exécution - nous ne faisons pas le meilleur usage du temps pour un compilateur JIT. Des solutions comme l'algorithme de Chaitin, qui peut produire du code hautement optimisé, ne conviennent pas.

Le rôle du compilateur JIT est cependant de trouver le meilleur équilibre entre le temps de compilation et la qualité du code produit, avec un biais important sur le temps de compilation rapide, car vous ne voulez pas laisser l'utilisateur attendre. Les performances du code en cours d'exécution sont plus lentes dans le cas JIT, car le compilateur natif n'est pas (beaucoup) lié par le temps dans l'optimisation du code, il est donc libre d'utiliser les meilleurs algorithmes. La possibilité que la compilation + exécution globale pour un compilateur JIT puisse battre uniquement le temps d'exécution pour le code compilé en mode natif est effectivement 0.

Mais nos machines virtuelles ne se limitent pas à la compilation JIT. Ils utilisent des techniques de compilation en avance, de mise en cache, de remplacement à chaud et d'optimisations adaptatives. Modifions donc notre affirmation selon laquelle les performances sont ce que l'utilisateur voit et limitons-les au temps nécessaire à l'exécution du programme (supposons que nous avons compilé AOT). Nous pouvons effectivement rendre le code d'exécution équivalent au compilateur natif (ou peut-être mieux?). Une grande prétention pour les machines virtuelles est qu'elles peuvent être en mesure de produire un code de meilleure qualité qu'un compilateur natif, car elles ont accès à plus d'informations - celles du processus en cours, telles que la fréquence à laquelle une certaine fonction peut être exécutée. Le VM peut ensuite appliquer des optimisations adaptatives au code le plus essentiel via un échange à chaud.

Il y a cependant un problème avec cet argument - il suppose que l'optimisation guidée par le profil et similaire est quelque chose d'unique aux machines virtuelles, ce qui n'est pas vrai. Nous pouvons également l'appliquer à la compilation native - en compilant notre application avec le profilage activé, en enregistrant les informations, puis en recompilant l'application avec ce profil. Il convient également de souligner que l'échange de code à chaud n'est pas quelque chose que seul un compilateur JIT peut faire non plus, nous pouvons le faire pour le code natif - bien que les solutions basées sur JIT pour ce faire soient plus facilement disponibles et beaucoup plus faciles pour le développeur. La grande question est donc: un VM peut-il nous offrir des informations que la compilation native ne peut pas, ce qui peut augmenter les performances de notre code?

Je ne peux pas le voir moi-même. Nous pouvons appliquer la plupart des techniques d'un VM au code natif également - bien que le processus soit plus complexe. De même, nous pouvons appliquer toutes les optimisations d'un compilateur natif à un VM qui utilise la compilation AOT ou les optimisations adaptatives. La réalité est que la différence entre le code exécuté en mode natif et celui qui s'exécute dans un VM n'est pas aussi grande que nous l'avons été) à croire. Ils conduisent finalement au même résultat, mais ils adoptent une approche différente pour y arriver. Le VM utilise une approche itérative pour produire du code optimisé, où le compilateur natif l'attend dès le début (et peut être amélioré avec une approche itérative).

Un programmeur C++ pourrait affirmer qu'il a besoin des optimisations dès le départ, et ne devrait pas attendre un VM pour comprendre comment les faire, le cas échéant. C'est probablement un point valable avec notre technologie actuelle, car le niveau actuel d'optimisations dans nos machines virtuelles est inférieur à ce que les compilateurs natifs peuvent offrir - mais cela ne sera pas toujours le cas si les solutions AOT dans nos machines virtuelles s'améliorent, etc.

2
Mark H

Cet article est un résumé d'un ensemble d'articles de blog essayant de comparer la vitesse de c ++ par rapport à c # et les problèmes que vous devez surmonter dans les deux langues pour obtenir du code haute performance. Le résumé est "votre bibliothèque compte bien plus que tout, mais si vous êtes en c ++, vous pouvez surmonter cela". ou "les langues modernes ont de meilleures bibliothèques et obtiennent ainsi des résultats plus rapides avec un effort moindre" selon votre orientation philosophique.

0
Jeff Gates

Je pense que la vraie question ici n'est pas "qui est plus rapide?" mais "qui a le meilleur potentiel pour de meilleures performances?". Vu dans ces conditions, C++ l'emporte clairement - il est compilé en code natif, il n'y a pas de JITting, c'est un niveau d'abstraction inférieur, etc.

C'est loin d'être l'histoire complète.

Étant donné que C++ est compilé, toutes les optimisations du compilateur doivent être effectuées au moment de la compilation, et les optimisations du compilateur appropriées pour une machine peuvent être complètement erronées pour une autre. Il est également vrai que toute optimisation globale du compilateur peut et favorisera certains algorithmes ou modèles de code par rapport à d'autres.

D'un autre côté, un programme JITted sera optimisé au moment JIT, il peut donc tirer quelques astuces qu'un programme précompilé ne peut pas et peut faire des optimisations très spécifiques pour la machine sur laquelle il s'exécute réellement et le code qu'il exécute réellement. Une fois que vous avez dépassé les frais généraux initiaux du JIT, il peut dans certains cas être plus rapide.

Dans les deux cas, une implémentation sensible de l'algorithme et d'autres instances du programmeur n'étant pas stupide seront probablement des facteurs beaucoup plus importants, par exemple - par exemple, il est parfaitement possible d'écrire du code de chaîne complètement mort en C++ qui sera bloqué par même un langage de script interprété.

0
Maximus Minimus