Performances C ++ par rapport à Java / C #

119

Je crois comprendre que C / C ++ produit du code natif à exécuter sur une architecture de machine particulière. Inversement, des langages comme Java et C # s'exécutent sur une machine virtuelle qui fait abstraction de l'architecture native. Logiquement, il semblerait impossible pour Java ou C # d'égaler la vitesse de C ++ à cause de cette étape intermédiaire, mais on m'a dit que les derniers compilateurs ("hot spot") peuvent atteindre cette vitesse ou même la dépasser.

C'est peut-être plus une question de compilation qu'une question de langue, mais quelqu'un peut-il expliquer en anglais simple comment il est possible pour l'un de ces langages de machine virtuelle de fonctionner mieux qu'une langue native?

user23126
la source
Java et C # peuvent effectuer une optimisation en fonction de la manière dont l'application est réellement exécutée en utilisant le code tel qu'il est disponible au moment de l'exécution. par exemple, il peut insérer du code dans une bibliothèque partagée qui peut changer pendant que le programme est en cours d'exécution et être toujours correct.
Peter Lawrey
Quelques mesures réelles à vérifier avant de lire beaucoup de théorie très floconneuse dans ces réponses: shootout.alioth.debian.org/u32/…
Justicle

Réponses:

178

Généralement, C # et Java peuvent être tout aussi rapides ou plus rapides car le compilateur JIT - un compilateur qui compile votre IL la première fois qu'il est exécuté - peut effectuer des optimisations qu'un programme compilé C ++ ne peut pas car il peut interroger la machine. Il peut déterminer si la machine est Intel ou AMD; Pentium 4, Core Solo ou Core Duo; ou si prend en charge SSE4, etc.

Un programme C ++ doit être compilé au préalable généralement avec des optimisations mixtes afin qu'il fonctionne correctement sur toutes les machines, mais n'est pas optimisé autant qu'il pourrait l'être pour une seule configuration (processeur, jeu d'instructions, autre matériel).

De plus, certaines fonctionnalités du langage permettent au compilateur en C # et Java de faire des hypothèses sur votre code qui lui permettent d'optimiser certaines parties qui ne sont tout simplement pas sûres pour le compilateur C / C ++. Lorsque vous avez accès à des pointeurs, de nombreuses optimisations ne sont tout simplement pas sûres.

Java et C # peuvent également effectuer des allocations de tas plus efficacement que C ++ car la couche d'abstraction entre le garbage collector et votre code lui permet de faire toute sa compression de tas en une seule fois (opération assez coûteuse).

Maintenant, je ne peux pas parler pour Java sur ce point suivant, mais je sais que C # par exemple supprimera en fait les méthodes et les appels de méthode quand il sait que le corps de la méthode est vide. Et il utilisera ce type de logique dans tout votre code.

Donc, comme vous pouvez le voir, il y a de nombreuses raisons pour lesquelles certaines implémentations C # ou Java seront plus rapides.

Maintenant, tout cela dit, des optimisations spécifiques peuvent être faites en C ++ qui épateront tout ce que vous pourriez faire avec C #, en particulier dans le domaine graphique et à chaque fois que vous êtes proche du matériel. Les pointeurs font des merveilles ici.

Donc, selon ce que vous écrivez, j'irais avec l'un ou l'autre. Mais si vous écrivez quelque chose qui ne dépend pas du matériel (pilote, jeu vidéo, etc.), je ne m'inquiéterais pas des performances de C # (encore une fois, je ne peux pas parler de Java). Ça ira très bien.

Du côté Java, @Swati fait remarquer un bon article:

https://www.ibm.com/developerworks/library/j-jtp09275

Orion Adrian
la source
Votre raisonnement est faux - les programmes C ++ sont construits pour leur architecture cible, ils n'ont pas besoin de basculer au moment de l'exécution.
Justicle
3
@Justicle Le meilleur que votre compilateur C ++ offrira pour différentes architectures est généralement x86, x64, ARM et ainsi de suite. Vous pouvez maintenant lui dire d'utiliser des fonctionnalités spécifiques (par exemple SSE2) et si vous avez de la chance, il générera même du code de sauvegarde si cette fonctionnalité n'est pas disponible, mais c'est à peu près aussi précis que possible. Certainement pas de spécialisation en fonction de la taille du cache et ainsi de suite.
Voo le
4
Voir shootout.alioth.debian.org/u32/ ... pour des exemples de cette théorie qui ne se produit pas .
Justicle
1
Pour être honnête, c'est l'une des pires réponses. C'est tellement infondé que je pourrais simplement l'inverser. Trop de généralisation, trop d'inconnaissance (l'optimisation des fonctions vides n'est en réalité que la pointe de l'iceberg). Un compilateur C ++ de luxe a: Time. Autre luxe: aucune vérification n'est imposée. Mais trouvez-en plus sur stackoverflow.com/questions/145110/c-performance-vs-java-c/… .
Sebastian Mach
1
@OrionAdrian ok, la boucle est bouclée maintenant ... Voir shootout.alioth.debian.org/u32/… pour des exemples de cette théorie qui ne se produit pas. En d'autres termes, montrez-nous que votre théorie peut être prouvée correcte avant de faire de vagues déclarations spéculatives.
Justicle
197

Compilateur JIT vs statique

Comme déjà dit dans les articles précédents, JIT peut compiler IL / bytecode en code natif au moment de l'exécution. Le coût de cela a été mentionné, mais pas à sa conclusion:

JIT a un énorme problème est qu'il ne peut pas tout compiler: la compilation JIT prend du temps, donc le JIT ne compilera que certaines parties du code, alors qu'un compilateur statique produira un binaire natif complet: pour certains types de programmes, le Le compilateur surpassera facilement le JIT.

Bien sûr, C # (ou Java, ou VB) est généralement plus rapide à produire une solution viable et robuste que C ++ (ne serait-ce que parce que C ++ a une sémantique complexe, et que la bibliothèque standard C ++, bien qu'intéressante et puissante, est assez médiocre par rapport à la version complète portée de la bibliothèque standard de .NET ou Java), donc généralement, la différence entre C ++ et .NET ou Java JIT ne sera pas visible pour la plupart des utilisateurs, et pour les binaires critiques, eh bien, vous pouvez toujours appeler le traitement C ++ depuis C # ou Java (même si ce type d'appels natifs peut être assez coûteux en soi) ...

Métaprogrammation C ++

Notez que généralement, vous comparez le code d'exécution C ++ avec son équivalent en C # ou Java. Mais C ++ a une fonctionnalité qui peut surpasser Java / C # dès la sortie de la boîte, à savoir la métaprogrammation des modèles: le traitement du code sera effectué au moment de la compilation (augmentant ainsi considérablement le temps de compilation), ce qui aboutira à zéro (ou presque) à l'exécution.

J'ai encore vu un effet réel sur cela (je n'ai joué qu'avec des concepts, mais à ce moment-là, la différence était en secondes d'exécution pour JIT et zéro pour C ++), mais cela vaut la peine d'être mentionné, à côté du fait que la métaprogrammation du modèle n'est pas banal...

Edit 2011-06-10: En C ++, jouer avec les types se fait au moment de la compilation, c'est-à-dire produire du code générique qui appelle du code non générique (par exemple un analyseur générique de la chaîne au type T, appelant l'API de bibliothèque standard pour les types T qu'il reconnaît, et rendre l'analyseur facilement extensible par son utilisateur) est très simple et très efficace, alors que l'équivalent en Java ou C # est au mieux pénible à écrire, et sera toujours plus lent et résolu à l'exécution même lorsque les types sont connus au moment de la compilation, ce qui signifie que votre seul espoir est que le JIT intègre le tout.

...

Edit 2011-09-20: L'équipe derrière Blitz ++ ( page d'accueil , Wikipédia ) est allée dans ce sens, et apparemment, leur objectif est d'atteindre les performances de FORTRAN sur les calculs scientifiques en passant autant que possible de l'exécution à la compilation, via la métaprogrammation de modèles C ++ . Ainsi , le « Je n'ai pas encore si voir un effet réel devie sur cette » partie I écrit ci - dessus semble - t exist dans la vie réelle.

Utilisation de la mémoire native C ++

C ++ a une utilisation de la mémoire différente de Java / C #, et donc, présente différents avantages / défauts.

Quelle que soit l'optimisation JIT, rien n'ira aussi vite que l'accès direct du pointeur à la mémoire (ignorons un instant les caches du processeur, etc.). Donc, si vous avez des données contiguës en mémoire, y accéder via des pointeurs C ++ (c'est-à-dire des pointeurs C ... Donnons à Caesar son dû) sera plus rapide qu'en Java / C #. Et C ++ a RAII, ce qui rend beaucoup de traitement beaucoup plus facile qu'en C # ou même en Java. C ++ n'a pas besoin usingde définir la portée de l'existence de ses objets. Et C ++ n'a pas de finallyclause. Ce n'est pas une erreur.

