Comment Go se compile-t-il si rapidement?

217

J'ai googlé et fouillé sur le site Web de Go, mais je n'arrive pas à trouver d'explication pour les temps de construction extraordinaires de Go. S'agit-il de produits du langage (ou de leur absence), d'un compilateur hautement optimisé ou d'autre chose? Je n'essaie pas de promouvoir Go; Je suis juste curieux.

Evan Kroske
la source
12
@Support, j'en suis conscient. Je pense que l'implémentation d'un compilateur de telle manière qu'il compile avec une rapidité notable est tout sauf une optimisation prématurée. Plus que probablement, il représente le résultat de bonnes pratiques de conception et de développement de logiciels. De plus, je ne supporte pas de voir les mots de Knuth sortis de leur contexte et appliqués incorrectement.
Adam Crossland
55
La version pessimiste de cette question est "Pourquoi le C ++ se compile-il si lentement?" stackoverflow.com/questions/588884/…
dan04
14
J'ai voté pour rouvrir cette question car elle n'est pas basée sur l'opinion. On peut donner un bon aperçu technique (sans opinion) des choix de langue et / ou de compilateur qui facilitent la vitesse de compilation.
Martin Tournoij
Pour les petits projets, Go me semble lent. C'est parce que je me souviens que Turbo-Pascal était beaucoup plus rapide sur un ordinateur qui était probablement des milliers de fois plus lent. prog21.dadgum.com/47.html?repost=true . Chaque fois que je tape "go build" et que rien ne se passe pendant plusieurs secondes, je repense aux vieux compilateurs et cartes perforées Fortran. YMMV. TLDR: "lent" et "rapide" sont des termes relatifs.
RedGrittyBrick
Je recommande vivement de lire dave.cheney.net/2014/06/07/five-things-that-make-go-fast pour des informations plus détaillées
Karthik

Réponses:

193

Analyse des dépendances.

La FAQ Go contenait la phrase suivante:

Go fournit un modèle de construction logicielle qui facilite l'analyse des dépendances et évite une grande partie des frais généraux des fichiers et bibliothèques d'inclusion de style C.

Bien que la phrase ne soit plus dans la FAQ, ce sujet est développé dans le discours Go de Google , qui compare l'approche d'analyse de dépendance de C / C ++ et Go.

C'est la raison principale d'une compilation rapide. Et c'est par conception.

Igor Krivokon
la source
Cette phrase n'est plus dans la FAQ Go, mais une explication plus détaillée de la rubrique "analyse de dépendance" comparant l'approche C / C ++ et Pascal / Modula / Go est disponible dans la conférence Go de Google
rob74
76

Je pense que ce n'est pas que les compilateurs Go sont rapides , c'est que les autres compilateurs sont lents .

Les compilateurs C et C ++ doivent analyser d'énormes quantités d'en-têtes - par exemple, la compilation de C ++ "hello world" nécessite la compilation de 18k lignes de code, ce qui représente presque un demi-mégaoctet de sources!

$ cpp hello.cpp | wc
  18364   40513  433334

Les compilateurs Java et C # s'exécutent dans une machine virtuelle, ce qui signifie qu'avant de pouvoir compiler quoi que ce soit, le système d'exploitation doit charger la machine virtuelle entière, puis ils doivent être compilés JIT du bytecode au code natif, ce qui prend un certain temps.

La vitesse de compilation dépend de plusieurs facteurs.

Certaines langues sont conçues pour être compilées rapidement. Par exemple, Pascal a été conçu pour être compilé à l'aide d'un compilateur en un seul passage.

Les compilateurs eux-mêmes peuvent également être optimisés. Par exemple, le compilateur Turbo Pascal a été écrit dans un assembleur optimisé à la main, ce qui, combiné à la conception du langage, a abouti à un compilateur très rapide fonctionnant sur du matériel de classe 286. Je pense que même maintenant, les compilateurs Pascal modernes (par exemple FreePascal) sont plus rapides que les compilateurs Go.

