it-swarm.com.de

Wie kompiliert Go so schnell?

Ich habe auf der Go-Website gegoogelt und gestöbert, aber ich kann keine Erklärung für die außergewöhnlichen Build-Zeiten von Go finden. Handelt es sich um Produkte mit Sprachfeatures (oder fehlenden Sprachfeatures), einem hochoptimierten Compiler oder etwas anderem? Ich versuche nicht, Go zu fördern. Ich bin nur Neugierig.

203
Evan Kroske

Abhängigkeitsanalyse.

Aus dem Go FAQ :

Go bietet ein Modell für die Software-Erstellung, das die Abhängigkeitsanalyse vereinfacht und einen Großteil des Overheads von C-Style-Include-Dateien und -Bibliotheken vermeidet.

Das ist der Hauptgrund für die schnelle Kompilierung. Und das ist so gewollt.

180
Igor Krivokon

Ich denke, es ist nicht so, dass Go-Compiler schnell sind, sondern dass andere Compiler langsam sind.

C- und C++ - Compiler müssen eine enorme Menge von Headern analysieren - zum Beispiel erfordert das Kompilieren von C++ "Hallo Welt" das Kompilieren von 18.000 Codezeilen, was fast einem halben Megabyte Quellcode entspricht!

$ cpp hello.cpp | wc
  18364   40513  433334

Java- und C # -Compiler werden in einer VM ausgeführt. Das bedeutet, dass das Betriebssystem die gesamte VM laden muss, bevor sie kompiliert werden können. Anschließend müssen sie von Bytecode zu systemeigenem Code JIT-kompiliert werden, was einige Zeit in Anspruch nimmt.

Die Geschwindigkeit der Kompilierung hängt von mehreren Faktoren ab.

Einige Sprachen sind so konzipiert, dass sie schnell kompiliert werden können. Pascal wurde beispielsweise für die Kompilierung mit einem Single-Pass-Compiler entwickelt.

Compiler selbst können ebenfalls optimiert werden. Zum Beispiel wurde der Turbo Pascal-Compiler in einem handoptimierten Assembler geschrieben, der zusammen mit dem Sprachdesign zu einem wirklich schnellen Compiler führte, der auf Hardware der 286-Klasse arbeitete. Ich denke, dass moderne Pascal-Compiler (z. B. FreePascal) schon jetzt schneller sind als Go-Compiler.

70
el.pescado

Es gibt mehrere Gründe, warum der Go-Compiler viel schneller ist als die meisten C/C++ - Compiler:

  • Hauptgrund: Die meisten C/C++ - Compiler weisen außergewöhnlich schlechte Designs auf (aus Sicht der Kompilierungsgeschwindigkeit). Aus der Sicht der Kompilierungsgeschwindigkeit sind einige Teile des C/C++ - Ökosystems (z. B. Editoren, in denen Programmierer ihre Codes schreiben) nicht auf Kompilierungsgeschwindigkeit ausgelegt.

  • Hauptgrund: Die schnelle Kompilierungsgeschwindigkeit war sowohl im Go-Compiler als auch in der Go-Sprache eine bewusste Wahl

  • Der Go-Compiler verfügt über einen einfacheren Optimierer als C/C++ - Compiler

  • Im Gegensatz zu C++ hat Go keine Vorlagen und keine Inline-Funktionen. Dies bedeutet, dass Go keine Template- oder Funktionsinstanziierung durchführen muss.

  • Der Go-Compiler generiert Assembler-Code auf niedriger Ebene früher und der Optimierer bearbeitet den Assembler-Code, während in einem typischen C/C++ - Compiler die Optimierung eine interne Darstellung des ursprünglichen Quellcodes durchführt. Der zusätzliche Aufwand im C/C++ - Compiler ergibt sich aus der Tatsache, dass die interne Darstellung generiert werden muss.

  • Das endgültige Verknüpfen (5l/6l/8l) eines Go-Programms kann langsamer sein als das Verknüpfen eines C/C++ - Programms, da der Go-Compiler den gesamten verwendeten Assembly-Code durchläuft und möglicherweise auch andere zusätzliche Aktionen ausführt, die in C/C++ ausgeführt werden Linker tun das nicht

  • Einige C/C++ - Compiler (GCC) generieren Anweisungen in Textform (an den Assembler zu übergeben), während der Go-Compiler Anweisungen in Binärform generiert. Es muss zusätzliche Arbeit (aber nicht viel) geleistet werden, um den Text in eine Binärdatei umzuwandeln.

  • Der Go-Compiler zielt nur auf eine kleine Anzahl von CPU-Architekturen ab, während der GCC-Compiler auf eine große Anzahl von CPUs zielt

  • Compiler wie Jikes, die mit dem Ziel einer hohen Kompilierungsgeschwindigkeit entwickelt wurden, sind schnell. Auf einer 2-GHz-CPU kann Jikes über 20000 Zeilen Java Code pro Sekunde) kompilieren (und der inkrementelle Kompilierungsmodus ist noch effizienter).

