Avertissement : La question que vous avez posée est en réalité assez complexe - probablement beaucoup plus que vous ne le pensez. Par conséquent, ceci est un vraiment longue réponse.
D'un point de vue purement théorique, il y a probablement une réponse simple à cette question : il n'y a (probablement) rien dans C# qui l'empêche vraiment d'être aussi rapide que C++. En dépit de la théorie, il y a cependant quelques raisons pratiques pour lesquelles il est difficile d'utiliser C#. est plus lent pour certaines choses dans certaines circonstances.
Je me pencherai sur trois domaines fondamentaux de différences : les caractéristiques du langage, l'exécution par la machine virtuelle et le ramassage des déchets. Ces deux derniers points vont souvent de pair, mais peuvent être indépendants, c'est pourquoi je les aborderai séparément.
Caractéristiques de la langue
Le C++ accorde une grande importance aux modèles et aux fonctionnalités du système de modèles qui sont largement destinés à permettre que le plus de choses possibles soient faites au moment de la compilation, donc du point de vue du programme, ils sont "statiques". La métaprogrammation par gabarit permet d'effectuer des calculs complètement arbitraires au moment de la compilation (c'est-à-dire que le système de gabarit est complet de Turing). En tant que tel, tout ce qui ne dépend pas de l'entrée de l'utilisateur peut être calculé au moment de la compilation, de sorte qu'au moment de l'exécution, il s'agit simplement d'une constante. L'entrée peut toutefois inclure des choses comme des informations de type, de sorte qu'une grande partie de ce que vous feriez via la réflexion au moment de l'exécution en C# est normalement faite au moment de la compilation via la métaprogrammation de modèle en C++. Il existe cependant un compromis entre la vitesse d'exécution et la polyvalence : ce que les modèles peuvent faire, ils le font de manière statique, mais ils ne peuvent tout simplement pas faire tout ce que la réflexion peut faire.
Les différences entre les caractéristiques des langages signifient que presque toute tentative de comparaison des deux langages par simple translittération de C# en C++ (ou vice versa) est susceptible de produire des résultats se situant entre l'insignifiance et la tromperie (et la même chose serait vraie pour la plupart des autres paires de langages également). Le fait est que pour tout ce qui est plus grand que quelques lignes de code, presque personne n'est susceptible d'utiliser les langages de la même façon (ou suffisamment proche de la même façon) pour qu'une telle comparaison vous dise quoi que ce soit sur la façon dont ces langages fonctionnent dans la vie réelle.
Machine virtuelle
Comme presque toutes les VM raisonnablement modernes, celle de Microsoft pour .NET peut faire et fera une compilation JIT (alias "dynamique"). Cela représente toutefois un certain nombre de compromis.
Principalement, l'optimisation du code (comme la plupart des autres problèmes d'optimisation) est en grande partie un problème NP-complet. Pour tout ce qui n'est pas un programme vraiment trivial/jouet, il est presque garanti que vous n'allez pas vraiment "optimiser" le résultat (c'est-à-dire que vous ne trouverez pas le véritable optimum) -- l'optimiseur va simplement rendre le code meilleur qu'elle ne l'était auparavant. Cependant, un certain nombre d'optimisations bien connues prennent beaucoup de temps (et souvent de la mémoire) pour être exécutées. Avec un compilateur JIT, l'utilisateur attend pendant que le compilateur s'exécute. La plupart des techniques d'optimisation les plus coûteuses sont exclues. La compilation statique présente deux avantages : tout d'abord, si elle est lente (par exemple, lors de la construction d'un grand système), elle est généralement exécutée sur un serveur, et personne passe son temps à l'attendre. Deuxièmement, un exécutable peut être généré une fois et utilisé de nombreuses fois par de nombreuses personnes. La première minimise le coût de l'optimisation ; la seconde amortit le coût beaucoup plus faible sur un nombre beaucoup plus important d'exécutions.
Comme mentionné dans la question initiale (et sur de nombreux autres sites web), la compilation JIT offre la possibilité de mieux connaître l'environnement cible, ce qui devrait (au moins théoriquement) compenser cet avantage. Il ne fait aucun doute que ce facteur peut compenser au moins en partie l'inconvénient de la compilation statique. Pour quelques types de code et d'environnements cibles plutôt spécifiques, il peut l'emportent même sur les avantages de la compilation statique, parfois de façon assez spectaculaire. Cependant, du moins dans mes tests et mon expérience, c'est assez inhabituel. Les optimisations dépendantes de la cible semblent le plus souvent soit faire des différences assez faibles, soit ne pouvoir être appliquées (automatiquement, en tout cas) qu'à des types de problèmes assez spécifiques. Les cas évidents où cela se produirait seraient si vous exécutiez un programme relativement ancien sur une machine moderne. Un vieux programme écrit en C++ a probablement été compilé en code 32 bits, et continuera à utiliser du code 32 bits même sur un processeur moderne 64 bits. Un programme écrit en C# aurait été compilé en code octet, que la VM aurait ensuite compilé en code machine 64 bits. Si ce programme tirait un avantage substantiel de son exécution en code 64 bits, cela pourrait donner un avantage substantiel. Pendant une courte période où les processeurs 64 bits étaient relativement nouveaux, cela s'est produit assez souvent. Le code récent qui est susceptible de bénéficier d'un processeur 64 bits sera généralement disponible compilé statiquement en code 64 bits.
L'utilisation d'une VM permet également d'améliorer l'utilisation du cache. Les instructions d'une VM sont souvent plus compactes que les instructions machine natives. Un plus grand nombre d'entre elles peuvent tenir dans une quantité donnée de mémoire cache, de sorte que vous avez plus de chances qu'un code donné se trouve dans le cache au moment voulu. Cela peut aider à maintenir l'exécution interprétée du code VM plus compétitive (en termes de vitesse) que ce à quoi la plupart des gens s'attendraient au départ -- vous pouvez exécuter un code lot d'instructions sur un CPU moderne dans le temps pris par un cache manquée.
Il convient également de mentionner que ce facteur n'est pas nécessairement pas du tout de différence entre les deux. Rien n'empêche (par exemple) un compilateur C++ de produire un résultat destiné à être exécuté sur une machine virtuelle (avec ou sans JIT). En fait, le C++/CLI de Microsoft est presque qui -- un compilateur C++ (presque) conforme (bien qu'avec beaucoup d'extensions) qui produit une sortie destinée à fonctionner sur une machine virtuelle.
L'inverse est également vrai : Microsoft propose désormais .NET Native, qui compile le code C# (ou VB.NET) en un exécutable natif. Cela permet d'obtenir des performances généralement plus proches de celles du C++, tout en conservant les caractéristiques de C#/VB (par exemple, C# compilé en code natif supporte toujours la réflexion). Si vous avez du code C# à haute performance, cela peut être utile.
Collecte des ordures
D'après ce que j'ai vu, je dirais que le ramassage des déchets est le plus mal compris de ces trois facteurs. Juste pour un exemple évident, la question ici mentionne : "GC n'ajoute pas non plus beaucoup de surcharge, à moins que vous ne créiez et détruisiez des milliers d'objets [...]". En réalité, si vous créez y détruire des milliers d'objets, les frais généraux liés au ramasse-miettes seront généralement assez faibles. .NET utilise un ramasse-miettes générationnel, qui est une variété de collecteur de copies. Le ramasseur de déchets fonctionne en partant des "endroits" (par exemple, les registres et la pile d'exécution) où les pointeurs/références sont connu sous le nom de pour être accessible. Il "poursuit" ensuite ces pointeurs vers les objets qui ont été alloués sur le tas. Il examine ces objets à la recherche d'autres pointeurs/références, jusqu'à ce qu'il les ait tous suivis jusqu'à la fin des chaînes et qu'il ait trouvé tous les objets qui sont (au moins potentiellement) accessibles. Dans l'étape suivante, il prend tous les objets qui sont (ou au moins pourrait être ) en cours d'utilisation, et compacte le tas en les copiant tous dans un morceau contigu à une extrémité de la mémoire gérée dans le tas. Le reste de la mémoire est alors libre (sauf si les finaliseurs doivent être exécutés, mais au moins dans du code bien écrit, ils sont suffisamment rares pour que je les ignore pour le moment).
Ce que cela signifie, c'est que si vous créez et détruire beaucoup d'objets, le ramassage des ordures n'ajoute que très peu de surcharge. La durée d'un cycle de ramassage des déchets dépend presque entièrement du nombre d'objets créés, mais no détruit. La principale conséquence de la création et de la destruction d'objets à la hâte est simplement que la GC doit s'exécuter plus souvent, mais chaque cycle sera toujours rapide. Si vous créez des objets et Ne le fais pas. les détruire, le GC fonctionnera plus souvent y chaque cycle sera sensiblement plus lent car il passe plus de temps à rechercher des pointeurs vers des objets potentiellement vivants, y il passe plus de temps à copier les objets qui sont encore utilisés.
Pour lutter contre ce phénomène, la récupération générationnelle part du principe que les objets que ont qui sont restés "en vie" pendant un certain temps sont susceptibles de continuer à rester en vie pendant un certain temps encore. En se basant sur cela, il a un système où les objets qui survivent à un certain nombre de cycles de ramassage des ordures deviennent "titulaires", et le ramasseur d'ordures commence à simplement supposer qu'ils sont toujours utilisés, donc au lieu de les copier à chaque cycle, il les laisse simplement tranquilles. C'est une hypothèse valable assez souvent pour que le scavenging générationnel ait généralement une surcharge considérablement plus faible que la plupart des autres formes de GC.
La gestion "manuelle" de la mémoire est souvent tout aussi mal comprise. Pour ne citer qu'un exemple, de nombreuses tentatives de comparaison supposent que toute gestion manuelle de la mémoire suit également un modèle spécifique (par exemple, l'allocation la mieux adaptée). Cette hypothèse n'est souvent pas plus proche de la réalité (si tant est qu'elle le soit) que les croyances de nombreuses personnes concernant le ramassage des ordures (par exemple, l'hypothèse répandue selon laquelle il est normalement effectué en utilisant le comptage de références).
Compte tenu de la diversité des stratégies de collecte des déchets. y gestion manuelle de la mémoire, il est assez difficile de comparer les deux en termes de vitesse globale. Tenter de comparer la vitesse d'allocation et/ou de libération de la mémoire (en soi) est pratiquement garanti pour produire des résultats qui, au mieux, ne veulent rien dire et, au pire, sont carrément trompeurs.
Sujet bonus : Points de repère
Étant donné que de nombreux blogs, sites web, articles de magazines, etc., prétendent fournir des preuves "objectives" dans un sens ou dans l'autre, je vais également apporter mon grain de sel à ce sujet.
La plupart de ces benchmarks sont un peu comme des adolescents qui décident de faire une course de voitures, et celui qui gagne peut garder les deux voitures. Les sites Web diffèrent cependant sur un point crucial : le type qui publie le benchmark a le droit de conduire les deux voitures. Par un étrange hasard, sa voiture gagne toujours, et tous les autres doivent se contenter de "faites-moi confiance, j'étais ". vraiment en conduisant ta voiture aussi vite qu'elle peut aller."
Il est facile d'écrire un mauvais benchmark qui produit des résultats qui ne veulent presque rien dire. Quiconque possède les compétences nécessaires pour concevoir un benchmark qui produise quelque chose de significatif, possède également les compétences pour en produire un qui donnera les résultats qu'il a décidé de vouloir. En fait, c'est probablement plus facile d'écrire du code pour produire un résultat spécifique que du code qui produira réellement des résultats significatifs.
Comme l'a dit mon ami James Kanze, "ne faites jamais confiance à une référence que vous n'avez pas falsifiée vous-même".
Conclusion
Il n'y a pas de réponse simple. Je suis raisonnablement certain que je pourrais tirer à pile ou face pour choisir le gagnant, puis choisir un nombre entre (disons) 1 et 20 pour le pourcentage de victoire, et écrire un code qui ressemblerait à un benchmark raisonnable et juste, et qui produirait cette conclusion courue d'avance (au moins sur un processeur cible - un processeur différent pourrait changer un peu le pourcentage).
Comme d'autres l'ont souligné, pour le plus code, la vitesse est presque sans importance. Le corollaire de cela (qui est bien plus souvent ignoré) est que dans le peu de code où la vitesse est importante, elle est généralement d'une grande importance. lot . Au moins dans mon expérience, pour le code où cela compte vraiment, C++ est presque toujours le gagnant. Il y a certainement des facteurs qui favorisent C#, mais dans la pratique, ils semblent être plus importants que les facteurs qui favorisent C++. Vous pouvez certainement trouver des benchmarks qui indiqueront le résultat de votre choix, mais lorsque vous écrivez du code réel, vous pouvez presque toujours le rendre plus rapide en C++ qu'en C#. Cela peut (ou non) demander plus de compétences et/ou d'efforts d'écriture, mais c'est pratiquement toujours possible.
0 votes
Parce que C# est beaucoup plus facile à utiliser que C++, surtout en ce qui concerne l'interface graphique.
0 votes
@Brain pouvez-vous expliquer "depends", je ne l'ai pas compris ?
3 votes
Vraiment... Ca dépend... Certaines choses sont plus rapides, d'autres plus lentes. Le C/C++ est plus "déterministe" (pas de ramasse-miettes dans le dos). Si vous voulez créer 100 threads, je peux vous dire que le GC vous hantera par sa lenteur (et avant de me dire que 100 threads sont trop nombreux, sachez que Skype et l'AV de McAfee sont chacun à 40 threads maintenant sur mon PC)... Marshaling en C# est une douleur (et c'est plus lent). Le codage est assez rapide. Non, ce n'est pas une attaque. Je préfère vraiment C#.
1 votes
Je pense que cette question devrait être modifiée et rouverte. Je pense que l'auteur de la question ne comprend peut-être pas qu'une question plus fondamentale a un impact sur les différences de performances entre C# et des langages comme C ou C++, qui serait "Quelle est la différence entre le code généré (et la façon dont il est exécuté) par un compilateur C et le code généré par le compilateur C# ?" C#/Java et d'autres langages interprétés ou VM peuvent avoir plusieurs étapes intermédiaires lors de l'exécution de leur bytecode qui sont totalement absentes dans un programme C équivalent.
0 votes
Si vous voulez un exemple concret, j'ai récemment dû porter un algorithme C++ assez important en C#. Je l'ai fait ligne par ligne, en traduisant les constructions/structures C++ en équivalents C# (par exemple, map --> sortedlist etc.). Eh bien, bien que je m'attendais à ce que c++ soit plus rapide, ce n'était pas le cas. Le C# était légèrement plus rapide, mais je peux dire que les performances de base étaient les mêmes. (Et BTW le projet C++ était bien écrit, pas de fuites de mémoire etc...). Donc, C/C++ plus rapide que C# n'est pas une vérité absolue...
3 votes
Pendant que vous pourrait soutenir que (comme indiqué) cela peut être qualifié de "pas une vraie question", prétendre que c'est subjectif est absurde. "Est-ce que A est plus rapide que B" est une question qui peut être mesurée objectivement. Même si la réponse obtenue par cette mesure est ambiguë (certaines choses sont plus rapides dans l'un, d'autres dans l'autre), elle reste objective.
0 votes
@Jerry : Je suis d'accord avec vous, et j'ai donc voté pour la réouverture du sujet !
0 votes
Je suis au courant du code intermédiaire. C'est pourquoi je n'ai pas posé de question à ce sujet.
0 votes
Ce sujet a fait l'objet de nombreuses discussions et de nombreuses réponses erronées. J'ai donc pensé à le poser ici et à voir ce que nous trouverons :) ... Je suis heureux de voir qu'il est resté ouvert.
1 votes
Ver le jeu de référence des langages informatiques pour une comparaison approfondie de nombreux langages sur de nombreux types de problèmes différents.
0 votes
L'utilisation de C++ ne permet pas automatiquement d'obtenir un programme plus rapide. Le C++ vous donne un bien meilleur contrôle sur les caractéristiques de performance de votre programme, ce qui vous permet d'écrire un programme plus rapide, mais cela peut représenter beaucoup de travail.