:-)

Et malgré les structures de type primitif C #, les objets C ++ "sur la pile" ne coûteront rien en allocation et en destruction, et n'auront pas besoin de GC pour travailler dans un thread indépendant pour faire le nettoyage.

En ce qui concerne la fragmentation de la mémoire, les allocateurs de mémoire en 2008 ne sont pas les anciens allocateurs de mémoire de 1980 qui sont généralement comparés à un GC: l'allocation C ++ ne peut pas être déplacée en mémoire, c'est vrai, mais alors, comme sur un système de fichiers Linux: Qui a besoin d'un disque dur défragmenter lorsque la fragmentation ne se produit pas? L'utilisation du bon allocateur pour la bonne tâche devrait faire partie de la boîte à outils du développeur C ++. Maintenant, écrire des allocateurs n'est pas facile, et puis, la plupart d'entre nous ont de meilleures choses à faire, et pour la plupart des utilisateurs, RAII ou GC est plus que suffisant.

Edit 2011-10-04: Pour des exemples d'allocateurs efficaces: Sur les plates-formes Windows, depuis Vista, le tas de faible fragmentation est activé par défaut. Pour les versions précédentes, le LFH peut être activé en appelant la fonction WinAPI HeapSetInformation ). Sur d'autres systèmes d'exploitation, des allocateurs alternatifs sont fournis (voirhttps://secure.wikimedia.org/wikipedia/en/wiki/Malloc pour une liste)

Maintenant, le modèle de mémoire devient un peu plus compliqué avec la montée en puissance de la technologie multicœur et multithreading. Dans ce domaine, je suppose que .NET a l'avantage, et Java, m'a-t-on dit, a tenu le dessus. Il est facile pour certains hackers "sur le métal nu" de louer son code "près de la machine". Mais maintenant, il est bien plus difficile de produire un meilleur assemblage à la main que de laisser le compilateur faire son travail. Pour C ++, le compilateur est généralement devenu meilleur que le hacker depuis une décennie. Pour C # et Java, c'est encore plus facile.

Pourtant, le nouveau standard C ++ 0x imposera un modèle de mémoire simple aux compilateurs C ++, ce qui standardisera (et simplifiera ainsi) le code multiprocesseur / parallèle / threading efficace en C ++, et rendra les optimisations plus faciles et plus sûres pour les compilateurs. Mais alors, nous verrons dans quelques années si ses promesses sont tenues.

C ++ / CLI et C # / VB.NET

Remarque: Dans cette section, je parle de C ++ / CLI, c'est-à-dire du C ++ hébergé par .NET, pas du C ++ natif.

La semaine dernière, j'ai suivi une formation sur l'optimisation .NET et j'ai découvert que le compilateur statique est de toute façon très important. Aussi important que JIT.

Le même code compilé en C ++ / CLI (ou son ancêtre, Managed C ++) pourrait être fois plus rapide que le même code produit en C # (ou VB.NET, dont le compilateur produit le même IL que C #).

Parce que le compilateur statique C ++ était bien meilleur pour produire du code déjà optimisé que celui de C #.

Par exemple, l'intégration de fonctions dans .NET est limitée aux fonctions dont le bytecode est inférieur ou égal à 32 octets de longueur. Ainsi, du code en C # produira un accesseur de 40 octets, qui ne sera jamais incorporé par le JIT. Le même code en C ++ / CLI produira un accesseur de 20 octets, qui sera intégré par le JIT.

Un autre exemple est les variables temporaires, qui sont simplement compilées par le compilateur C ++ tout en étant toujours mentionnées dans l'IL produit par le compilateur C #. L'optimisation de la compilation statique C ++ entraînera moins de code, autorisant ainsi une optimisation JIT plus agressive, encore une fois.

La raison en est supposée être le fait que le compilateur C ++ / CLI a profité des vastes techniques d'optimisation du compilateur natif C ++.

Conclusion

J'adore C ++.

Mais pour autant que je le vois, C # ou Java sont dans l'ensemble un meilleur pari. Non pas parce qu'ils sont plus rapides que C ++, mais parce que lorsque vous additionnez leurs qualités, ils finissent par être plus productifs, nécessitent moins de formation et ont des bibliothèques standard plus complètes que C ++. Et comme pour la plupart des programmes, leurs différences de vitesse (d'une manière ou d'une autre) seront négligeables ...

Modifier (06/06/2011)

Mon expérience sur C # /. NET

J'ai maintenant 5 mois de codage C # professionnel presque exclusif (ce qui s'ajoute à mon CV déjà plein de C ++ et Java, et une touche de C ++ / CLI).

J'ai joué avec WinForms (Ahem ...) et WCF (cool!), Et WPF (Cool !!!! À la fois via XAML et C # brut. WPF est si facile que je pense que Swing ne peut tout simplement pas se comparer), et C # 4.0.

