Pourquoi les langues de haut niveau ne semblent-elles jamais atteindre des langues de niveau inférieur en termes de vitesse? Des exemples de langages de haut niveau seraient Python, Haskell et Java. Les langages de bas niveau seraient plus difficiles à définir, mais disons que C. Des comparaisons peuvent être trouvées partout sur Internet et ils conviennent tous que C est beaucoup plus rapide, parfois d'un facteur 10 ou plus.
Qu'est-ce qui cause une si grande différence de performances et pourquoi les langages de haut niveau ne peuvent-ils pas rattraper leur retard?
Au début, je pensais que tout cela était la faute des compilateurs et que les choses s'amélioreraient à l'avenir, mais certaines des langues de niveau supérieur les plus populaires existent depuis des décennies et elles sont toujours à la traîne en matière de vitesse. Ne peuvent-ils pas simplement compiler dans un arbre de syntaxe similaire à C, puis suivre les mêmes procédures qui génèrent le code machine? Ou peut-être est-ce lié à la syntaxe elle-même?
Exemples:
Réponses:
Démystifier certains mythes
Il n'y a pas de langage rapide. Un langage peut généralement produire du code rapide, mais différents langages excelleront sur différents benchmarks. Nous pouvons classer les langues sur un ensemble particulier de repères défectueux, mais nous ne pouvons pas classer les langues dans le vide.
Le code C a tendance à être plus rapide car les personnes qui ont besoin de chaque pouce de performance utilisent C. Une statistique selon laquelle C est plus rapide "par un facteur de 10" peut être inexacte, car il se peut que les personnes utilisant Python s'en foutent tout autant sur la vitesse et n'a pas écrit de code Python optimal. Nous le voyons en particulier avec des langues comme Haskell. Si vous essayez vraiment dur , vous pouvez écrire Haskell qui fonctionne à égalité avec C. Mais la plupart des gens n'ont pas besoin de cette performance, nous avons donc un tas de comparaisons imparfaites.
Parfois, c'est l'insécurité, pas l'abstraction, qui rend le C rapide. Son manque de limites de tableau et de vérifications de pointeur nul permet de gagner du temps et a été à l'origine de nombreuses failles de sécurité au fil des ans.
Les langages ne sont pas rapides, les implémentations sont rapides. De nombreux langages abstraits commencent lentement, car la vitesse n'est pas leur objectif, mais s'accélèrent à mesure que de plus en plus d'optimisations sont ajoutées.
Le compromis abstraction vs vitesse est peut-être inexact. Je suggérerais une meilleure comparaison:
Simplicité, vitesse, abstraction: choisissez-en deux.
Si nous exécutons des algorithmes identiques dans différentes langues, la question de la vitesse se résume au problème de "combien de choses devons-nous faire au moment de l'exécution pour que cela fonctionne?"
Dans un langage très abstrait qui est simple , comme Python ou JavaScript, parce que nous ne connaissons pas la représentation des données avant l'exécution, il y a beaucoup de dé-références de pointeurs et de vérifications dynamiques qui se produisent lors de l'exécution, qui sont lentes.
De même, de nombreuses vérifications doivent être effectuées pour vous assurer que vous ne détruisez pas votre ordinateur. Lorsque vous appelez une fonction en python, il doit s'assurer que l'objet que vous appelez est bien une fonction, car sinon vous pourriez finir par exécuter du code aléatoire qui fait des choses terribles.
Enfin, la plupart des langages abstraits ont des frais généraux provenant de la récupération de place. La plupart des programmeurs ont convenu que devoir suivre les durées de vie de la mémoire allouée dynamiquement est une douleur, et ils préfèrent qu'un garbage collector le fasse pour eux au moment de l'exécution. Cela prend du temps, qu'un programme C n'a pas besoin de dépenser pour GC.
Abstrait ne signifie pas lent
Cependant, il existe des langages à la fois abstraits et rapides. La plus dominante à cette époque est la rouille. En introduisant un vérificateur d'emprunt et un système de type sophistiqué, il permet le code abstrait et utilise les informations de compilation pour réduire la quantité de travail que nous devons faire au moment de l'exécution (c'est-à-dire la collecte des ordures).
Tout langage de type statique nous fait gagner du temps en réduisant le nombre de vérifications d'exécution et introduit de la complexité en nous obligeant à plaire à un vérificateur de typage au moment de la compilation. De même, si nous avons un langage qui code si une valeur peut être nulle dans son système de type, nous pouvons gagner du temps avec des vérifications de pointeur nul de temps de compilation.
la source
voici quelques idées clés à ce sujet.
une comparaison / étude de cas facile pour faire une abstraction par rapport à la vitesse est java vs c ++. java a été conçu pour résumer certains des aspects de bas niveau du c ++ tels que la gestion de la mémoire. au début (autour de l'invention de la langue au milieu des années 1990), la détection des déchets java n'était pas très rapide, mais après quelques décennies de recherche, les collecteurs de déchets sont extrêmement affinés / rapides / optimisés, de sorte que les collecteurs de déchets sont largement éliminés en tant que drain de performance sur java. par exemple, voir même ce titre de 1998: les tests de performances montrent Java aussi vite que C ++ / javaworld
Les langages de programmation et leur longue évolution ont une "structure pyramidale / hiérarchique" inhérente comme une sorte de modèle de conception transcendant. au sommet de la pyramide est quelque chose qui contrôle les autres sections inférieures de la pyramide. en d'autres termes, les blocs de construction sont constitués de blocs de construction. cela peut également être vu dans la structure de l'API. en ce sens, une plus grande abstraction conduit toujours à un nouveau composant au sommet de la pyramide contrôlant d'autres composants. dans un sens, ce n'est pas tellement que toutes les langues sont au même niveau, mais que les langues font appel à des routines dans d'autres langues. Par exemple, de nombreux langages de script (python / ruby) appellent souvent des bibliothèques C ou C ++, les routines numériques ou matricielles en sont un exemple typique. il y a donc des langues de niveau supérieur et de niveau inférieur et les langues de haut niveau appellent des langues de niveau inférieur pour ainsi dire. en ce sens, mesurer la vitesse relative n'est pas vraiment comparable.
on pourrait dire que de nouveaux langages sont constamment inventés pour essayer d'optimiser le compromis abstraction / vitesse, c'est-à-dire son objectif clé de conception. peut-être que ce n'est pas tant qu'une plus grande abstraction sacrifie toujours la vitesse, mais qu'un meilleur équilibre est toujours recherché avec des conceptions plus récentes. Par exemple, Google Go a été à bien des égards spécifiquement optimisé avec le compromis à l'esprit, pour être à la fois de haut niveau et performant. voir par exemple Google Go: pourquoi le langage de programmation de Google peut rivaliser avec Java dans l'entreprise / le monde technologique
la source
La façon dont j'aime penser la performance est "où le caoutchouc rencontre la route". L'ordinateur exécute des instructions, pas des abstractions.
Ce que je veux voir, c'est ceci: chaque instruction exécutée "gagne-t-elle son lot" en contribuant substantiellement au résultat final? À titre d'exemple trop simple, envisagez de rechercher une entrée dans une table de 1024 entrées. C'est un problème de 10 bits car le programme doit "apprendre" 10 bits avant de connaître la réponse. Si l'algorithme est une recherche binaire, chaque itération fournit 1 bit d'information, car elle réduit l'incertitude d'un facteur 2. Il faut donc 10 itérations, une pour chaque bit.
La recherche linéaire, en revanche, est initialement très inefficace car les premières itérations réduisent l'incertitude d'un très petit facteur. Ils n'apprennent donc pas grand-chose pour l'effort déployé.
OK, donc si le compilateur peut permettre à l'utilisateur d'envelopper de bonnes instructions d'une manière considérée comme "abstraite", c'est très bien.
la source
De par sa nature, l'abstraction réduit la communication des informations, tant au programmeur qu'aux couches inférieures du système (le compilateur, les bibliothèques et le système d'exécution). En faveur de l'abstraction, cela permet généralement aux couches inférieures de supposer que le programmeur n'est concerné par aucun comportement non spécifié, offrant une plus grande flexibilité dans la fourniture du comportement spécifié.
Un exemple d'un avantage potentiel de cet aspect "ne se soucie pas" est dans la disposition des données. En C (abstraction faible), le compilateur est plus contraint dans les optimisations de mise en page des données. Même si le compilateur pouvait discerner (par exemple, à travers des informations de profil) que des optimisations évitant le partage chaud / froid ou faux seraient bénéfiques, il est généralement empêché de les appliquer. (Il y a une certaine liberté à spécifier "comme si", c'est-à-dire à traiter la spécification de manière plus abstraite, mais dériver tous les effets secondaires potentiels alourdit le compilateur.)
Une spécification plus abstraite est également plus robuste contre les changements de compromis et d'utilisations. Les couches inférieures sont moins contraintes à réoptimiser le programme pour de nouvelles caractéristiques du système ou de nouvelles utilisations. Une spécification plus concrète doit être réécrite par un programmeur ou un effort supplémentaire doit être fait par les couches inférieures pour garantir un comportement "comme si".
L'aspect nuisant aux performances de l'abstraction masquant des informations est «ne peut pas exprimer», que les couches inférieures traiteront généralement comme «ne sait pas». Cela signifie que les couches inférieures doivent discerner les informations utiles à l'optimisation à partir d'autres moyens tels qu'une utilisation générale typique, une utilisation ciblée ou des informations de profil spécifiques.
L'impact de la dissimulation d'informations fonctionne également dans l'autre sens. Le programmeur peut être plus productif en ne devant pas considérer et spécifier chaque détail, mais le programmeur peut avoir moins d'informations sur l'impact des choix de conception de niveau supérieur.
D'un autre côté, lorsque le code est plus spécifique (moins abstrait), les couches inférieures du système peuvent plus simplement faire ce qu'on leur dit de faire comme on leur dit de le faire. Si le code est bien écrit pour son utilisation ciblée, il s'adaptera bien à son utilisation ciblée. Un langage moins abstrait (ou paradigme de programmation) permet au programmeur d'optimiser la mise en œuvre par une conception détaillée et par l'utilisation d'informations qui ne sont pas facilement communiquées dans un langage donné aux couches inférieures.
Comme cela a été noté, les langages moins abstraits (ou techniques de programmation) sont attrayants lorsque des compétences et des efforts supplémentaires du programmeur peuvent produire des résultats intéressants. Lorsque plus d'efforts et de compétences sont programmés, les résultats seront généralement meilleurs. De plus, un système de langage qui est moins utilisé pour les applications critiques pour les performances (au lieu de mettre l'accent sur l'effort de développement ou la fiabilité - les vérifications des limites et la collecte des ordures ne concernent pas seulement la productivité du programmeur mais la correction, l'abstraction réduisant la charge mentale du programmeur peut améliorer la fiabilité) aura moins de pression pour améliorer les performances.
La spécificité va également à l'encontre du principe de ne pas se répéter, car l'optimisation est généralement possible en adaptant le code à une utilisation spécifique. Cela a des implications évidentes en termes de fiabilité et d'effort de programmation.
Les abstractions fournies par une langue peuvent également inclure un travail indésirable ou inutile sans aucun moyen de choisir une abstraction moins lourde. Bien que des travaux inutiles puissent parfois être découverts et supprimés par les couches inférieures (par exemple, les vérifications des limites peuvent être extraites du corps d'une boucle et entièrement supprimées dans certains cas), déterminer qu'il s'agit d'une optimisation valide nécessite plus de «compétence et d'efforts» de la part de le compilateur.
L'âge et la popularité de la langue sont également des facteurs notables à la fois dans la disponibilité de programmeurs qualifiés et dans la qualité des couches inférieures du système (y compris les bibliothèques matures et les exemples de code).
Un autre facteur de convergence dans de telles comparaisons est la différence quelque peu orthogonale entre la compilation à l'avance et la compilation juste à temps. Alors que la compilation juste à temps peut plus facilement exploiter les informations de profil (ne pas compter sur le programmeur pour fournir des exécutions de profil) et l'optimisation spécifique au système (la compilation anticipée peut cibler une compatibilité plus large), la surcharge de l'optimisation agressive est comptabilisée comme partie des performances d'exécution. Les résultats JIT peuvent être mis en cache, ce qui réduit la surcharge pour le code couramment utilisé. (L'alternative de la réoptimisation binaire peut offrir certains avantages de la compilation JIT, mais les formats de distribution binaires traditionnels suppriment la plupart des informations de code source, forçant potentiellement le système à tenter de discerner l'intention d'une implémentation spécifique.)
(Les langages d'abstraction inférieurs, en raison de leur accent sur le contrôle du programmateur, favorisent l'utilisation de la compilation anticipée. La compilation au moment de l'installation peut être tolérée, bien que la sélection de l'implémentation de la liaison fournisse un meilleur contrôle du programmeur. La compilation JIT sacrifie le contrôle significatif. )
Il y a aussi la question de la méthodologie de référence. Un effort / compétence égal est effectivement impossible à établir, mais même si cela pouvait être atteint, les objectifs linguistiques fausseraient les résultats. Si un temps de programmation maximum faible était requis, un programme pour un langage moins abstrait pourrait même ne pas être complètement écrit par rapport à une simple expression idiomatique dans un langage plus abstrait. Si un temps / effort de programmation maximum élevé était autorisé, les langages à abstraction inférieure auraient un avantage. Les repères présentant les meilleurs résultats seraient naturellement biaisés en faveur de langages moins abstraits.
Il est parfois possible de programmer de manière moins idiomatique dans un langage pour bénéficier des avantages d'autres paradigmes de programmation, mais même lorsque le pouvoir expressif est disponible, les compromis pour le faire peuvent ne pas être favorables.
la source