Ich habe auf der Go-Website gegoogelt und gestöbert, aber ich kann anscheinend keine Erklärung für die außergewöhnlichen Bauzeiten von Go finden. Sind sie Produkte der Sprachfunktionen (oder deren Fehlen), ein hochoptimierter Compiler oder etwas anderes? Ich versuche nicht, Go zu fördern. Ich bin nur Neugierig.
performance
compiler-construction
build-process
go
Evan Kroske
quelle
quelle
Antworten:
Abhängigkeitsanalyse.
Die Go-FAQ enthielten den folgenden Satz:
Während der Satz nicht mehr in den FAQ enthalten ist, wird dieses Thema im Vortrag Go bei Google behandelt , in dem der Ansatz der Abhängigkeitsanalyse von C / C ++ und Go verglichen wird.
Das ist der Hauptgrund für die schnelle Kompilierung. Und das ist beabsichtigt.
quelle
Ich denke, es ist nicht so, dass Go-Compiler schnell sind , sondern dass andere Compiler langsam sind .
C- und C ++ - Compiler müssen enorme Mengen an Headern analysieren - zum Kompilieren von C ++ "Hallo Welt" müssen beispielsweise 18.000 Codezeilen kompiliert werden, was fast einem halben Megabyte an Quellen entspricht!
Java- und C # -Compiler werden in einer VM ausgeführt. Das bedeutet, dass das Betriebssystem die gesamte VM laden muss, bevor sie etwas kompilieren können. Anschließend müssen sie von Bytecode zu nativem Code JIT-kompiliert werden. Dies alles dauert einige Zeit.
Die Geschwindigkeit der Kompilierung hängt von mehreren Faktoren ab.
Einige Sprachen sind so konzipiert, dass sie schnell kompiliert werden können. Zum Beispiel wurde Pascal so konzipiert, dass es mit einem Single-Pass-Compiler kompiliert werden kann.
Auch die Compiler selbst können optimiert werden. Zum Beispiel wurde der Turbo Pascal-Compiler in einem handoptimierten Assembler geschrieben, was in Kombination mit dem Sprachdesign zu einem sehr 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.
quelle
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 Sicht der Kompilierungsgeschwindigkeit sind einige Teile des C / C ++ - Ökosystems (z. B. Editoren, in denen Programmierer ihre Codes schreiben) nicht auf Kompilierungsgeschwindigkeit ausgelegt.
Hauptgrund : Schnelle Kompilierungsgeschwindigkeit war eine bewusste Wahl im Go-Compiler und auch in der Go-Sprache
Der Go-Compiler hat einen einfacheren Optimierer als C / C ++ - Compiler
Im Gegensatz zu C ++ hat Go keine Vorlagen und keine Inline-Funktionen. Dies bedeutet, dass Go keine Vorlagen- oder Funktionsinstanziierung durchführen muss.
Der Go-Compiler generiert Assembler-Code auf niedriger Ebene früher und der Optimierer arbeitet mit dem Assembler-Code, während in einem typischen C / C ++ - Compiler die Optimierung die Arbeit an einer internen Darstellung des ursprünglichen Quellcodes übergibt. Der zusätzliche Aufwand im C / C ++ - Compiler ergibt sich aus der Tatsache, dass die interne Darstellung generiert werden muss.
Die endgültige Verknüpfung (5l / 6l / 8l) eines Go-Programms kann langsamer sein als die Verknüpfung eines C / C ++ - Programms, da der Go-Compiler den gesamten verwendeten Assemblycode durchläuft und möglicherweise auch andere zusätzliche Aktionen als C / C ++ ausführt Linker tun das nicht
Einige C / C ++ - Compiler (GCC) generieren Anweisungen in Textform (die an den Assembler übergeben werden sollen), während der Go-Compiler Anweisungen in Binärform generiert. Zusätzliche Arbeit (aber nicht viel) muss getan werden, um den Text in Binär 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 abzielt
Compiler wie Jikes, die mit dem Ziel einer hohen Kompilierungsgeschwindigkeit entwickelt wurden, sind schnell. Auf einer 2-GHz-CPU kann Jikes mehr als 20000 Zeilen Java-Code pro Sekunde kompilieren (und der inkrementelle Kompilierungsmodus ist noch effizienter).
quelle
Die Kompilierungseffizienz war ein wichtiges Entwurfsziel:
Die Sprach-FAQ ist ziemlich interessant in Bezug auf bestimmte Sprachfunktionen im Zusammenhang mit dem Parsen:
quelle
aType
eine Variablenreferenz, und später in der Phase der semantischen Analyse, wenn Sie feststellen, dass zu diesem Zeitpunkt kein aussagekräftiger Fehler gedruckt wird.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 einschließen, die Sie direkt importieren (da diese bereits importiert haben, was sie benötigen). Dies steht in krassem Gegensatz zu C / C ++, wo jede einzelne Datei beginnt, einschließlich x-Headern, einschließlich y-Headern usw. Fazit: Die Kompilierung von Go dauert linear bis zur Anzahl der importierten Pakete, wobei C / C ++ exponentielle Zeit benötigt.
quelle
Ein guter Test für die Übersetzungseffizienz eines Compilers ist die Selbstkompilierung: Wie lange dauert es, bis ein bestimmter Compiler sich selbst kompiliert? Für C ++ dauert es sehr lange (Stunden?). Im Vergleich dazu würde sich ein Pascal / Modula-2 / Oberon-Compiler in weniger als einem kompilieren auf einer modernen Maschine Sekunde [1].
Go wurde von diesen Sprachen inspiriert, aber einige der Hauptgründe für diese Effizienz sind:
Eine klar definierte, mathematisch fundierte Syntax für effizientes Scannen und Parsen.
Eine typsichere und statisch kompilierte Sprache, die eine separate Kompilierung mit Abhängigkeits- und Typprüfung über Modulgrenzen hinweg verwendet, um unnötiges erneutes Lesen von Header-Dateien und erneutes Kompilieren anderer Module zu vermeiden - im Gegensatz zur unabhängigen Kompilierung wie in C / C ++, wo Der Compiler führt keine derartigen modulübergreifenden Überprüfungen durch (daher müssen alle Header-Dateien auch bei einem einfachen einzeiligen "Hallo Welt" -Programm immer wieder neu gelesen werden).
Eine effiziente Compiler-Implementierung (z. B. Top-Down-Analyse mit rekursivem Abstieg in einem Durchgang) - was natürlich durch die obigen Punkte 1 und 2 erheblich unterstützt wird.
Diese Prinzipien sind bereits in den 1970er und 1980er Jahren in Sprachen wie Mesa, Ada, Modula-2 / Oberon und mehreren anderen bekannt und vollständig umgesetzt worden und finden erst jetzt (in den 2010er Jahren) Eingang 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 passieren:
Zunächst sollten Anbieter von Softwareplattformen wie Google, Microsoft und Apple Anwendungsentwickler dazu ermutigen , die neue Kompilierungsmethode zu verwenden, und ihnen gleichzeitig ermöglichen, ihre vorhandene Codebasis wiederzuverwenden. Dies versucht Apple jetzt mit der Programmiersprache Swift zu tun, die mit Objective-C koexistieren kann (da dieselbe Laufzeitumgebung verwendet wird).
Zweitens sollten die zugrunde liegenden Softwareplattformen selbst im Laufe der Zeit unter Verwendung dieser Prinzipien neu geschrieben werden, während gleichzeitig die Modulhierarchie neu gestaltet wird, um sie weniger monolithisch zu machen. Dies ist natürlich eine Mammutaufgabe und kann den größten Teil eines Jahrzehnts in Anspruch nehmen (wenn sie mutig genug sind, dies 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 Angebot gilt für eine kostengünstige Xilinx Spartan-3-FPGA-Entwicklungsplatine, die mit einer Taktfrequenz von 25 MHz und 1 MByte Hauptspeicher betrieben wird. Von hier aus kann man auf "weniger als 1 Sekunde" für einen modernen Prozessor extrapolieren, der mit einer Taktfrequenz weit über 1 GHz und mehreren GByte Hauptspeicher läuft (dh mehrere Größenordnungen leistungsstärker als die Xilinx Spartan-3 FPGA-Karte). auch unter Berücksichtigung der E / A-Geschwindigkeiten. Bereits 1990, als Oberon auf einem 25-MHz-NS32X32-Prozessor mit 2-4 MByte Hauptspeicher ausgeführt wurde, kompilierte sich der Compiler in wenigen Sekunden. Der Gedanke, tatsächlich zu warten dem Compiler leicht einen Kompilierungszyklus beenden, der Oberon-Programmierern schon damals völlig unbekannt war. Für typische Programme ist es immer so dauerte 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 abgeschlossen hat. Es war wirklich eine sofortige Befriedigung mit Wartezeiten nahe Null. Und die Qualität des produzierten Codes war für die meisten Aufgaben bemerkenswert gut und im Allgemeinen durchaus akzeptabel, obwohl sie nicht immer mit den besten damals verfügbaren Compilern vergleichbar war.
quelle
Go wurde entwickelt, um schnell zu sein, und es zeigt.
Beachten Sie, dass GO nicht die einzige Sprache mit solchen Funktionen ist (Module sind in modernen Sprachen die Norm), aber sie haben es gut gemacht.
quelle
Zitat aus dem Buch " The Go Programming Language " von Alan Donovan und Brian Kernighan:
quelle
Die Grundidee der Kompilierung ist eigentlich sehr einfach. Ein Parser mit rekursivem Abstieg 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 erfordern nicht viel Berechnung.
Es ist jedoch nicht schwer, einen Compiler zu verlangsamen.
Wenn es eine Präprozessorphase mit mehrstufigen Include- Direktiven, Makrodefinitionen und bedingter Kompilierung gibt, so nützlich diese Dinge auch sind, ist es nicht schwer, sie herunterzuladen. (Zum Beispiel denke ich an die Windows- und MFC-Header-Dateien.) Deshalb 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.
quelle
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 folgt.
Beispiel: In diesem Codebeispiel: "Schnittstellen" überprüft der Compiler nicht, ob der beabsichtigte Typ die angegebene Schnittstelle implementiert , während er diesen Typ analysiert. Nur bis es verwendet wird (und WENN es verwendet wird), wird die Prüfung durchgeführt.
In einem anderen Beispiel teilt Ihnen der Compiler mit, ob Sie eine Variable deklarieren und nicht verwenden (oder ob Sie einen Rückgabewert halten sollen und nicht).
Folgendes wird nicht kompiliert:
Diese Art von Durchsetzungen und Prinzipien machen den resultierenden Code sicherer, und der Compiler muss keine zusätzlichen Überprüfungen durchführen, die der Programmierer durchführen kann.
Insgesamt erleichtern all diese Details das Parsen einer Sprache, was zu schnellen Kompilierungen führt.
Wieder in meinen eigenen Worten.
quelle
Ich denke, Go wurde parallel zur Compiler-Erstellung entwickelt, daher waren sie von Geburt an beste Freunde. (IMO)
quelle
Was sonst?
quelle