La conclusion est que s'il est plus facile / plus rapide de produire un code qui fonctionne en C # / Java qu'en C ++, il est beaucoup plus difficile de produire un code fort, sûr et robuste en C # (et encore plus difficile en Java) qu'en C ++. Les raisons abondent, mais elles peuvent être résumées par:

  1. Les génériques ne sont pas aussi puissants que les templates ( essayez d'écrire une méthode Parse générique efficace (de chaîne à T), ou un équivalent efficace de boost :: lexical_cast en C # pour comprendre le problème )
  2. RAII reste inégalé ( GC peut toujours fuir (oui, j'ai dû gérer ce problème) et ne traitera que la mémoire. Même C # usingn'est pas aussi facile et puissant car écrire une implémentation Dispose correcte est difficile )
  3. C # readonlyet Java ne finalsont nulle part aussi utiles que ceux de C ++const (il n'y a aucun moyen d'exposer des données complexes en lecture seule (un arbre de nœuds, par exemple) en C # sans travail énorme, alors qu'il s'agit d'une fonctionnalité intégrée de C ++. Les données immuables sont une solution intéressante , mais tout ne peut pas être rendu immuable, donc ce n'est même pas suffisant, de loin ).

Ainsi, C # reste un langage agréable tant que vous voulez quelque chose qui fonctionne, mais un langage frustrant au moment où vous voulez quelque chose qui fonctionne toujours et en toute sécurité .

Java est encore plus frustrant, car il a les mêmes problèmes que C #, et plus encore: manquant l'équivalent du usingmot-clé de C # , un de mes collègues très expérimentés a passé trop de temps à s'assurer que ses ressources étaient correctement libérées, alors que l'équivalent en C ++ aurait été facile (en utilisant des destructeurs et des pointeurs intelligents).

Donc, je suppose que le gain de productivité de C # / Java est visible pour la plupart du code ... jusqu'au jour où vous avez besoin que le code soit aussi parfait que possible. Ce jour-là, vous connaîtrez la douleur. (vous ne croirez pas ce qui est demandé à notre serveur et à nos applications GUI ...).

À propos de Java et C ++ côté serveur

J'ai gardé le contact avec les équipes serveurs (j'ai travaillé 2 ans parmi elles, avant de retourner dans l'équipe GUI), de l'autre côté du bâtiment, et j'ai appris quelque chose d'intéressant.

Les années dernières, la tendance était que les applications serveur Java soient destinées à remplacer les anciennes applications serveur C ++, car Java a beaucoup de frameworks / outils, et est facile à maintenir, déployer, etc. etc.

... Jusqu'à ce que le problème de la faible latence ne fasse son apparition ces derniers mois. Ensuite, les applications serveur Java, quelle que soit l'optimisation tentée par notre équipe Java qualifiée, ont simplement et proprement perdu la course contre l'ancien serveur C ++ pas vraiment optimisé.

Actuellement, la décision est de conserver les serveurs Java pour une utilisation courante où les performances, bien que toujours importantes, ne sont pas concernées par la cible à faible latence, et d'optimiser de manière agressive les applications de serveur C ++ déjà plus rapides pour les besoins à faible latence et ultra-faible latence.

Conclusion

Rien n'est aussi simple que prévu.

Java, et encore plus C #, sont des langages sympas, avec des bibliothèques et des frameworks standard étendus, où vous pouvez coder rapidement et obtenir des résultats très bientôt.

Mais lorsque vous avez besoin de puissance brute, d'optimisations puissantes et systématiques, d'une prise en charge solide du compilateur, de fonctionnalités de langage puissantes et d'une sécurité absolue, Java et C # rendent difficile la victoire des derniers pourcentages de qualité manquants mais critiques dont vous avez besoin pour rester au-dessus de la concurrence.

C'est comme si vous aviez besoin de moins de temps et de développeurs moins expérimentés en C # / Java qu'en C ++ pour produire du code de qualité moyenne, mais d'un autre côté, au moment où vous aviez besoin d'un code de qualité excellente pour perfectionner, il était soudainement plus facile et plus rapide d'obtenir les résultats directement en C ++.

Bien sûr, c'est ma propre perception, peut-être limitée à nos besoins spécifiques.

Mais encore, c'est ce qui se passe aujourd'hui, à la fois dans les équipes GUI et les équipes côté serveur.

Bien sûr, je mettrai à jour ce post si quelque chose de nouveau se produit.

Modifier (22/06/2011)

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

[...] La version Java était probablement la plus simple à implémenter, mais la plus difficile à analyser pour les performances. Plus précisément, les effets liés au ramassage des ordures étaient compliqués et très difficiles à régler. "

Sources:

Modifier (20/09/2011)

«Le mot courant chez Facebook est que« le code C ++ raisonnablement écrit s'exécute rapidement », ce qui souligne l'énorme effort consacré à l'optimisation du code PHP et Java. Paradoxalement, le code C ++ est plus difficile à écrire que dans d'autres langages, mais un code efficace est un beaucoup plus facile [d'écrire en C ++ que dans d'autres langages]. "

- Herb Sutter à // build / , citant Andrei Alexandrescu

Sources:

paercebal
la source
8
Vous éditez après 5 mois de C # décrit exactement ma propre expérience (modèles mieux, const mieux, RAII). +1. Ces trois fonctionnalités restent mes principales fonctionnalités pour C ++ (ou D, pour lesquelles je n'avais pas encore le temps).
Sebastian Mach
"Le traitement du code se fera au moment de la compilation". Par conséquent, la métaprogrammation de modèle ne fonctionne que dans le programme est disponible au moment de la compilation, ce qui n'est souvent pas le cas, par exemple, il est impossible d'écrire une bibliothèque d'expressions régulières performante en vanilla C ++ car elle est incapable de générer du code à l'exécution (un aspect important de métaprogrammation).
JD
"jouer avec les types se fait au moment de la compilation ... l'équivalent en Java ou C # est au mieux pénible à écrire, et sera toujours plus lent et résolu à l'exécution même si les types sont connus au moment de la compilation". En C #, cela n'est vrai que pour les types référence et n'est pas vrai pour les types valeur.
JD
1
"Quelle que soit l'optimisation JIT, rien n'ira aussi vite que l'accès direct du pointeur à la mémoire ... si vous avez des données contiguës en mémoire, y accéder via des pointeurs C ++ (c'est-à-dire des pointeurs C ... Donnons à César son dû) va parfois plus rapide qu'en Java / C # ". Les gens ont observé Java battre C ++ au test SOR du benchmark SciMark2 précisément parce que les pointeurs empêchent les optimisations liées aux alias. blogs.oracle.com/dagastine/entry/sun_java_is_faster_than
JD
Il convient également de noter que .NET spécialise les types de génériques dans les bibliothèques liées dynamiquement après la liaison, alors que C ++ ne le peut pas car les modèles doivent être résolus avant la liaison. Et évidemment, le gros avantage des génériques par rapport aux modèles est la compréhension des messages d'erreur.
JD
48

Chaque fois que je parle de performances gérées et non gérées, j'aime évoquer la série que Rico (et Raymond) a faite en comparant les versions C ++ et C # d'un dictionnaire chinois / anglais. Cette recherche Google vous permettra de lire par vous-même, mais j'aime le résumé de Rico.

Alors ai-je honte de ma défaite écrasante? À peine. Le code géré a obtenu un très bon résultat pour pratiquement aucun effort. Pour vaincre le managé, Raymond devait:

  • Ecrire son propre fichier E / S
  • Ecrire sa propre classe de chaînes
  • Ecrire son propre allocateur
  • Rédiger sa propre cartographie internationale

Bien sûr, il a utilisé les bibliothèques de niveau inférieur disponibles pour ce faire, mais cela représente encore beaucoup de travail. Pouvez-vous appeler ce qui reste un programme STL? Je ne pense pas, je pense qu'il a gardé la classe std :: vector qui n'a finalement jamais été un problème et il a gardé la fonction find. Presque tout le reste a disparu.

Donc, oui, vous pouvez certainement battre le CLR. Raymond peut accélérer son programme, je pense.

Il est intéressant de noter que le temps d'analyse du fichier indiqué par les minuteurs internes des deux programmes est à peu près le même - 30 ms pour chacun. La différence réside dans les frais généraux.

Pour moi, l'essentiel est qu'il a fallu 6 révisions pour que la version non gérée batte la version gérée qui était un simple portage du code non géré d'origine. Si vous avez besoin de toutes les dernières performances (et que vous avez le temps et l'expertise pour l'obtenir), vous devrez rester non géré, mais pour moi, je profiterai de l'ordre de grandeur que j'ai sur les premières versions par rapport aux 33 % Je gagne si j'essaye 6 fois.

Jon Norton
la source
3
le lien est mort, trouvé l'article mentionné ici: blogs.msdn.com/b/ricom/archive/2005/05/10/416151.aspx
gjvdkamp
Tout d'abord, si l'on regarde le code de Raymond Chen, il ne comprend clairement pas très bien le C ++ ou les structures de données. Son code atteint presque directement le code C de bas niveau, même dans les cas où le code C n'a aucun avantage en termes de performances (cela semble juste être une sorte de méfiance et peut-être un manque de connaissances sur la façon d'utiliser les profileurs). Il a également échoué à comprendre la manière la plus algorithmique d'implémenter un dictionnaire (il a utilisé std :: find pour l'amour de Christ). S'il y a quelque chose de bien à propos de Java, Python, C #, etc. - ils fournissent tous des dictionnaires très efficaces ...
stinky472
Les essais ou même std :: map seraient beaucoup plus favorables à C ++ ou même à une table de hachage. Enfin, un dictionnaire est exactement le type de programme qui profite le plus des bibliothèques et des frameworks de haut niveau. Il ne démontre pas autant les différences de langage que les bibliothèques impliquées (dont, je dirais volontiers que C # est beaucoup plus complet et fournit beaucoup plus d'outils adaptés à la tâche). Afficher un programme qui manipule de gros blocs de mémoire en comparaison, comme un code matrice / vecteur à grande échelle. Cela réglera cela assez rapidement même si, comme dans ce cas, les codeurs ne savent pas quoi ...
stinky472
26

La compilation pour des optimisations spécifiques du processeur est généralement surfaite. Il suffit de prendre un programme en C ++ et de le compiler avec optimisation pour pentium PRO et de l'exécuter sur un pentium 4. Puis recompiler avec Optimize pour pentium 4. J'ai passé de longs après-midi à le faire avec plusieurs programmes. Résultats généraux ?? Augmentation des performances généralement inférieure à 2-3%. Les avantages théoriques du JIT sont donc presque inexistants. La plupart des différences de performances ne peuvent être observées que lors de l'utilisation de fonctionnalités de traitement de données scalaires, ce qui nécessitera éventuellement un réglage fin manuel pour atteindre de toute façon des performances maximales. Les optimisations de ce type sont lentes et coûteuses à réaliser, ce qui les rend parfois inappropriées pour JIT de toute façon.

Dans le monde réel et dans les applications réelles, le C ++ est toujours généralement plus rapide que Java, principalement en raison d'une empreinte mémoire plus légère qui se traduit par de meilleures performances de cache.

Mais pour utiliser toutes les fonctionnalités C ++, le développeur doit travailler dur. Vous pouvez obtenir des résultats supérieurs, mais vous devez utiliser votre cerveau pour cela. C ++ est un langage qui a décidé de vous présenter plus d'outils, facturant le prix que vous devez les apprendre pour pouvoir bien utiliser le langage.

Vieil homme
la source
4
Ce n'est pas tant que vous compilez pour l'optimisation du processeur, mais vous compilez pour l'optimisation du chemin d'exécution. Si vous constatez qu'une méthode est très souvent appelée avec un paramètre spécifique, vous pouvez pré-compiler cette routine avec ce paramètre en tant que constante qui pourrait (dans le cas d'un booléen qui contrôle le flux) éliminer de gigantesques morceaux de travail. C ++ ne peut pas être proche de faire ce genre d'optimisation.
Bill K
1
Alors, comment font les JIT pour recompiler des routines pour tirer parti des chemins d'exécution observés, et quelle différence cela fait-il?
David Thornley
2
@Bill Je mixe peut-être deux choses ... mais la prédiction de branche n'est-elle pas effectuée au moment de l'exécution dans le pipeline d'instructions pour atteindre des objectifs similaires indépendamment du langage?
Hardy
@Hardy oui, le CPU peut faire de la prédiction de branche quelle que soit la langue, mais il ne peut pas factoriser une boucle entière en observant que la boucle n'a aucun effet sur quoi que ce soit. Il n'observera pas non plus que mult (0) est câblé pour retourner 0 et remplace simplement l'appel de méthode entier par if (param == 0) result = 0; et évitez tout l'appel de fonction / méthode. C pourrait faire ces choses si le compilateur avait une vue d'ensemble complète de ce qui se passait, mais généralement, il n'a pas assez d'informations au moment de la compilation.
Bill K
21