34
user811773

Die Effizienz der Kompilierung war ein wichtiges Designziel:

Schließlich soll es schnell gehen: Es sollte höchstens einige Sekunden dauern, um eine große ausführbare Datei auf einem einzelnen Computer zu erstellen. Um diese Ziele zu erreichen, musste eine Reihe von sprachlichen Problemen angegangen werden: ein ausdrucksstarkes, aber leichtgewichtiges System; Parallelität und Speicherbereinigung; starre Abhängigkeitsangabe; und so weiter. FAQ

Die Sprache FAQ ist in Bezug auf bestimmte Sprachfunktionen im Zusammenhang mit dem Parsen ziemlich interessant:

Zweitens ist die Sprache so konzipiert, dass sie leicht zu analysieren ist und ohne Symboltabelle analysiert werden kann.

32
Larry OBrien

Während die meisten der oben genannten Punkte zutreffen, gibt es einen sehr wichtigen Punkt, der nicht wirklich erwähnt wurde: das Abhängigkeitsmanagement.

Go muss nur die Pakete enthalten, die Sie importieren , direkt (da diese bereits importiert haben, was sie brauchen). Dies steht in krassem Gegensatz zu C/C++, wo jede einzelne Datei beginnt, x-Header einzuschließen, die y-Header usw. einschließen/C++ benötigt exponentielle Zeit.

23
Kosta

Ein guter Test für die Übersetzungseffizienz eines Compilers ist die Selbstkompilierung: Wie lange braucht ein Compiler, um sich selbst zu kompilieren? Für C++ dauert es sehr lange (Stunden?). Zum Vergleich: Ein Pascal/Modula-2/Oberon-Compiler würde sich auf einer modernen Maschine in weniger als one Sekunden kompilieren [1].

Go wurde von diesen Sprachen inspiriert, aber einige der Hauptgründe für diese Effizienz sind:

  1. Eine klar definierte, mathematisch fundierte Syntax für effizientes Scannen und Parsen.

  2. Eine typsichere und statisch kompilierte Sprache, die separate Kompilierung mit Abhängigkeit und Typprüfung across = verwendet Modulgrenzen, um unnötiges erneutes Lesen von Header-Dateien und Kompilieren anderer Module zu vermeiden - im Gegensatz zur unabhängigen Kompilierung wie in C/C++, wo keine solche vorhanden ist Modulübergreifende Überprüfungen werden vom Compiler durchgeführt (daher müssen alle diese Header-Dateien immer wieder neu gelesen werden, selbst für ein einfaches einzeiliges "Hallo Welt" -Programm).

  3. Eine effiziente Compiler-Implementierung (z. B. Top-Down-Parsing in einem Durchgang, rekursiver Abstieg) - was natürlich durch die obigen Punkte 1 und 2 erheblich unterstützt wird.

