web-dev-qa-db-fra.com

Comment Go compile-t-il si rapidement?

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

203
Evan Kroske

Analyse de dépendance.

De la Aller FAQ :

Go fournit un modèle de construction logicielle qui facilite l’analyse de dépendance et évite une bonne partie des frais généraux des fichiers d’inclusion et des bibliothèques.

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

180
Igor Krivokon

Je pense que ce n'est pas que les compilateurs Go sont rapide, mais que les autres compilateurs sont lent.

Les compilateurs C et C++ doivent analyser d’énormes quantités d’en-têtes - par exemple, compiler C++ "hello world" nécessite la compilation de 18 000 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 sur une machine virtuelle. Cela signifie qu'avant de pouvoir tout compiler, le système d'exploitation doit charger l'intégralité de la machine virtuelle, puis compiler JIT à partir du bytecode en code natif, ce qui prend un certain temps.

La rapidité 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 à passe unique.

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

70
el.pescado

Le compilateur Go est beaucoup plus rapide que la plupart des compilateurs C/C++ pour plusieurs raisons:

  • Top raison: La plupart des compilateurs C/C++ présentent des conceptions exceptionnellement mauvaises (du point de vue de la vitesse de compilation). De même, 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 pour une compilation rapide.

  • Principale raison: La vitesse de compilation rapide était un choix conscient du compilateur Go et du langage Go

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

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

  • Le compilateur Go génère plus rapidement le code d'assemblage de bas niveau et l'optimiseur fonctionne sur le code d'assemblage, tandis que dans un compilateur C/C++ typique, les passes d'optimisation fonctionnent sur une représentation interne du code source d'origine. La surcharge supplémentaire du compilateur C/C++ provient 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 Assembly utilisé et peut-être qu'il effectue également d'autres actions supplémentaires que C/C++. les linkers 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 grand chose) doit être fait pour transformer le texte en binaire.

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

  • Les compilateurs conçus pour atteindre une vitesse de compilation élevée, tels que Jikes, sont rapides. Sur un processeur de 2 GHz, Jikes peut compiler plus de 20000 lignes de Java par seconde (et le mode de compilation incrémental est encore plus efficace).

34
user811773

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

Enfin, il est prévu d’être rapide: la création d’un fichier exécutable volumineux sur un seul ordinateur ne devrait prendre que quelques secondes. Pour atteindre ces objectifs, il a fallu résoudre un certain nombre de problèmes linguistiques: un système de types expressif mais léger; accès simultané et collecte des ordures; spécification de dépendance rigide; etc. FAQ

Le langage FAQ est assez intéressant en ce qui concerne les fonctionnalités de langage spécifiques relatives à l'analyse syntaxique:

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

32
Larry OBrien

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

Go n’a besoin d’inclure que les paquetages que vous importez directement (comme ceux déjà importés que ils besoin). Ceci est en contraste frappant avec C/C++, où chaque fichier commence à inclure les en-têtes x, y compris les en-têtes etc./C++ prend un temps exponentiel.

23
Kosta