JIT (Just In Time Compiling) peut être incroyablement rapide car il s'optimise pour la plate-forme cible.

Cela signifie qu'il peut tirer parti de n'importe quelle astuce du compilateur que votre processeur peut prendre en charge, quel que soit le processeur sur lequel le développeur a écrit le code.

Le concept de base du .NET JIT fonctionne comme ceci (fortement simplifié):

Appel d'une méthode pour la première fois:

  • Le code de votre programme appelle une méthode Foo ()
  • Le CLR regarde le type qui implémente Foo () et obtient les métadonnées qui lui sont associées
  • À partir des métadonnées, le CLR sait dans quelle adresse mémoire l'IL (code d'octet intermédiaire) est stocké.
  • Le CLR alloue un bloc de mémoire et appelle le JIT.
  • Le JIT compile l'IL en code natif, le place dans la mémoire allouée, puis change le pointeur de fonction dans les métadonnées de type de Foo () pour qu'il pointe vers ce code natif.
  • Le code natif est exécuté.

Appel d'une méthode pour la deuxième fois:

  • Le code de votre programme appelle une méthode Foo ()
  • Le CLR examine le type qui implémente Foo () et trouve le pointeur de fonction dans les métadonnées.
  • Le code natif de cet emplacement mémoire est exécuté.

Comme vous pouvez le voir, la deuxième fois, c'est pratiquement le même processus que C ++, sauf avec l'avantage des optimisations en temps réel.

Cela dit, il existe encore d'autres problèmes de frais généraux qui ralentissent un langage géré, mais le JIT aide beaucoup.

FlySwat
la source
Au fait, Jonathan, je pense que quelqu'un continue de voter pour vos affaires. Quand je vous ai voté, vous aviez un -1 sur ce post.
Brian R. Bondy
12

J'aime la réponse d' Orion Adrian , mais il y a un autre aspect à cela.

La même question a été posée il y a des décennies à propos du langage d'assemblage par rapport aux langages «humains» comme FORTRAN. Et une partie de la réponse est similaire.

Oui, un programme C ++ est capable d'être plus rapide que C # sur n'importe quel algorithme donné (non trivial?), Mais le programme en C # sera souvent aussi rapide ou plus rapide qu'une implémentation "naïve" en C ++, et une version optimisée en C ++ prendra plus de temps à se développer et pourrait encore battre la version C # par une très petite marge. Alors, ça vaut vraiment le coup?

Vous devrez répondre à cette question une par une.

Cela dit, je suis un fan de longue date du C ++, et je pense que c'est un langage incroyablement expressif et puissant - parfois sous-estimé. Mais dans de nombreux problèmes «réels» (pour moi personnellement, cela signifie «le genre de problèmes pour lesquels je suis payé»), C # fera le travail plus tôt et plus sûr.

La plus grosse pénalité que vous payez? De nombreux programmes .NET et Java sont des porcs de mémoire. J'ai vu des applications .NET et Java prendre "des centaines" de mégaoctets de mémoire, alors que des programmes C ++ de complexité similaire effleurent à peine les "dizaines" de Mo.

Euro Micelli
la source
7

Je ne sais pas à quelle fréquence vous constaterez que le code Java fonctionnera plus rapidement que C ++, même avec Hotspot, mais je vais essayer d'expliquer comment cela pourrait arriver.

Considérez le code Java compilé comme un langage machine interprété pour la JVM. Lorsque le processeur Hotspot remarque que certaines parties du code compilé vont être utilisées plusieurs fois, il effectue une optimisation sur le code machine. Étant donné que l'assemblage de réglage manuel est presque toujours plus rapide que le code compilé C ++, il est normal de comprendre que le code machine réglé par programme ne le sera pas trop mauvais.

Donc, pour le code très répétitif, j'ai pu voir où il serait possible pour Hotspot JVM d'exécuter Java plus rapidement que C ++ ... jusqu'à ce que le ramasse-miettes entre en jeu. :)

billjamesdev
la source
Pourriez-vous développer cette affirmation Since hand-tuning Assembly is almost always faster than C++ compiled code? Qu'entendez-vous par «assemblage de réglage manuel» et «code compilé C ++»?
paercebal
Eh bien, il est basé sur l'idée que l'optimiseur d'un compilateur suit des règles, et pas les codeurs. Il y aura donc toujours du code que l'optimiseur trouvera qu'il ne peut pas optimiser parfaitement, alors qu'un humain le pourrait, soit en regardant une image plus grande, soit en en sachant plus sur ce que fait vraiment le code. J'ajouterai qu'il s'agit d'un commentaire vieux de 3 ans, et j'en sais plus sur HotSpot qu'avant, et je peux facilement voir que l'optimisation dynamique est un TRÈS bon moyen de faire fonctionner le code plus rapidement.
billjamesdev
1. Les optimisations de Hotspot ou de tout autre JIT sont toujours des optimisations de compilateur. JIT a l'avantage sur un compilateur statique de pouvoir intégrer certains résultats (code fréquemment appelé), voire de faire des optimisations basées sur le processeur en cours d'exécution, mais il s'agit toujours d'une optimisation du compilateur. . . 2. Je suppose que vous parlez d'optimisation d'algorithme, pas de "réglage fin d'assemblage". «Le réglage fin de l'assemblage manuel par un codeur humain» n'a pas réussi à produire de meilleurs résultats que les optimisations du compilateur depuis plus d'une décennie. En fait, un humain jouant avec l'assemblage
vrille
Ok, je comprends que j'utilise la mauvaise terminologie, "optimisation du compilateur" plutôt que "optimisation statique". Je tiens à souligner que, au moins dans l'industrie du jeu vidéo, aussi récemment que pour la PS2, nous utilisions encore l'assemblage codé à la main à certains endroits pour «optimiser» les puces spécifiques que nous savions se trouver sur la console; les compilateurs croisés de ces nouvelles puces n'étant pas encore aussi sophistiqués que ceux des architectures x86. Retour à la question initiale ci-dessus: le JIT a l'avantage de pouvoir mesurer avant l'optimisation, ce qui est une bonne chose (TM)
billjamesdev
Notez que la plupart des GC de production utilisent également l'assembleur manuscrit, car C / C ++ ne le coupe pas.
JD
6

En règle générale, l' algorithme de votre programme sera beaucoup plus important pour la vitesse de votre application que le langue . Vous pouvez implémenter un algorithme médiocre dans n'importe quel langage, y compris C ++. Dans cet esprit, vous serez généralement en mesure d'écrire du code plus rapidement dans un langage qui vous aide à implémenter un algorithme plus efficace.

Les langages de niveau supérieur y parviennent très bien en offrant un accès plus facile à de nombreuses structures de données pré-construites efficaces et en encourageant les pratiques qui vous aideront à éviter le code inefficace. Bien sûr, ils peuvent parfois aussi faciliter l'écriture d'un tas de code très lent, vous devez donc toujours connaître votre plate-forme.

De plus, C ++ rattrape les "nouvelles" fonctionnalités (notez les guillemets) comme les conteneurs STL, les pointeurs automatiques, etc. - voir la bibliothèque boost, par exemple. Et vous pourriez parfois constater que le moyen le plus rapide d'accomplir une tâche nécessite une technique comme l'arithmétique des pointeurs qui est interdite dans un langage de niveau supérieur - bien qu'ils vous permettent généralement d'appeler une bibliothèque écrite dans un langage qui peut l'implémenter comme vous le souhaitez. .

L'essentiel est de connaître le langage que vous utilisez, son API associée, ce qu'elle peut faire et ses limites.

Joël Coehoorn
la source
5

Je ne sais pas non plus ... mes programmes Java sont toujours lents. :-) Je n'ai cependant jamais vraiment remarqué que les programmes C # étaient particulièrement lents.

Paul Nathan
la source
4

Voici une autre référence intéressante, que vous pouvez essayer vous-même sur votre propre ordinateur.

Il compare ASM, VC ++, C #, Silverlight, applet Java, Javascript, Flash (AS3)

Démo de la vitesse du plugin Roozz