Diese Prinzipien wurden bereits in den 1970er und 1980er Jahren in Sprachen wie Mesa, Ada, Modula-2/Oberon und mehreren anderen bekannt und vollständig umgesetzt und finden erst jetzt (in den 2010er Jahren) ihren Weg in moderne Sprachen wie Go (Google). , Swift (Apple), C # (Microsoft) und einige andere.

Hoffen wir, dass dies bald die Norm und nicht die Ausnahme sein wird. Um dorthin zu gelangen, müssen zwei Dinge geschehen:

  1. Zunächst sollten Software-Plattform-Anbieter wie Google, Microsoft und Apple) application Entwickler dazu ermutigen, die neue Kompilierungsmethode zu verwenden, während sie dies ermöglichen Verwenden Sie die vorhandene Codebasis erneut. Dies ist, was Apple versucht jetzt mit der Programmiersprache Swift) zu tun, die mit Objective-C koexistieren kann (da es die gleiche Laufzeitumgebung verwendet).

  2. Zweitens sollten die zugrunde liegenden Softwareplattformen selbst im Laufe der Zeit unter Verwendung dieser Prinzipien neu geschrieben werden, während gleichzeitig die Modulhierarchie neu entworfen wird, um sie weniger monolithisch zu machen. Dies ist natürlich eine Mammutaufgabe und kann durchaus den größten Teil eines Jahrzehnts in Anspruch nehmen (wenn sie mutig genug sind, es tatsächlich zu tun - was ich bei Google überhaupt nicht sicher bin).

In jedem Fall ist es die Plattform, die die Sprachakzeptanz vorantreibt, und nicht umgekehrt.

Verweise:

[1] http://www.inf.ethz.ch/personal/wirth/ProjectOberon/PO.System.pdf , Seite 6: "Der Compiler kompiliert sich in ca. 3 Sekunden selbst". Dieses Zitat bezieht sich auf ein kostengünstiges Xilinx Spartan-3 FPGA-Entwicklungsboard mit einer Taktfrequenz von 25 MHz und 1 MByte Hauptspeicher. Daraus kann man leicht auf "weniger als 1 Sekunde" für einen modernen Prozessor extrapolieren, der mit einer Taktfrequenz weit über 1 GHz und mehreren GByte Hauptspeicher (dh mehreren Größenordnungen) arbeitet Leistungsstärker als die Xilinx Spartan-3 FPGA-Karte), auch unter Berücksichtigung der E/A-Geschwindigkeit. Bereits 1990, als Oberon auf einem 25-MHz-NS32X32-Prozessor mit 2-4 MB Hauptspeicher lief, kompilierte sich der Compiler in wenigen Sekunden. Die Vorstellung, dass der Compiler tatsächlich wartet , um einen Kompilierungszyklus zu beenden, war den Oberon-Programmierern schon damals völlig unbekannt. Bei typischen Programmen dauerte es immer länger, den Finger von der Maustaste zu entfernen, die den Kompilierungsbefehl ausgelöst hat, als darauf zu warten, dass der Compiler die gerade ausgelöste Kompilierung abschließt. Es war wirklich eine sofortige Befriedigung mit nahezu null Wartezeiten. Und die Qualität des produzierten Codes war für die meisten Aufgaben bemerkenswert gut und allgemein akzeptabel, obwohl sie nicht immer den besten damals verfügbaren Compilern entsprach.

21
Andreas

Go wurde entwickelt, um schnell zu sein, und es zeigt.

  1. Abhängigkeitsmanagement: Keine Header-Datei, Sie müssen sich nur die Pakete ansehen, die direkt importiert werden (Sie müssen sich keine Gedanken darüber machen, was sie importieren), sodass Sie lineare Abhängigkeiten haben.
  2. Grammatik: Die Grammatik der Sprache ist einfach und daher leicht zu analysieren. Obwohl die Anzahl der Features reduziert ist, ist der Compiler-Code selbst eng (wenige Pfade).
  3. Keine Überladung erlaubt: Sie sehen ein Symbol und wissen, auf welche Methode es sich bezieht.
  4. Es ist trivialerweise möglich, Go parallel zu kompilieren, da jedes Paket unabhängig kompiliert werden kann.