el.pescado
la source
19
Le compilateur C # de Microsoft ne s'exécute pas sur une machine virtuelle. Il est toujours écrit en C ++, principalement pour des raisons de performances.
blucz
19
Turbo Pascal et Delphi plus tard sont les meilleurs exemples de compilateurs incroyablement rapides. Après que l'architecte des deux a migré vers Microsoft, nous avons constaté de grandes améliorations dans les compilateurs MS et les langages. Ce n'est pas une coïncidence aléatoire.
TheBlastOne
7
18k lignes (18364 pour être exact) de code est 433334 octets (~ 0,5 Mo)
el.pescado
9
Le compilateur C # est compilé avec C # depuis 2011. Juste une mise à jour au cas où quelqu'un le lirait plus tard.
Kurt Koller
3
Le compilateur C # et le CLR qui exécute le MSIL généré sont cependant des choses différentes. Je suis assez certain que le CLR n'est pas écrit en C #.
jocull
39

Il existe plusieurs raisons pour lesquelles le compilateur Go est beaucoup plus rapide que la plupart des compilateurs C / C ++:

  • Raison principale : la plupart des compilateurs C / C ++ présentent des conceptions exceptionnellement mauvaises (du point de vue de la vitesse de compilation). De plus, du point de vue de la vitesse de compilation, certaines parties de l'écosystème C / C ++ (telles que les éditeurs dans lesquels les programmeurs écrivent leurs codes) ne sont pas conçues en fonction de la vitesse de compilation.

  • Principale raison : la vitesse de compilation rapide était un choix conscient dans le compilateur Go et aussi dans le langage Go

  • Le compilateur Go a un optimiseur plus simple que les compilateurs C / C ++

  • Contrairement à C ++, Go n'a pas de modèles ni de fonctions en ligne. Cela signifie que Go n'a pas besoin d'effectuer d'instanciation de modèle ou de fonction.

  • Le compilateur Go génère plus tôt le code assembleur de bas niveau et l'optimiseur fonctionne sur le code assembleur, tandis que dans un compilateur C / C ++ typique, l'optimisation passe le travail sur une représentation interne du code source d'origine. La surcharge supplémentaire dans le compilateur C / C ++ vient du fait que la représentation interne doit être générée.

  • La liaison finale (5l / 6l / 8l) d'un programme Go peut être plus lente que la liaison d'un programme C / C ++, car le compilateur Go parcourt tout le code assembleur utilisé et peut-être qu'il effectue également d'autres actions supplémentaires que C / C ++ les éditeurs de liens ne font pas

  • Certains compilateurs C / C ++ (GCC) génèrent des instructions sous forme de texte (à transmettre à l'assembleur), tandis que le compilateur Go génère des instructions sous forme binaire. Un travail supplémentaire (mais pas beaucoup) doit être effectué afin de transformer le texte en binaire.

  • Le compilateur Go ne cible qu'un petit nombre d'architectures CPU, tandis que le compilateur GCC cible un grand nombre de CPU

  • Les compilateurs qui ont été conçus dans le but d'une vitesse de compilation élevée, tels que Jikes, sont rapides. Sur un processeur à 2 GHz, Jikes peut compiler plus de 20000 lignes de code Java par seconde (et le mode de compilation incrémentiel est encore plus efficace).

user811773
la source
17
Le compilateur de Go intègre de petites fonctions. Je ne sais pas comment le ciblage d'un petit nombre de processeurs vous rend plus rapide plus lent ... Je suppose que gcc ne génère pas de code PPC pendant que je compile pour x86.
Brad Fitzpatrick
@BradFitzpatrick déteste ressusciter un vieux commentaire mais en ciblant un plus petit nombre de plates-formes, les développeurs du compilateur peuvent passer plus de temps à l'optimiser pour chacun.
Persistance
l'utilisation d'un formulaire intermédiaire vous permet de prendre en charge beaucoup plus d'architectures puisque vous n'avez maintenant qu'à écrire un nouveau backend pour chaque nouvelle architecture
phuclv
34

L'efficacité de la compilation était un objectif de conception majeur:

Enfin, il se veut rapide: il ne faut pas plus de quelques secondes pour construire un gros exécutable sur un seul ordinateur. Pour atteindre ces objectifs, il fallait résoudre un certain nombre de problèmes linguistiques: un système de type expressif mais léger; simultanéité et collecte des ordures; spécification de dépendance rigide; etc. FAQ

La FAQ sur la langue est assez intéressante en ce qui concerne les fonctionnalités de langue spécifiques liées à l'analyse:

Deuxièmement, le langage a été conçu pour être facile à analyser et peut être analysé sans table de symboles.

Larry OBrien
la source
6
Ce n'est pas vrai. Vous ne pouvez pas analyser complètement le code source de Go sans table de symboles.
12
Je ne vois pas non plus pourquoi la récupération de place améliore les temps de compilation. Ce n'est pas le cas.
TheBlastOne
3
Ce sont des citations de la FAQ: golang.org/doc/go_faq.html Je ne peux pas dire s'ils n'ont pas atteint leurs objectifs (tableau des symboles) ou si leur logique est défectueuse (GC).
Larry OBrien
5
@FUZxxl Allez sur golang.org/ref/spec#Primary_expressions et considérez les deux séquences [Opérande, Appel] et [Conversion]. Exemple Code source Go: identifiant1 (identifiant2). Sans table de symboles, il est impossible de décider si cet exemple est un appel ou une conversion. | Toute langue peut être analysée dans une certaine mesure sans table de symboles. Il est vrai que la plupart des parties des codes source de Go peuvent être analysées sans table de symboles, mais il n'est pas vrai qu'il soit possible de reconnaître tous les éléments de grammaire définis dans les spécifications golang.
3
@Atom Vous travaillez dur pour éviter que l'analyseur ne soit jamais le morceau de code qui signale une erreur. Les analyseurs font généralement un mauvais travail de rapport de messages d'erreur cohérents. Ici, vous créez un arbre d'analyse pour l'expression comme s'il aTypes'agissait d'une référence de variable, et plus tard dans la phase d'analyse sémantique lorsque vous découvrez que vous n'imprimez pas une erreur significative à ce moment-là.
Sam Harwell
26

Bien que la plupart de ce qui précède soit vrai, il y a un point très important qui n'était pas vraiment mentionné: la gestion des dépendances.

Go doit uniquement inclure les packages que vous importez directement (comme ceux déjà importés dont ils ont besoin). C'est en contraste frappant avec C / C ++, où chaque fichier commence avec x en-têtes, qui incluent y en-têtes, etc.

Kosta
la source
22

L'auto-compilation est un bon test pour l'efficacité de traduction d'un compilateur: combien de temps faut-il à un compilateur donné pour se compiler? Pour C ++, cela prend beaucoup de temps (heures?). Par comparaison, un compilateur Pascal / Modula-2 / Oberon se compilerait en moins d' une seconde sur une machine moderne [1].

Go a été inspiré par ces langues, mais certaines des principales raisons de cette efficacité incluent:

  1. Une syntaxe clairement définie et mathématiquement saine, pour un balayage et une analyse efficaces.

  2. Un langage de type sécurisé et compilé statiquement qui utilise une compilation séparée avec dépendance et vérification de type à travers les limites du module, pour éviter la relecture inutile des fichiers d'en-tête et la recompilation d'autres modules - par opposition à une compilation indépendante comme en C / C ++ où aucune vérification inter-modules de ce type n'est effectuée par le compilateur (d'où la nécessité de relire tous ces fichiers d'en-tête encore et encore, même pour un simple programme "hello world" sur une seule ligne).

  3. Une implémentation efficace du compilateur (par exemple, analyse descendante à passage unique, descente récursive) - ce qui bien sûr est grandement aidé par les points 1 et 2 ci-dessus.

Ces principes ont déjà été connus et pleinement mis en œuvre dans les années 1970 et 1980 dans des langues comme Mesa, Ada, Modula-2 / Oberon et plusieurs autres, et ne font que maintenant (dans les années 2010) leur chemin vers des langues modernes comme Go (Google) , Swift (Apple), C # (Microsoft) et plusieurs autres.

Espérons que ce sera bientôt la norme et non l'exception. Pour y arriver, deux choses doivent se produire:

  1. Premièrement, les fournisseurs de plates-formes logicielles telles que Google, Microsoft et Apple devraient commencer par encourager les développeurs d' applications à utiliser la nouvelle méthodologie de compilation, tout en leur permettant de réutiliser leur base de code existante. C'est ce qu'Apple essaie maintenant de faire avec le langage de programmation Swift, qui peut coexister avec Objective-C (car il utilise le même environnement d'exécution).

  2. Deuxièmement, les plates-formes logicielles sous-jacentes elles-mêmes devraient éventuellement être réécrites au fil du temps en utilisant ces principes, tout en repensant simultanément la hiérarchie des modules dans le processus pour les rendre moins monolithiques. C'est bien sûr une tâche gigantesque et pourrait bien prendre la majeure partie d'une décennie (s'ils sont assez courageux pour le faire - ce que je ne suis pas du tout sûr dans le cas de Google).

Dans tous les cas, c'est la plate-forme qui stimule l'adoption du langage, et non l'inverse.

Références:

[1] http://www.inf.ethz.ch/personal/wirth/ProjectOberon/PO.System.pdf , page 6: "Le compilateur se compile en 3 secondes environ". Cette citation est pour une carte de développement FPGA Xilinx Spartan-3 à faible coût fonctionnant à une fréquence d'horloge de 25 MHz et disposant de 1 Mo de mémoire principale. De celui-ci peut facilement extrapoler à "moins de 1 seconde" pour un processeur moderne fonctionnant à une fréquence d'horloge bien au-dessus de 1 GHz et plusieurs Go de mémoire principale (c'est-à-dire plusieurs ordres de grandeur plus puissants que la carte FPGA Xilinx Spartan-3), même en tenant compte des vitesses d'E / S. Déjà en 1990, quand Oberon était exécuté sur un processeur NS32X32 à 25 MHz avec 2-4 Mo de mémoire principale, le compilateur s'est compilé en quelques secondes. La notion d' attendre réellementpour que le compilateur termine un cycle de compilation était complètement inconnu des programmeurs Oberon même à l'époque. Pour les programmes typiques, il a toujours fallu plus de temps pour retirer le doigt du bouton de la souris qui a déclenché la commande de compilation que d'attendre que le compilateur termine la compilation qui vient d'être déclenchée. C'était une satisfaction vraiment instantanée, avec des temps d'attente presque nuls. Et la qualité du code produit, même si elle n'était pas toujours complètement comparable aux meilleurs compilateurs disponibles à l'époque, était remarquablement bonne pour la plupart des tâches et tout à fait acceptable en général.

Andreas
la source
1
Un compilateur Pascal / Modula-2 / Oberon / Oberon-2 se compilerait en moins d'une seconde sur une machine moderne [citation nécessaire]
CoffeeandCode
1
Citation ajoutée, voir référence [1].
Andreas
1
"... principes ... trouver leur chemin dans des langages modernes comme Go (Google), Swift (Apple)" Je ne sais pas comment Swift a fait partie de cette liste: le compilateur Swift est glacial . Lors d'une récente rencontre CocoaHeads Berlin, quelqu'un a fourni quelques chiffres pour un cadre de taille moyenne, ils sont venus à 16 LOC par seconde.
mpw
13

Go a été conçu pour être rapide, et ça se voit.

  1. Gestion des dépendances: pas de fichier d'en-tête, il suffit de regarder les packages directement importés (pas besoin de se soucier de ce qu'ils importent) donc vous avez des dépendances linéaires.
  2. Grammaire: la grammaire de la langue est simple, donc facilement analysable. Bien que le nombre de fonctionnalités soit réduit, le code du compilateur lui-même est donc serré (peu de chemins).
  3. Aucune surcharge autorisée: vous voyez un symbole, vous savez à quelle méthode il se réfère.
  4. Il est trivialement possible de compiler Go en parallèle car chaque paquet peut être compilé indépendamment.

