En regardant la page Web de Julia , vous pouvez voir quelques points de repère de plusieurs langues à travers plusieurs algorithmes (timings montrés ci-dessous). Comment un langage avec un compilateur écrit à l'origine en C peut-il surpasser le code C?
Figure: temps de référence par rapport à C (plus petit est bon, meilleur est C = 1,0).
programming-languages
compilers
efficiency
Programmeur en difficulté
la source
la source
Réponses:
Il n'y a pas de relation nécessaire entre l'implémentation du compilateur et la sortie du compilateur. Vous pouvez écrire un compilateur dans un langage tel que Python ou Ruby, dont les implémentations les plus courantes sont très lentes, et ce compilateur pourrait générer un code machine hautement optimisé capable de surperformer C. Le compilateur lui-même prendrait beaucoup de temps à s'exécuter, car sonle code est écrit dans une langue lente. (Pour être plus précis, écrit dans un langage avec une implémentation lente. Les langages ne sont pas vraiment foncièrement rapides ou lents, comme le souligne Raphael dans un commentaire. Je développe cette idée ci-dessous.) Le programme compilé serait aussi rapide que son propre implémentation autorisée - nous pourrions écrire un compilateur en Python qui génère le même code machine qu'un compilateur Fortran, et nos programmes compilés seraient aussi rapides que Fortran, même s’ils prendraient beaucoup de temps à compiler.
C'est une autre histoire si nous parlons d'un interprète. Les interprètes doivent être en cours d'exécution pendant l'exécution du programme qu'ils interprètent. Il existe donc un lien entre le langage dans lequel l'interpréteur est implémenté et les performances du code interprété. Un langage interprété fonctionnant plus rapidement que le langage dans lequel l'interpréteur est implémenté nécessite une optimisation d'exécution astucieuse, et les performances finales peuvent dépendre de la mesure dans laquelle un élément de code est susceptible de ce type d'optimisation. De nombreux langages, tels que Java et C #, utilisent des exécutions avec un modèle hybride qui combine certains des avantages des interprètes avec ceux des compilateurs.
À titre d'exemple concret, examinons de plus près Python. Python a plusieurs implémentations. Le plus courant est CPython, un interpréteur de bytecode écrit en C. Il existe également PyPy, qui est écrit dans un dialecte spécialisé de Python appelé RPython, qui utilise un modèle de compilation hybride un peu comme la machine virtuelle Java. PyPy est beaucoup plus rapide que CPython dans la plupart des tests. il utilise toutes sortes de trucs incroyables pour optimiser le code au moment de l'exécution. Cependant, le langage Python utilisé par PyPy est exactement le même que celui utilisé par CPython, à l’exception de quelques différences qui n’affectent pas les performances.
Supposons que nous ayons écrit un compilateur en langage Python pour Fortran. Notre compilateur produit le même code machine que GFortran. Maintenant, nous compilons un programme Fortran. Nous pouvons exécuter notre compilateur sur CPython ou sur PyPy, car il est écrit en Python et que ces deux implémentations utilisent le même langage Python. Nous trouverons que si nous utilisons notre compilateur sur CPython, puis sur PyPy, puis compilons le même source Fortran avec GFortran, nous obtiendrons exactement le même code machine à trois reprises, de sorte que le programme compilé sera toujours exécuté. à peu près à la même vitesse. Cependant, le temps nécessaire pour produire ce programme compilé sera différent. CPython prendra probablement plus de temps que PyPy, et PyPy prendra plus de temps que GFortran, même si tous produiront le même code machine à la fin.
En parcourant le tableau des points de repère du site Web de Julia, il semble qu'aucune des langues exécutées sur les interprètes (Python, R, Matlab / Octave, Javascript) n'ait de repère où ils battent C. C'est généralement conforme à ce que j'attendais de voir, Bien que je puisse imaginer du code écrit avec la bibliothèque hautement optimisée Numpy de Python (écrite en C et Fortran) battant certaines implémentations C possibles d’un code similaire. Les langages égaux ou supérieurs à C sont en cours de compilation (Fortran, Julia ) ou utilisent un modèle hybride avec compilation partielle (Java et probablement LuaJIT). PyPy utilise également un modèle hybride. Il est donc tout à fait possible que si nous utilisions le même code Python sur PyPy au lieu de CPython, nous le verrions en réalité battre le C sur certains points de repère.
la source
Comment une machine construite par un homme peut-elle être plus forte qu'un homme? C'est exactement la même question.
La réponse est que la sortie du compilateur dépend des algorithmes mis en œuvre par ce compilateur, et non de la langue utilisée pour l'implémenter. Vous pourriez écrire un compilateur très lent et inefficace qui produit un code très efficace. Un compilateur n'a rien de spécial: il s'agit simplement d'un programme qui prend des entrées et produit des sorties.
la source
Je veux faire un point contre une hypothèse commune qui, à mon avis, est fallacieuse au point d’être préjudiciable au moment de choisir les outils d’un travail.
Il n’existe pas de langage lent ou rapide. ¹
En route vers la CPU, il y a plusieurs étapes ².
Chaque élément contribue à la durée d'exécution réelle que vous pouvez mesurer, parfois de manière importante. Différentes "langues" se concentrent sur différentes choses³.
Juste pour donner quelques exemples.
1 vs 2-4 : un programmeur C moyen produira probablement un code bien pire qu'un programmeur Java moyen, à la fois en termes de correction et d'efficacité. C'est parce que le programmeur a plus de responsabilités en C.
1/4 vs 7 : dans un langage de bas niveau comme le C, vous pourrez peut-être exploiter certaines fonctionnalités de la CPU en tant que programmeur . Dans les langages de niveau supérieur, seul le compilateur / interprète peut le faire, et uniquement s'il connaît le processeur cible.
1/4 vs 5 : voulez-vous ou devez-vous contrôler la disposition de la mémoire afin de mieux utiliser l'architecture de la mémoire? Certaines langues vous permettent de contrôler cela, d'autres non.
2/4 vs 3 : Python interprété lui-même est horriblement lent, mais il existe des liaisons populaires pour des bibliothèques hautement optimisées, compilées de manière native pour le calcul scientifique. Donc, faire certaines choses en Python est rapide au final, si l'essentiel du travail est fait par ces bibliothèques.
2 vs 4 : l'interpréteur Ruby standard est assez lent. JRuby, en revanche, peut être très rapide. C'est la même langue est rapide en utilisant un autre compilateur / interprète.
1/2 vs 4 : En utilisant les optimisations du compilateur, un code simple peut être traduit en code machine très efficace.
En fin de compte, le critère que vous avez trouvé n’a pas beaucoup de sens, du moins pas lorsque vous le présentez. Même si tout ce qui vous intéresse, c'est le temps d'exécution, vous devez spécifier la chaîne entière du programmeur au processeur; échanger n'importe lequel des éléments peut changer radicalement les résultats.
Pour être clair, cela répond à la question parce que cela montre que le langage dans lequel le compilateur (étape 4) est écrit n’est qu’un élément du puzzle, et n’est probablement pas pertinent du tout (voir autres réponses).
Je n'entre délibérément pas dans des mesures de réussite différentes: efficacité du temps d'exécution, efficacité de la mémoire, temps du développeur, sécurité, sécurité (justifiable (prouvable?), Support des outils, indépendance de la plate-forme, ...
La comparaison de langues avec une métrique, même si elles ont été conçues pour des objectifs complètement différents, est une grave erreur.
la source
Il y a une chose oubliée à propos de l'optimisation ici.
Il y avait un long débat sur la performance de Fortran par rapport à C. Mise à part un débat mal formé: le même code était écrit en C et en Fortran (comme le pensaient les testeurs) et les performances étaient testées sur la base des mêmes données. Le problème, c’est que ces langues diffèrent, C autorise le crénelage des pointeurs, contrairement à Fortran.
Donc les codes n'étaient pas les mêmes, il n'y avait pas de __restrict dans les fichiers testés en C, ce qui donnait des différences, après la réécriture des fichiers pour indiquer au compilateur qu'il peut optimiser les pointeurs, les runtimes deviennent similaires.
Le point ici est que certaines techniques d'optimisation sont plus faciles (ou commencent à être légales) dans un langage nouvellement créé.
En outre , il est possible à long terme à VM avec JIT surperformer C. Il existe deux possibilités:X
code JIT peut profiter de la machine qu'il l' héberge (par exemple , certains SSE ou une autre exclusive pour certaines instructions CPU vectorisé) qui ne sont pas mis en œuvre programme comparé.
Deuxièmement, VM peut effectuer un test de pression en cours d’exécution, afin de prendre le code sous pression et de l’optimiser ou même le précalculer au cours de l’exécution. À l’avance, le programme C compilé ne s’attend pas à savoir où est la pression ni (la plupart du temps), il existe des versions génériques d’exécutables pour la famille générale de machines.
Dans ce test, il existe également JS, ainsi que des machines virtuelles plus rapides que V8, et il est également plus rapide que C dans certains tests.
Je l'ai vérifié, et il y avait des techniques d'optimisation uniques non encore disponibles dans les compilateurs C.
Le compilateur C devrait faire une analyse statique de tout le code à la fois, marcher sur une plateforme donnée et contourner les problèmes d’alignement de la mémoire.
VM a simplement translittéré une partie du code pour optimiser l’assemblage et l’exécuter.
À propos de Julia - comme je l’ai vérifié, cela fonctionne sur du code AST, par exemple, GCC a ignoré cette étape et a récemment commencé à prendre des informations à partir de là. Ceci, ajouté à d’autres contraintes et techniques de machine virtuelle, pourrait en expliquer un peu.
Exemple: prenons une boucle simple, qui prend le point final de fin à partir de variables et charge une partie des variables dans les calculs connus au moment de l'exécution.
Le compilateur C génère des variables de chargement à partir de registres.
Mais au moment de l'exécution, ces variables sont connues et traitées comme des constantes lors de l'exécution.
Ainsi, au lieu de charger des variables à partir de registres (et de ne pas effectuer la mise en cache car cela peut changer, et d'après l'analyse statique, ce n'est pas clair), elles sont traitées intégralement comme des constantes et repliées et propagées.
la source
Les réponses précédentes donnent à peu près l'explication, bien que principalement d'un point de vue pragmatique, dans la mesure où la question a un sens , comme l'a parfaitement expliqué la réponse de Raphaël .
Ajoutant à cette réponse, il convient de noter que, de nos jours, les compilateurs C sont écrits en C. Bien sûr, comme le note Raphael, leur sortie et ses performances peuvent dépendre, entre autres choses, du processeur sur lequel il est exécuté. Mais cela dépend aussi de la quantité d'optimisation effectuée par le compilateur. Si vous écrivez en C un meilleur compilateur d'optimisation pour C (que vous compilez ensuite avec l'ancien pour pouvoir l'exécuter), vous obtenez un nouveau compilateur qui fait du C un langage plus rapide qu'auparavant. Alors, quelle est la vitesse de C? Notez que vous pouvez même compiler le nouveau compilateur avec lui-même, en tant que deuxième passe, afin de compiler plus efficacement, tout en fournissant le même code objet. Et le théorème du plein emploi montre qu’il n’ya pas de fin à de telles améliorations (merci à Raphaël pour le pointeur).
Mais je pense qu'il peut être intéressant d'essayer de formaliser la question, car elle illustre très bien certains concepts fondamentaux, notamment la vision dénotationnelle ou opérationnelle des choses.
Qu'est-ce qu'un compilateur?
Un compilateur , abrégé en s'il n'y a pas d'ambiguïté, est la réalisation d'une fonction calculable qui traduira un texte de programme calculant une fonction , écrit dans une langue source dans le texte du programme écrit dans une langue cible , qui est censé calculer la même fonction .CS→T C CS→T P:S P S P:T T P
D'un point de vue sémantique, c'est-à-dire dénotant , peu importe comment cette fonction de compilation est calculée, c'est-à-dire quelle réalisation est choisie. Cela pourrait même être fait par un oracle magique. Mathématiquement, la fonction est simplement un ensemble de paires .CS→T CS→T {(P:S,P:T)∣PS∈S∧PT∈T}
La fonction de compilation sémantique est correct si les deux et calculent la même fonction . Mais cette formalisation s'applique également à un compilateur incorrect. Le seul point est que tout ce qui est mis en œuvre aboutit au même résultat indépendamment des moyens utilisés. Ce qui compte sémantiquement, c’est ce qui est fait par le compilateur, et non comment (et à quelle vitesse) cela est fait.CS→T PS PT P
Obtenir réellement de est un problème opérationnel , qui doit être résolu. C'est pourquoi la fonction de compilation doit être une fonction calculable. Alors, toute langue avec le pouvoir de Turing, peu importe sa lenteur, sera certainement en mesure de produire un code aussi efficace que toute autre langue, même si elle le fait moins efficacement.P:T P:S CS→T
En affinant l’argument, nous voulons probablement que le compilateur ait une bonne efficacité, afin que la traduction puisse être effectuée dans un délai raisonnable. La performance du programme de compilation est donc importante pour les utilisateurs, mais elle n’a aucun impact sur la sémantique. Je parle de performance, car la complexité théorique de certains compilateurs peut être bien supérieure à ce à quoi on pourrait s’attendre.
À propos de l'amorçage
Cela illustrera la distinction et montrera une application pratique.
Il est maintenant courant de commencer par implémenter un langage avec un interprète , puis d’écrire un compilateur dans le langage lui-même. Ce compilateur peut être exécuté avec l'interpréteur pour traduire tout programme en programme . Nous avons donc un compilateur en cours d’exécution du langage au langage (machine?) , mais il est très lent, ne serait-ce que parce qu’il s’appuie sur un interprète.I S C S → TS IS S C S → TCS→T:S S I S P : S P : T STCS→T:S IS P:S P:T S T
Mais vous pouvez utiliser cette fonction de compilation pour compiler le compilateur , car il est écrit en langage , et vous obtenez ainsi un compilateur écrit en la langue cible . Si vous assumez, comme souvent le cas, que est une langue qui est plus efficacement interprété (natif de la machine, par exemple), vous obtenez une version plus rapide de votre compilateur directement dans le langage courant . Il fait exactement le même travail (c.-à-d. Produit les mêmes programmes cibles), mais le fait plus efficacement. S C S → TCS→T:S S TTTCS→T:T T T T
la source
Selon le théorème d'accélération de Blum, certains programmes écrits et exécutés sur la combinaison ordinateur / compilateur la plus rapide s'exécutent plus lentement qu'un programme identique sur votre premier PC exécutant BASIC interprété. Il n'y a tout simplement pas de "langage le plus rapide". Tout ce que vous pouvez dire, c’est que si vous écrivez le même algorithme dans plusieurs langages (implémentations; comme indiqué, il existe de nombreux compilateurs C, et j’ai même rencontré un interpréteur C assez capable), il fonctionnera plus vite ou plus lentement dans chaque langage. .
Il ne peut y avoir de hiérarchie "toujours plus lente". C’est un phénomène connu de tous les gens qui maîtrisent plusieurs langues: chaque langage de programmation a été conçu pour un type d’application spécifique et les implémentations les plus utilisées ont été amoureusement optimisées pour ce type de programme. Je suis à peu près sûr qu'un programme permettant de bricoler avec des chaînes écrites en Perl battra probablement le même algorithme que celui écrit en C, alors qu'un programme grignotant de grands tableaux d'entiers en C sera plus rapide que Perl.
la source
Revenons à la ligne d'origine: "Comment un langage dont le compilateur est écrit en C peut-il être plus rapide que C?" Je pense que cela voulait vraiment dire: comment un programme écrit en Julia, dont le cœur est écrit en C, peut-il être plus rapide qu'un programme écrit en C? En particulier, comment le programme "mandel", tel qu’écrit en Julia, peut-il s’exécuter dans 87% du temps d’exécution du programme "mandel" équivalent écrit en C?
Le traité de Babou est la seule réponse correcte à cette question jusqu'à présent. Jusqu'à présent, toutes les autres réponses répondent plus ou moins à d'autres questions. Le problème avec le texte de babou est que la description théorique de "Qu'est-ce qu'un compilateur", longue de plusieurs paragraphes, est rédigée dans des termes que l'affiche originale aura probablement du mal à comprendre. Quiconque saisit les concepts mentionnés par les mots "sémantique", "dénotationellement", "réalisation", "calculable", etc., connaît déjà la réponse à la question.
La réponse la plus simple est que ni le code C ni le code de Julia ne sont directement exécutables par la machine. Les deux doivent être traduits, et ce processus de traduction introduit de nombreuses manières pour que le code machine exécutable puisse être plus lent ou plus rapide, tout en produisant le même résultat final. C et Julia font tous les deux une compilation, ce qui signifie une série de traductions sous une autre forme. Généralement, un fichier texte lisible par un humain est traduit en une représentation interne, puis écrit sous la forme d'une séquence d'instructions que l'ordinateur peut comprendre directement. Avec certaines langues, cela ne se résume pas à cela, et Julia en fait partie: elle dispose d'un compilateur "JIT", ce qui signifie que le processus de traduction ne doit pas nécessairement se dérouler en même temps pour l'ensemble du programme. Mais le résultat final pour toute langue est un code machine qui ne nécessite aucune autre traduction, code qui peut être envoyé directement à la CPU pour lui faire faire quelque chose. À la fin, CECI est le "calcul", et il existe plus d’une façon de dire à un processeur comment obtenir la réponse que vous souhaitez.
On pourrait imaginer un langage de programmation comportant à la fois un opérateur "plus" et un opérateur "multipliant", et un autre langage n'ayant que "plus". Si votre calcul nécessite une multiplication, une langue sera "plus lente" car bien sûr, le processeur peut faire les deux directement, mais si vous n'avez aucun moyen d'exprimer le besoin de multiplier 5 * 5, il vous reste à écrire "5 + 5 + 5 + 5 + 5 ". Ce dernier prendra plus de temps pour arriver à la même réponse. Vraisemblablement, il y a une partie de ce qui se passe avec Julia; peut-être que le langage permet au programmeur d’énoncer l’objectif souhaité du calcul d’un ensemble de Mandelbrot d’une manière qu’il n’est pas possible d’exprimer directement en C.
Le processeur utilisé pour le test de référence était répertorié en tant que processeur Xeon E7-8850 2,00 GHz. Le benchmark C a utilisé le compilateur gcc 4.8.2 pour produire des instructions pour ce processeur, tandis que Julia a utilisé le framework de compilateur LLVM. Il est possible que le backend de gcc (la partie qui produit le code machine pour une architecture de processeur particulière) ne soit pas aussi avancé que le backend de LLVM. Cela pourrait faire une différence dans les performances. Il y a aussi beaucoup d'autres choses en cours - le compilateur peut "optimiser" en émettant peut-être des instructions dans un ordre différent de celui spécifié par le programmeur, ou même en ne faisant rien du tout s'il peut analyser le code et déterminer qu'il ne l'est pas. nécessaire pour obtenir la bonne réponse. Et le programmeur aurait peut-être écrit une partie du programme C de manière à le ralentir, mais ne l'a pas fait.
Toutes ces choses sont des façons de dire: il existe de nombreuses façons d'écrire du code machine pour calculer un ensemble de Mandelbrot, et le langage que vous utilisez a un effet majeur sur la façon dont le code machine est écrit. Plus vous en comprendrez sur la compilation, les jeux d'instructions, les caches, etc., mieux vous serez en mesure d'obtenir les résultats souhaités. Le principal résultat tiré des résultats de référence cités pour Julia est qu’aucun langage ou outil n’est meilleur dans tous les domaines. En fait, le meilleur facteur de vitesse de l’ensemble du graphique était celui de Java!
la source
La vitesse d'un programme compilé dépend de deux choses:
Le langage dans lequel un compilateur est écrit est sans importance pour (1). Par exemple, un compilateur Java peut être écrit en C, Java ou Python, mais dans tous les cas, la "machine" qui exécute le programme est la machine virtuelle Java.
Le langage dans lequel un compilateur est écrit est sans importance pour (2). Par exemple, il n'y a aucune raison pour qu'un compilateur C écrit en Python ne puisse pas produire exactement le même fichier exécutable qu'un compilateur C écrit en C ou en Java.
la source
Je vais essayer d'offrir une réponse plus courte.
Le coeur de la question réside dans la définition de la "vitesse" d'une langue .
La plupart des tests de comparaison de vitesse, sinon tous, ne testent pas la vitesse maximale possible. Au lieu de cela, ils écrivent un petit programme dans une langue qu'ils veulent tester pour résoudre un problème. Lors de l’écriture du programme, le programmeur utilise ce qu’ils supposent * être les meilleures pratiques et conventions du langage, au moment du test. Ensuite, ils mesurent la vitesse à laquelle le programme a été exécuté.
* Les hypothèses sont parfois fausses.
la source
Le code écrit dans un langage X dont le compilateur est écrit en C peut surperformer un code écrit en C, à condition que le compilateur C fasse une optimisation médiocre par rapport à celle du langage X. Si nous gardons l'optimisation en discussion, alors si le compilateur de X pourrait générer mieux code objet que celui généré par le compilateur C, le code écrit en X peut également gagner la course.
Mais si le langage X est un langage interprété et que l'interpréteur est écrit en C, et si nous supposons que l'interpréteur du langage X et du code écrit en C est compilé par le même compilateur C, le code écrit en X ne surpassera en rien le code écrit en C, à condition que les deux implémentations suivent le même algorithme et utilisent des structures de données équivalentes.
la source