L'auto-compilation est un bon test de l'efficacité de la 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 de un seconde sur une machine moderne [1].

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

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

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

  3. Une implémentation efficace du compilateur (par exemple, l'analyse syntaxique descendante à descente récursive en un seul passage) - qui est bien évidemment grandement facilitée 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 langages tels que Mesa, Ada, Modula-2/Oberon et plusieurs autres, et ne font que commencer (dans les années 2010) à se frayer un chemin dans des langues modernes comme Go , Swift (Apple), C # (Microsoft) et plusieurs autres.

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

  1. Premièrement, les fournisseurs de plateformes logicielles tels que Google, Microsoft et Apple devraient commencer par encourager les développeurs d'applications à utiliser la nouvelle compilation méthodologie, tout en leur permettant de réutiliser leur base de code existante. C’est ce que Apple tente maintenant de faire avec le Swift, qui peut -exist avec Objective-C (puisqu'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 restructurant simultanément la hiérarchie des modules afin de les rendre moins monolithiques. Il s’agit bien entendu d’une tâche gigantesque qui risque de prendre une bonne partie de la décennie (s’ils ont le courage de le faire - ce que je ne suis pas du tout certain dans le cas de Google).

En tout cas, c'est la plateforme qui oriente l'adoption de la langue, et non l'inverse.

Les 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 concerne une carte de développement FPGA Xilinx Spartan-3 à faible coût, fonctionnant à une fréquence d'horloge de 25 MHz et dotée d'une mémoire principale de 1 Mo. À partir de cela, on peut facilement extrapoler à "moins de 1 seconde" pour un processeur moderne fonctionnant à une fréquence d'horloge bien supérieure à 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 se compilait en quelques secondes seulement. La notion d'attendre réellement que le compilateur termine un cycle de compilation était totalement inconnue des programmeurs Oberon, même à cette époque. Pour les programmes typiques, il fallait toujours plus de temps pour supprimer le doigt du bouton de la souris qui a déclenché la commande de compilation, plutôt que d'attendre que le compilateur termine la compilation. juste déclenché. C'était vraiment une gratification instantanée, avec des temps d'attente presque nuls. Et la qualité du code produit, même si elle n’était pas toujours à la hauteur des meilleurs compilateurs disponibles à l’époque, était remarquablement bonne pour la plupart des tâches et tout à fait acceptable.

21
Andreas

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 vous suffit de regarder les paquets directement importés (vous n'avez pas à vous soucier de ce qu'ils importent). Vous avez donc 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 restreint (peu de chemins).
  3. Aucune surcharge autorisée: vous voyez un symbole, vous savez à quelle méthode il fait référence.
  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 la seule langue avec de telles fonctionnalités (les modules sont la norme dans les langues modernes), mais ils l'ont bien fait.

11
Matthieu M.

L'idée de base de la compilation est en réalité très simple. Un analyseur syntaxique à descente récursive peut en principe fonctionner à la 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 types de base ne nécessitent 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 multi-niveaux include, des définitions de macros et une compilation conditionnelle, aussi utiles soient-ils, il n'est pas difficile de les charger. (Par exemple, je pense aux fichiers d'en-tête Windows et MFC.) C'est pourquoi les 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 au nombre de traitements pouvant être ajoutés à cette phase.

8
Mike Dunlavey

Citant le livre " Le langage de programmation Go " de Alan Donovan et Brian Kernighan:

La compilation est nettement plus rapide que la plupart des autres langages compilés, même lors de la création à partir de rien. Trois raisons principales expliquent la vitesse du compilateur. Tout d'abord, toutes les importations doivent être explicitement répertoriées au début de chaque fichier source. Le compilateur n'a donc pas besoin de lire et de traiter un fichier entier pour déterminer ses dépendances. Deuxièmement, les dépendances d'un paquet forment un graphe acyclique dirigé et, comme il n'y a pas de cycles, les paquets 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 également pour ses dépendances. Lors de la compilation d'un package, le compilateur doit lire un fichier objet pour chaque importation, sans nécessairement aller au-delà de ces fichiers.

7
Miscreant

Simplement (dans mes propres mots), car la syntaxe est très facile (analyser et analyser)

Par exemple, aucun héritage de type ne signifie, pas une analyse problématique pour savoir si le nouveau type respecte les règles imposées par le type de base.

Par exemple, dans cet exemple de code: "interfaces" le compilateur ne vérifie pas si le type voulu implémente l'interface donnée lors de l'analyse de ce type. Seulement jusqu'à ce qu'il soit utilisé (et SI c'est utilisé), le contrôle est effectué.

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

Ce qui suit ne compile pas:

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

Ces types de règles et principes rendent 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 permet des compilations rapides.

Encore une fois, avec mes propres mots.

4
OscarRyz

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

2
Andrey