Notez que GO n'est pas le seul langage avec de telles fonctionnalités (les modules sont la norme dans les langages modernes), mais ils l'ont bien fait.

Matthieu M.
la source
Le point (4) n'est pas entièrement vrai. Les modules qui dépendent les uns des autres doivent être compilés par ordre de dépendance pour permettre l'incrustation et les éléments entre modules.
fuz
1
@FUZxxl: Cela ne concerne que l'étape d'optimisation, vous pouvez avoir un parallélisme parfait jusqu'à la génération IR backend; seule l'optimisation cross-module est ainsi concernée, ce qui peut se faire au stade de la liaison, et la liaison n'est de toute façon pas parallèle. Bien sûr, si vous ne souhaitez pas dupliquer votre travail (ré-analyse), il vaut mieux compiler de manière "treillis": 1 / modules sans dépendance, 2 / modules dépendant uniquement de (1), 3 / modules en fonction uniquement de (1) et (2), ...
Matthieu M.
2
Ce qui est parfaitement facile à faire en utilisant des utilitaires de base tels qu'un Makefile.
fuz
12

Extrait du livre " The Go Programming Language " d'Alan Donovan et Brian Kernighan:

La compilation de Go est notablement plus rapide que la plupart des autres langues compilées, même lors de la construction à partir de zéro. Il y a trois raisons principales à la vitesse du compilateur. Tout d'abord, toutes les importations doivent être explicitement répertoriées au début de chaque fichier source, afin que le compilateur n'ait pas à lire et traiter un fichier entier pour déterminer ses dépendances. Deuxièmement, les dépendances d'un package forment un graphe acyclique dirigé, et comme il n'y a pas de cycle, les packages peuvent être compilés séparément et peut-être en parallèle. Enfin, le fichier objet d'un package Go compilé enregistre les informations d'exportation non seulement pour le package lui-même, mais aussi pour ses dépendances. Lors de la compilation d'un package, le compilateur doit lire un fichier objet pour chaque importation mais n'a pas besoin de regarder au-delà de ces fichiers.