Veuillez noter que la vitesse du javascript varie beaucoup en fonction du navigateur qui l'exécute. Il en va de même pour Flash et Silverlight car ces plugins fonctionnent dans le même processus que le navigateur d'hébergement. Mais le plugin Roozz exécute des fichiers .exe standard, qui s'exécutent dans leur propre processus, donc la vitesse n'est pas influencée par le navigateur d'hébergement.

Thomas
la source
4

Vous devez définir "performer mieux que ..". Eh bien, je sais, vous avez posé des questions sur la vitesse, mais ce n'est pas tout ce qui compte.

  • Les machines virtuelles effectuent-elles plus de temps d'exécution? Oui!
  • Mangent-ils plus de mémoire de travail? Oui!
  • Ont-ils des coûts de démarrage plus élevés (initialisation du runtime et compilateur JIT)? Oui!
  • Ont-ils besoin d'une énorme bibliothèque installée? Oui!

Et ainsi de suite, c'est biaisé, oui;)

Avec C # et Java, vous payez un prix pour ce que vous obtenez (codage plus rapide, gestion automatique de la mémoire, grande bibliothèque, etc.). Mais vous n'avez pas beaucoup de place pour marchander les détails: prenez le package complet ou rien.

Même si ces langages peuvent optimiser certains codes pour s'exécuter plus rapidement que le code compilé, toute l'approche est (à mon humble avis) inefficace. Imaginez conduire tous les jours 8 km jusqu'à votre lieu de travail, avec un camion! C'est confortable, ça fait du bien, vous êtes en sécurité (zone de déformation extrême) et après avoir appuyé sur l'accélérateur pendant un certain temps, elle sera même aussi rapide qu'une voiture standard! Pourquoi n'avons-nous pas tous un camion pour nous rendre au travail? ;)

En C ++, vous en avez pour votre argent, ni plus, ni moins.

Citant Bjarne Stroustrup: "C ++ est mon langage de récupération de place préféré car il génère si peu de déchets" lien texte

Frunsi
la source
Eh bien, je pense qu'il a une bonne idée de ses inconvénients, il a également dit: "C vous permet de vous tirer une balle dans le pied facilement; C ++ le rend plus difficile, mais quand vous le faites, il vous souffle toute la jambe";)
Frunsi
"Ont-ils besoin d'une énorme bibliothèque installée" Java résout ce problème avec le puzzle de projet, je crois.
toc777
«En C ++, vous en avez pour votre argent, ni plus, ni moins». Contre-exemple: j'ai comparé une implémentation d'arbre RB en OCaml et C ++ (GNU GCC) qui utilisait une exception pour sauter en longueur de la récursivité si un élément ajouté était déjà présent afin de réutiliser l'ensemble existant. OCaml était jusqu'à 6 fois plus rapide que C ++ car il ne paie pas pour la vérification des destructeurs lorsque la pile est déroulée.
JD
3
@Jon: mais à un moment donné (plus tard?) Dans le temps, il doit quand même détruire les objets (au moins il doit libérer sa mémoire). Et notez également que les exceptions concernent des cas exceptionnels, au moins en C ++, cette règle doit être respectée. Les exceptions C ++ peuvent être lourdes lorsque des exceptions se produisent, c'est un compromis.
Frunsi
@Jon: essayez peut-être de répéter votre benchmark avec timessur un shell. Pour qu'il vérifie l'ensemble du programme, pas seulement un seul aspect. Les résultats sont-ils donc similaires?
Frunsi
3

Le code exécutable produit à partir d'un compilateur Java ou C # n'est pas interprété - il est compilé en code natif "juste à temps" (JIT). Ainsi, le premier code temporel d'un programme Java / C # est rencontré pendant l'exécution, il y a une surcharge car le "compilateur d'exécution" (aussi appelé compilateur JIT) transforme le code d'octet (Java) ou le code IL (C #) en instructions machine natives. Cependant, la prochaine fois que ce code est rencontré alors que l'application est toujours en cours d'exécution, le code natif est exécuté immédiatement. Cela explique comment certains programmes Java / C # semblent être lents au départ, mais fonctionnent mieux au fur et à mesure qu'ils s'exécutent. Un bon exemple est un site Web ASP.Net. La toute première fois que vous accédez au site Web, cela peut être un peu plus lent car le code C # est compilé en code natif par le compilateur JIT.

Peter Meyer
la source
3

Quelques bonnes réponses ici sur la question spécifique que vous avez posée. J'aimerais prendre du recul et regarder la situation dans son ensemble.

Gardez à l'esprit que la perception de votre utilisateur de la vitesse du logiciel que vous écrivez est affectée par de nombreux autres facteurs que l'optimisation du codegen. Voici quelques exemples:

  • La gestion manuelle de la mémoire est difficile à faire correctement (pas de fuite), et encore plus difficile à faire efficacement (libérer de la mémoire peu de temps après que vous en ayez terminé). L'utilisation d'un GC est, en général, plus susceptible de produire un programme qui gère bien la mémoire. Êtes-vous prêt à travailler très dur et à retarder la livraison de votre logiciel, dans le but de surpasser le GC?

  • Mon C # est plus facile à lire et à comprendre que mon C ++. J'ai également plus de moyens de me convaincre que mon code C # fonctionne correctement. Cela signifie que je peux optimiser mes algorithmes avec moins de risque d'introduire des bogues (et les utilisateurs n'aiment pas les logiciels qui plantent, même s'ils le font rapidement!)

  • Je peux créer mon logiciel plus rapidement en C # qu'en C ++. Cela libère du temps pour travailler sur les performances et toujours livrer mon logiciel à temps.

  • Il est plus facile d'écrire une bonne interface utilisateur en C # qu'en C ++, donc je suis plus susceptible de pouvoir pousser le travail en arrière-plan pendant que l'interface utilisateur reste réactive, ou de fournir une interface utilisateur de progression ou de rythme lorsque le programme doit bloquer pendant un certain temps. Cela ne rend rien plus rapide, mais cela rend les utilisateurs plus heureux d'attendre.

Tout ce que j'ai dit sur C # est probablement vrai pour Java, je n'ai tout simplement pas l'expérience pour le dire avec certitude.

Jay Bazuzi
la source
3

Si vous êtes un programmeur Java / C # apprenant C ++, vous serez tenté de continuer à penser en termes de Java / C # et de traduire textuellement en syntaxe C ++. Dans ce cas, vous n'obtenez que les avantages mentionnés précédemment du code natif par rapport à interprété / JIT. Pour obtenir le plus grand gain de performances en C ++ par rapport à Java / C #, vous devez apprendre à penser en C ++ et concevoir du code spécifiquement pour exploiter les atouts du C ++.

Pour paraphraser Edsger Dijkstra : [votre langue maternelle] mutile l'esprit au-delà de la guérison.
Pour paraphraser Jeff Atwood : vous pouvez écrire [votre langue maternelle] dans n'importe quelle nouvelle langue.

palm3D
la source
1
Je soupçonne que le dicton "Vous pouvez écrire FORTRAN dans n'importe quelle langue" est antérieur à la carrière de Jeff.
David Thornley
3

L'une des optimisations JIT les plus importantes est l'intégration de méthodes. Java peut même intégrer des méthodes virtuelles s'il peut garantir l'exactitude d'exécution. Ce type d'optimisation ne peut généralement pas être effectué par des compilateurs statiques standard car il nécessite une analyse de l'ensemble du programme, ce qui est difficile à cause d'une compilation séparée (en revanche, JIT a tout le programme disponible). L'intégration de méthodes améliore d'autres optimisations, donnant des blocs de code plus volumineux à optimiser.

L'allocation de mémoire standard en Java / C # est également plus rapide et la désallocation (GC) n'est pas beaucoup plus lente, mais seulement moins déterministe.

user57697
la source
Notez que freeet deletene sont pas non plus déterministes et GC peut être rendu déterministe en n'allouant pas.
JD
3