Beachten Sie, dass GO nicht die einzige Sprache mit solchen Funktionen ist (Module sind die Norm in modernen Sprachen), aber sie haben es gut gemacht.

11
Matthieu M.

Die Grundidee der Kompilierung ist eigentlich sehr einfach. Ein rekursiv absteigender Parser kann im Prinzip mit E/A-gebundener Geschwindigkeit ausgeführt werden. Die Codegenerierung ist im Grunde ein sehr einfacher Prozess. Eine Symboltabelle und ein Basistypsystem sind nicht rechenintensiv.

Es ist jedoch nicht schwer, einen Compiler zu verlangsamen.

Wenn es eine Präprozessorphase mit mehrstufigen include Direktiven, Makrodefinitionen und bedingter Kompilierung gibt, ist es nicht schwer, sie zu laden, so nützlich diese Dinge auch sind. (Zum Beispiel denke ich an die Windows- und MFC-Header-Dateien.) Aus diesem Grund sind vorkompilierte Header erforderlich.

In Bezug auf die Optimierung des generierten Codes gibt es keine Begrenzung, wie viel Verarbeitung zu dieser Phase hinzugefügt werden kann.

8
Mike Dunlavey

Zitat aus dem Buch " The Go Programming Language " von Alan Donovan und Brian Kernighan:

Die Kompilierung von Go ist erheblich schneller als die meisten anderen kompilierten Sprachen, selbst wenn Sie von Grund auf neu erstellen. Es gibt drei Hauptgründe für die Geschwindigkeit des Compilers. Erstens müssen alle Importe am Anfang jeder Quelldatei explizit aufgelistet werden, damit der Compiler nicht die gesamte Datei lesen und verarbeiten muss, um ihre Abhängigkeiten zu bestimmen. Zweitens bilden die Abhängigkeiten eines Pakets einen gerichteten azyklischen Graphen. Da es keine Zyklen gibt, können Pakete separat und möglicherweise parallel kompiliert werden. Schließlich zeichnet die Objektdatei für ein kompiliertes Go-Paket Exportinformationen nicht nur für das Paket selbst, sondern auch für seine Abhängigkeiten auf. Beim Kompilieren eines Pakets muss der Compiler für jeden Import eine Objektdatei lesen, darf jedoch nicht über diese Dateien hinausschauen.

7
Miscreant

Einfach (in meinen eigenen Worten), weil die Syntax sehr einfach ist (zu analysieren und zu analysieren)

Zum Beispiel bedeutet keine Typvererbung, keine problematische Analyse, um herauszufinden, ob der neue Typ den vom Basistyp auferlegten Regeln entspricht.

Zum Beispiel in diesem Codebeispiel: "interfaces" Der Compiler überprüft nicht, ob der beabsichtigte Typ Implementiere die angegebene Schnittstelle, während du diesen Typ analysierst. Die Prüfung wird nur solange durchgeführt, bis sie verwendet wird (und WENN sie verwendet wird).

In einem anderen Beispiel teilt der Compiler Ihnen mit, ob Sie eine Variable deklarieren und nicht verwenden (oder ob Sie einen Rückgabewert halten sollen und nicht).

Folgendes wird nicht kompiliert:

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

Diese Art der Durchsetzung und Prinzipien machen den resultierenden Code sicherer und der Compiler muss keine zusätzlichen Überprüfungen durchführen, die der Programmierer durchführen kann.

Im Großen und Ganzen erleichtern all diese Details das Parsen einer Sprache, was zu schnellen Kompilierungen führt.

Wieder in meinen eigenen Worten.

4
OscarRyz

ich denke, Go wurde parallel zur Compilererstellung entwickelt, daher waren sie von Geburt an die besten Freunde. (IMO)

2
Andrey