Scélérat
la source
9

L'idée de base de la compilation est en fait très simple. Un analyseur à descente récursive, en principe, peut fonctionner à une vitesse liée aux E / S. La génération de code est fondamentalement un processus très simple. Une table de symboles et un système de type de base ne demandent pas beaucoup de calculs.

Cependant, il n'est pas difficile de ralentir un compilateur.

S'il y a une phase de préprocesseur, avec des directives d' inclusion à plusieurs niveaux , des définitions de macro et une compilation conditionnelle, aussi utiles que cela soit, il n'est pas difficile de la charger. (Par exemple, je pense aux fichiers d'en-tête Windows et MFC.) C'est pourquoi des en-têtes précompilés sont nécessaires.

En termes d'optimisation du code généré, il n'y a pas de limite à la quantité de traitement pouvant être ajoutée à cette phase.

Mike Dunlavey
la source
7

Simplement (selon mes propres mots), car la syntaxe est très simple (à analyser et à analyser)

Par exemple, pas d'héritage de type signifie, pas d'analyse problématique pour savoir si le nouveau type suit les règles imposées par le type de base.

Par exemple dans cet exemple de code: "interfaces" le compilateur ne va pas vérifier si le type prévu implémente l'interface donnée lors de l'analyse de ce type. Ce n'est que jusqu'à son utilisation (et SI elle est utilisée) que la vérification est effectuée.

Autre exemple, le compilateur vous indique si vous déclarez une variable et ne l'utilisez pas (ou si vous êtes censé contenir une valeur de retour et que vous ne l'êtes pas)

Ce qui suit ne se compile pas:

package main
func main() {
    var a int 
    a = 0
}
notused.go:3: a declared and not used

Ce genre d’applications et principes rend le code résultant plus sûr, et le compilateur n'a pas à effectuer de validations supplémentaires que le programmeur peut effectuer.

Dans l'ensemble, tous ces détails facilitent l'analyse d'un langage, ce qui se traduit par des compilations rapides.

Encore une fois, selon mes propres mots.

OscarRyz
la source
3

Je pense que Go a été conçu en parallèle avec la création de compilateurs, ils étaient donc les meilleurs amis de la naissance. (OMI)

Andrey
la source
0
  • Go importe les dépendances une fois pour tous les fichiers, de sorte que le temps d'importation n'augmente pas de façon exponentielle avec la taille du projet.
  • Une linguistique plus simple signifie que leur interprétation nécessite moins de calcul.

Quoi d'autre?

Alberto Salvia Novella
la source