Il est peu probable que les langages de la machine virtuelle surpassent les langages compilés, mais ils peuvent être suffisamment proches pour que cela n'ait pas d'importance, pour (au moins) les raisons suivantes (je parle pour Java ici car je n'ai jamais fait C #).

1 / L'environnement d'exécution Java est généralement capable de détecter des morceaux de code qui sont exécutés fréquemment et d'effectuer une compilation juste à temps (JIT) de ces sections afin qu'à l'avenir, elles s'exécutent à la vitesse de compilation complète.

2 / De vastes portions des bibliothèques Java sont compilées pour que, lorsque vous appelez une fonction de bibliothèque, vous exécutez du code compilé, non interprété. Vous pouvez voir le code (en C) en téléchargeant OpenJDK.

3 / Sauf si vous faites des calculs massifs, la plupart du temps, votre programme est en cours d'exécution, il attend l'entrée d'un humain très lent (relativement parlant).

4 / Etant donné qu'une grande partie de la validation du bytecode Java est effectuée au moment du chargement de la classe, la surcharge normale des vérifications d'exécution est considérablement réduite.

5 / Dans le pire des cas, du code gourmand en performances peut être extrait vers un module compilé et appelé depuis Java (voir JNI) pour qu'il s'exécute à pleine vitesse.

En résumé, le bytecode Java ne surpassera jamais le langage machine natif, mais il existe des moyens de l'atténuer. Le gros avantage de Java (comme je le vois) est la bibliothèque standard ÉNORME et la nature multiplateforme.

paxdiablo
la source
1
Concernant le point 2, "2 / De vastes portions des bibliothèques Java sont compilées de sorte que, lorsque vous appelez une fonction de bibliothèque, vous exécutez du code compilé, non interprété": Avez-vous une citation pour cela? Si c'était vraiment ce que vous décrivez, je m'attendrais à rencontrer beaucoup de code natif de mon débogueur, mais ce n'est pas le cas.
cero du
Les débogueurs Re: cero utilisent souvent des chemins moins efficaces mais plus expressifs, et ne sont donc pas un bon marqueur pour tout ce qui concerne les performances.
Guvante
2
Il y a un autre énorme gain de performances à cette bibliothèque HUGH - le code de la bibliothèque est probablement mieux écrit que ce que de nombreux programmeurs écriront seuls (étant donné un temps limité et un manque de connaissances spécialisées) et sur Java, pour de nombreuses raisons, les programmeurs utilisent souvent la bibliothèque.
Liran Orevi
3

Orion Adrian , permettez-moi d'inverser votre message pour voir à quel point vos remarques sont infondées, car on peut également dire beaucoup de choses sur C ++. Et dire que le compilateur Java / C # optimise les fonctions vides vous donne vraiment l'impression que vous n'êtes pas mon expert en optimisation, car a) pourquoi un vrai programme devrait-il contenir des fonctions vides, à l'exception du très mauvais code hérité, b) ce n'est vraiment pas optimisation du noir et de la fine pointe.

En dehors de cette phrase, vous avez dénoncé ouvertement les pointeurs, mais les objets en Java et C # ne fonctionnent-ils pas essentiellement comme des pointeurs C ++? Ne peuvent-ils pas se chevaucher? Ne peuvent-ils pas être nuls? C (et la plupart des implémentations C ++) a le mot clé restrict, les deux ont des types valeur, C ++ a une référence à valeur avec une garantie non nulle. Que proposent Java et C #?

>>>>>>>>>>

En règle générale, C et C ++ peuvent être tout aussi rapides ou plus rapides car le compilateur AOT - un compilateur qui compile votre code avant le déploiement, une fois pour toutes, sur votre serveur de compilation à mémoire élevée - peut faire des optimisations qu'un programme compilé C # ne peut pas parce qu'il a une tonne de temps pour le faire. Le compilateur peut déterminer si la machine est Intel ou AMD; Pentium 4, Core Solo ou Core Duo; ou si prend en charge SSE4, etc., et si votre compilateur ne prend pas en charge la répartition d'exécution, vous pouvez résoudre cela vous-même en déployant une poignée de binaires spécialisés.

Le programme AC # est généralement compilé lors de son exécution afin qu'il fonctionne correctement sur toutes les machines, mais n'est pas optimisé autant qu'il pourrait l'être pour une seule configuration (processeur, jeu d'instructions, autre matériel), et il doit passer un certain temps première. Des fonctionnalités telles que la fission de boucle, l'inversion de boucle, la vectorisation automatique, l'optimisation de l'ensemble du programme, l'expansion du modèle, l'introduction en bourse et bien d'autres sont très difficiles à résoudre complètement et complètement d'une manière qui n'ennuie pas l'utilisateur final.

De plus, certaines fonctionnalités du langage permettent au compilateur en C ++ ou C de faire des hypothèses sur votre code qui lui permettent d'optimiser certaines parties qui ne sont tout simplement pas sûres pour le compilateur Java / C #. Lorsque vous n'avez pas accès à l'ID de type complet des génériques ou à un flux de programme garanti, de nombreuses optimisations ne sont tout simplement pas sûres.

De plus, C ++ et C effectuent de nombreuses allocations de pile à la fois avec une seule incrémentation de registre, ce qui est sûrement plus efficace que les allocations Javas et C # en ce qui concerne la couche d'abstraction entre le garbage collector et votre code.

Maintenant, je ne peux pas parler pour Java sur ce point suivant, mais je sais que les compilateurs C ++ par exemple supprimeront en fait les méthodes et les appels de méthode quand il sait que le corps de la méthode est vide, il éliminera les sous-expressions courantes, il peut essayer de réessayer pour trouver une utilisation optimale du registre, il n'applique pas la vérification des limites, il va autovectoriser les boucles et les boucles internes et inversera l'intérieur vers l'extérieur, il déplace les conditions hors des boucles, il divise et déverrouille les boucles. Il étendra std :: vector dans des tableaux de surcharge natifs comme vous le feriez en C. Il fera des optimisations inter-procédurales. Il construira des valeurs de retour directement sur le site de l'appelant. Il pliera et propagera les expressions. Il réorganisera les données de manière conviviale pour le cache. Cela fera des threads sautés. Il vous permet d'écrire des traceurs de rayons au moment de la compilation avec une surcharge d'exécution nulle. Cela fera des optimisations basées sur des graphiques très coûteuses. Cela réduira la force, s'il remplaçait certains codes par du code syntaxiquement totalement inégal mais sémantiquement équivalent (l'ancien "xor foo, foo" est juste l'optimisation la plus simple, bien que dépassée, de ce type). Si vous le demandez gentiment, vous pouvez omettre les normes de virgule flottante IEEE et activer encore plus d'optimisations comme la réorganisation des opérandes en virgule flottante. Une fois qu'il a massé et massacré votre code, il peut répéter tout le processus, car souvent, certaines optimisations jettent les bases d'optimisations encore plus certaines. Il peut également simplement réessayer avec des paramètres mélangés et voir comment l'autre variante se classe dans son classement interne. Et il utilisera ce type de logique dans tout votre code. s'il remplace certains codes par du code syntaxiquement totalement inégal mais sémantiquement équivalent (l'ancien "xor foo, foo" est simplement l'optimisation la plus simple, bien que dépassée, de ce type). Si vous le demandez gentiment, vous pouvez omettre les normes de virgule flottante IEEE et activer encore plus d'optimisations comme la réorganisation des opérandes en virgule flottante. Une fois qu'il a massé et massacré votre code, il peut répéter tout le processus, car souvent, certaines optimisations jettent les bases d'optimisations encore plus certaines. Il peut également simplement réessayer avec des paramètres mélangés et voir comment l'autre variante se classe dans son classement interne. Et il utilisera ce type de logique dans tout votre code. s'il remplace certains codes par du code syntaxiquement totalement inégal mais sémantiquement équivalent (l'ancien "xor foo, foo" est simplement l'optimisation la plus simple, bien que dépassée, de ce type). Si vous le demandez gentiment, vous pouvez omettre les normes de virgule flottante IEEE et activer encore plus d'optimisations comme la réorganisation des opérandes en virgule flottante. Une fois qu'il a massé et massacré votre code, il peut répéter tout le processus, car souvent, certaines optimisations jettent les bases d'optimisations encore plus certaines. Il peut également simplement réessayer avec des paramètres mélangés et voir comment l'autre variante se classe dans son classement interne. Et il utilisera ce type de logique dans tout votre code. Si vous le demandez gentiment, vous pouvez omettre les normes de virgule flottante IEEE et activer encore plus d'optimisations comme la réorganisation des opérandes en virgule flottante. Une fois qu'il a massé et massacré votre code, il peut répéter tout le processus, car souvent, certaines optimisations jettent les bases d'optimisations encore plus certaines. Il peut également simplement réessayer avec des paramètres mélangés et voir comment l'autre variante se classe dans son classement interne. Et il utilisera ce type de logique dans tout votre code. Si vous le demandez gentiment, vous pouvez omettre les normes de virgule flottante IEEE et activer encore plus d'optimisations comme la réorganisation des opérandes en virgule flottante. Une fois qu'il a massé et massacré votre code, il peut répéter tout le processus, car souvent, certaines optimisations jettent les bases d'optimisations encore plus certaines. Il peut également simplement réessayer avec des paramètres mélangés et voir comment l'autre variante se classe dans son classement interne. Et il utilisera ce type de logique dans tout votre code. Il peut également simplement réessayer avec des paramètres mélangés et voir comment l'autre variante se classe dans son classement interne. Et il utilisera ce type de logique dans tout votre code. Il peut également simplement réessayer avec des paramètres mélangés et voir comment l'autre variante se classe dans son classement interne. Et il utilisera ce type de logique dans tout votre code.

Donc, comme vous pouvez le voir, il y a de nombreuses raisons pour lesquelles certaines implémentations C ++ ou C seront plus rapides.

Cela dit, de nombreuses optimisations peuvent être faites en C ++ qui épateront tout ce que vous pourriez faire avec C #, en particulier dans le domaine du calcul des nombres, en temps réel et proche du métal, mais pas exclusivement là-bas. Vous n'avez même pas besoin de toucher un seul pointeur pour parcourir un long chemin.

Donc, selon ce que vous écrivez, j'irais avec l'un ou l'autre. Mais si vous écrivez quelque chose qui ne dépend pas du matériel (pilote, jeu vidéo, etc.), je ne m'inquiéterais pas des performances de C # (encore une fois, je ne peux pas parler de Java). Ça ira très bien.

<<<<<<<<<<

Généralement, certains arguments généralisés peuvent sembler cool dans des articles spécifiques, mais ne semblent généralement pas crédibles.

Bref, pour faire la paix: AOT est génial, tout comme JIT . La seule bonne réponse peut être: cela dépend. Et les vrais gens intelligents savent que vous pouvez de toute façon utiliser le meilleur des deux mondes.

Sebastian Mach
la source
2

Cela ne se produirait que si l'interpréteur Java produit du code machine qui est en fait mieux optimisé que le code machine que votre compilateur génère pour le code C ++ que vous écrivez, au point où le code C ++ est plus lent que Java et le coût d'interprétation.

Cependant, les chances que cela se produise sont assez faibles - à moins que Java n'ait peut-être une bibliothèque très bien écrite et que vous ayez votre propre bibliothèque C ++ mal écrite.

ine
la source
Je crois aussi qu'il y a un certain poids de la langue aussi, lorsque vous travaillez à un niveau inférieur, avec moins d'abstraction, vous développerez un programme plus rapide. Cela n'a aucun rapport avec les points concernant l'exécution du bytecode lui-même.
Brian R. Bondy
2

En fait, C # ne fonctionne pas vraiment dans une machine virtuelle comme le fait Java. IL est compilé en langage d'assemblage, qui est entièrement du code natif et s'exécute à la même vitesse que le code natif. Vous pouvez pré-JIT une application .NET qui supprime entièrement le coût JIT, puis vous exécutez du code entièrement natif.

Le ralentissement avec .NET ne viendra pas parce que le code .NET est plus lent, mais parce qu'il fait beaucoup plus en arrière-plan pour faire des choses comme la collecte des ordures, vérifier les références, stocker des cadres de pile complets, etc. Cela peut être assez puissant et utile lorsque la construction d'applications, mais a également un coût. Notez que vous pouvez également faire toutes ces choses dans un programme C ++ (une grande partie des fonctionnalités principales de .NET est en fait du code .NET que vous pouvez afficher dans ROTOR). Cependant, si vous écriviez à la main la même fonctionnalité, vous vous retrouveriez probablement avec un programme beaucoup plus lent car le runtime .NET a été optimisé et finement réglé.

Cela dit, l'un des points forts du code géré est qu'il peut être entièrement vérifiable, c'est-à-dire. vous pouvez vérifier que le code n'accédera jamais à la mémoire d'un autre processus ou n'effectuera aucune opération non conforme avant de l'exécuter. Microsoft a un prototype de recherche d'un système d'exploitation entièrement géré qui a montré de manière surprenante qu'un environnement géré à 100% peut en fait fonctionner beaucoup plus rapidement que n'importe quel système d'exploitation moderne en tirant parti de cette vérification pour désactiver les fonctionnalités de sécurité qui ne sont plus nécessaires aux programmes gérés. (nous parlons comme 10x dans certains cas). La radio SE a un grand épisode parlant de ce projet.

jezell
la source
1

Dans certains cas, le code managé peut en fait être plus rapide que le code natif. Par exemple, les algorithmes de récupération de place "mark-and-sweep" permettent à des environnements comme le JRE ou le CLR de libérer un grand nombre d'objets de courte durée (généralement) en une seule passe, où la plupart des objets de tas C / C ++ sont libérés un par un. une fois.

De wikipedia :

Pour de nombreuses raisons pratiques, les algorithmes d'allocation / désallocation intensifs implémentés dans les langages de récupération de place peuvent en fait être plus rapides que leurs équivalents en utilisant l'allocation manuelle de tas. L'une des principales raisons à cela est que le ramasse-miettes permet au système d'exécution d'amortir les opérations d'allocation et de désallocation d'une manière potentiellement avantageuse.

Cela dit, j'ai écrit beaucoup de C # et beaucoup de C ++, et j'ai exécuté beaucoup de benchmarks. D'après mon expérience, C ++ est beaucoup plus rapide que C #, de deux manières: (1) si vous prenez du code que vous avez écrit en C #, portez-le en C ++, le code natif a tendance à être plus rapide. Combien plus vite? Eh bien, cela varie beaucoup, mais il n'est pas rare de voir une amélioration de la vitesse de 100%. (2) Dans certains cas, le garbage collection peut ralentir massivement une application gérée. Le .NET CLR fait un travail terrible avec de gros tas (par exemple,> 2 Go), et peut finir par passer beaucoup de temps en GC - même dans des applications qui ont peu ou même pas d'objets de durée de vie intermédiaire.

Bien sûr, dans la plupart des cas que j'ai rencontrés, les langages gérés sont assez rapides, de loin, et le compromis entre la maintenance et le codage pour les performances supplémentaires du C ++ n'est tout simplement pas bon.

cero
la source
1
Le problème est que pour les processus de longue durée, tels qu'un serveur Web, votre mémoire au fil du temps deviendra si fragmentée (dans un programme écrit en C ++) que vous devrez implémenter quelque chose qui ressemble à un garbage collection (ou redémarrer de temps en temps, voir IIS ).
Tony BenBrahim
3
Je n'ai pas observé cela sur les grands programmes Unix qui sont censés fonctionner pour toujours. Ils ont tendance à être écrits en C, ce qui est encore pire pour la gestion de la mémoire que C ++.
David Thornley
Bien sûr, la question est de savoir si nous comparons une implémentation d'un programme en code managé par rapport à un code non managé, ou les meilleures performances théoriques du langage. De toute évidence, le code non géré peut toujours être au moins aussi rapide que géré, car dans le pire des cas, vous pouvez simplement écrire un programme non géré qui fait exactement la même chose que le code géré! Mais la plupart des problèmes de performances sont algorithmiques et non micro. De plus, vous n'optimisez pas le code managé et non managé de la même manière, donc "C ++ en C #" ne fonctionnera généralement pas bien.
kyoryu
2
En C / C ++, vous pouvez allouer des objets de courte durée sur la pile, et vous le faites quand c'est approprié. En code managé, vous ne pouvez pas , vous n'avez pas le choix. De plus, en C / C ++, vous pouvez allouer des listes d'objets dans des zones contigues (nouveau Foo [100]), dans le code managé, vous ne pouvez pas. Donc, votre comparaison n'est pas valide. Eh bien, ce pouvoir de choix impose un fardeau aux développeurs, mais de cette façon, ils apprennent à connaître le monde dans lequel ils vivent (mémoire ...).
Frunsi
@frunsi: "en C / C ++ vous pouvez allouer des listes d'objets dans des zones contigues (nouveau Foo [100]), en code managé vous ne pouvez pas". C'est inexact. Les types de valeur locaux sont alloués par pile et vous pouvez même en allouer des tableaux en C #. Il existe même des systèmes de production écrits en C # qui sont totalement sans allocation en régime permanent.
JD
1

En fait, la JVM HotSpot de Sun utilise une exécution en "mode mixte". Il interprète le bytecode de la méthode jusqu'à ce qu'il détermine (généralement via un compteur quelconque) qu'un bloc de code particulier (méthode, boucle, bloc try-catch, etc.) va être beaucoup exécuté, puis il le compile par JIT. Le temps requis pour compiler une méthode en JIT prend souvent plus de temps que si la méthode devait être interprétée s'il s'agissait d'une méthode rarement exécutée. Les performances sont généralement plus élevées pour le «mode mixte» car la JVM ne perd pas de temps à JITing du code qui est rarement, voire jamais, exécuté. C # et .NET ne font pas cela. .NET JIT tout ce qui, souvent, fait perdre du temps.

mcjabberz
la source
1

Allez lire sur Dynamo de HP Labs , un interpréteur pour PA-8000 qui fonctionne sur PA-8000, et exécute souvent les programmes plus rapidement qu'ils ne le font en mode natif. Alors ça ne paraîtra pas du tout surprenant!

Ne le considérez pas comme une "étape intermédiaire" - l'exécution d'un programme implique déjà beaucoup d'autres étapes, dans n'importe quelle langue.

Cela revient souvent à:

  • les programmes ont des points chauds, donc même si vous exécutez plus lentement 95% du corps du code que vous devez exécuter, vous pouvez toujours être compétitif en termes de performances si vous êtes plus rapide à 5%

  • un HLL en sait plus sur votre intention qu'un LLL comme C / C ++, et peut donc générer du code plus optimisé (OCaml en a encore plus, et en pratique est souvent encore plus rapide)

  • un compilateur JIT a beaucoup d'informations qu'un compilateur statique n'a pas (comme, les données réelles que vous avez cette fois)

  • un compilateur JIT peut effectuer des optimisations au moment de l'exécution que les éditeurs de liens traditionnels ne sont pas vraiment autorisés à faire (comme la réorganisation des branches afin que le cas commun soit plat, ou les appels de bibliothèque en ligne)

Dans l'ensemble, C / C ++ sont des langages assez médiocres pour les performances: il y a relativement peu d'informations sur vos types de données, aucune information sur vos données et aucun runtime dynamique pour permettre une optimisation au moment de l'exécution.

Ken
la source
1

Vous pouvez obtenir de courtes rafales lorsque Java ou CLR est plus rapide que C ++, mais dans l'ensemble, les performances sont pires pour la durée de vie de l'application: voir www.codeproject.com/KB/dotnet/RuntimePerformance.aspx pour obtenir des résultats à ce sujet.

dmihailescu
la source
1

Je crois comprendre que C / C ++ produit du code natif à exécuter sur une architecture de machine particulière. Inversement, des langages comme Java et C # s'exécutent sur une machine virtuelle qui fait abstraction de l'architecture native. Logiquement, il semblerait impossible pour Java ou C # d'égaler la vitesse de C ++ à cause de cette étape intermédiaire, mais on m'a dit que les derniers compilateurs ("hot spot") peuvent atteindre cette vitesse ou même la dépasser.

C'est illogique. L'utilisation d'une représentation intermédiaire ne dégrade pas intrinsèquement les performances. Par exemple, llvm-gcc compile C et C ++ via LLVM IR (qui est une machine virtuelle à registre infini) en code natif et obtient d'excellentes performances (battant souvent GCC).

C'est peut-être plus une question de compilation qu'une question de langue, mais quelqu'un peut-il expliquer en anglais simple comment il est possible pour l'un de ces langages de machine virtuelle de fonctionner mieux qu'une langue native?

Voici quelques exemples:

  • Les machines virtuelles avec la compilation JIT facilitent la génération de code d'exécution (par exemple System.Reflection.Emitsur .NET) afin que vous puissiez compiler le code généré à la volée dans des langages comme C # et F # mais devez recourir à l'écriture d'un interpréteur relativement lent en C ou C ++. Par exemple, pour implémenter des expressions régulières.

  • Certaines parties de la machine virtuelle (par exemple la barrière d'écriture et l'allocateur) sont souvent écrites dans l'assembleur codé à la main car C et C ++ ne génèrent pas de code assez rapidement. Si un programme met l'accent sur ces parties d'un système, il pourrait alors surpasser tout ce qui peut être écrit en C ou C ++.

  • La liaison dynamique du code natif nécessite la conformité à une ABI qui peut entraver les performances et empêcher l'optimisation de l'ensemble du programme, tandis que la liaison est généralement différée sur les machines virtuelles et peut bénéficier d'optimisations de l'ensemble du programme (comme les génériques réifiés de .NET).

J'aimerais également aborder certains problèmes liés à la réponse hautement votée de paercebal ci-dessus (parce que quelqu'un n'arrête pas de supprimer mes commentaires sur sa réponse) qui présente une vue polarisée contre-productive:

Le traitement du code se fera au moment de la compilation ...

Par conséquent, la métaprogrammation de modèles ne fonctionne que si le programme est disponible au moment de la compilation, ce qui n'est souvent pas le cas, par exemple, il est impossible d'écrire une bibliothèque d'expressions régulières performante en vanilla C ++ car elle est incapable de générer du code à l'exécution (un aspect important de métaprogrammation).

... jouer avec les types se fait au moment de la compilation ... l'équivalent en Java ou C # est au mieux pénible à écrire, et sera toujours plus lent et résolu à l'exécution même si les types sont connus au moment de la compilation.

En C #, cela n'est vrai que pour les types référence et n'est pas vrai pour les types valeur.

Quelle que soit l'optimisation JIT, rien n'ira aussi vite que l'accès direct du pointeur à la mémoire ... si vous avez des données contiguës en mémoire, y accéder via des pointeurs C ++ (c'est-à-dire des pointeurs C ... Donnons à César son dû) sera des fois plus rapide qu'en Java / C #.

Les gens ont observé Java battre C ++ au test SOR du benchmark SciMark2 précisément parce que les pointeurs empêchent les optimisations liées aux alias.

Il convient également de noter que .NET spécialise les types de génériques dans les bibliothèques liées dynamiquement après la liaison, alors que C ++ ne le peut pas car les modèles doivent être résolus avant la liaison. Et évidemment, le gros avantage des génériques par rapport aux modèles est la compréhension des messages d'erreur.

Jon Harrop
la source
0

En plus de ce que certains ont dit, d'après ma compréhension, .NET et Java sont meilleurs pour l'allocation de mémoire. Par exemple, ils peuvent compacter la mémoire lorsqu'elle est fragmentée, tandis que C ++ ne le peut pas (en mode natif, mais c'est possible si vous utilisez un garbage collector intelligent).

Giovanni Galbo
la source
Ou si vous utilisez un meilleur allocateur C ++ et / ou un pool d'objets. C'est loin d'être magique, d'un point de vue C ++, et cela peut se résumer à ce qu'une «allocation de tas» devienne aussi rapide qu'une allocation de pile.
paercebal
Si vous allouez toujours tout sur le tas, alors .NET et Java peuvent même fonctionner mieux que C / C ++. Mais vous ne le ferez pas en C / C ++.
Frunsi
0

Pour tout ce qui nécessite beaucoup de vitesse, la JVM appelle simplement une implémentation C ++, il s'agit donc davantage de la qualité de leurs bibliothèques que de la qualité de la JVM pour la plupart des choses liées au système d'exploitation. Le ramassage des ordures réduit votre mémoire de moitié, mais l'utilisation de certaines des fonctionnalités STL et Boost plus sophistiquées aura le même effet mais avec plusieurs fois le potentiel de bogue.

Si vous utilisez uniquement des bibliothèques C ++ et de nombreuses fonctionnalités de haut niveau dans un grand projet avec de nombreuses classes, vous vous retrouverez probablement plus lentement que d'utiliser une JVM. Sauf beaucoup plus sujet aux erreurs.

Cependant, l'avantage du C ++ est qu'il vous permet de vous optimiser, sinon vous êtes coincé avec ce que fait le compilateur / jvm. Si vous créez vos propres conteneurs, écrivez votre propre gestion de la mémoire alignée, utilisez SIMD et passez à l'assemblage ici et là, vous pouvez accélérer au moins 2x à 4x par rapport à ce que la plupart des compilateurs C ++ feront seuls. Pour certaines opérations, 16x-32x. Cela utilise les mêmes algorithmes, si vous utilisez de meilleurs algorithmes et parallélisez, les augmentations peuvent être dramatiques, parfois des milliers de fois plus rapides que les méthodes couramment utilisées.

Fromage Charles Eli
la source
0

Je le regarde à partir de quelques points différents.

  1. Avec un temps et des ressources infinis, le code géré ou non géré sera-t-il plus rapide? De toute évidence, la réponse est que le code non géré peut toujours au moins lier le code géré dans cet aspect - comme dans le pire des cas, vous coderiez simplement en dur la solution de code géré.
  2. Si vous prenez un programme dans une langue et que vous le traduisez directement dans une autre, à quel point sera-t-il pire? Probablement beaucoup, pour les deux langues. La plupart des langages nécessitent différentes optimisations et ont des pièges différents. La micro-performance consiste souvent à connaître ces détails.
  3. Compte tenu du temps et des ressources limités, laquelle des deux langues produira un meilleur résultat? C'est la question la plus intéressante, car si un langage géré peut produire un code légèrement plus lent (étant donné un programme raisonnablement écrit pour ce langage), cette version sera probablement faite plus tôt, ce qui permettra plus de temps consacré à l'optimisation.
kyoryu
la source
0

Une réponse très courte: avec un budget fixe, vous obtiendrez une application Java plus performante qu'une application C ++ (considérations ROI) De plus, la plate-forme Java a des profileurs plus décents, qui vous aideront à identifier vos hotspots plus rapidement

lifey
la source