it-swarm.com.de

Warum dauert die C ++ - Kompilierung so lange?

Das Kompilieren einer C++ - Datei dauert im Vergleich zu C # und Java sehr lange. Das Kompilieren einer C++ - Datei dauert erheblich länger als das Ausführen eines Skripts normaler Größe Python. Ich verwende derzeit VC++, aber es ist bei jedem Compiler das Gleiche. Warum ist das so?

Die beiden Gründe, die ich mir vorstellen konnte, waren das Laden von Header-Dateien und das Ausführen des Präprozessors, aber das scheint nicht zu erklären, warum es so lange dauert.

509
Dan Goldstein

Mehrere Gründe

Header-Dateien

Für jede einzelne Kompilierungseinheit müssen Hunderte oder sogar Tausende von Headern (1) geladen und (2) kompiliert werden. Jeder von ihnen muss normalerweise für jede Kompilierungseinheit neu kompiliert werden, da der Präprozessor sicherstellt, dass das Ergebnis des Kompilierens eines Headers kann zwischen jeder Kompilierungseinheit variieren kann. (In einer Zusammenstellungseinheit kann ein Makro definiert werden, das den Inhalt des Headers ändert.).

Dies ist wahrscheinlich der Hauptgrund, da für jede Kompilierungseinheit große Mengen an Code kompiliert werden müssen und außerdem jeder Header mehrmals kompiliert werden muss (einmal für jede Kompilierungseinheit, die ihn enthält). .

Verknüpfen

Nach dem Kompilieren müssen alle Objektdateien miteinander verknüpft werden. Dies ist im Grunde ein monolithischer Prozess, der nicht sehr gut parallelisiert werden kann und Ihr gesamtes Projekt verarbeiten muss.

Parsing

Die Syntax ist extrem kompliziert zu analysieren, hängt stark vom Kontext ab und ist sehr schwer zu unterscheiden. Das kostet viel Zeit.

Vorlagen

In C # wird List<T> ist der einzige Typ, der kompiliert wird, unabhängig davon, wie viele List-Instanzen sich in Ihrem Programm befinden. In C++ wird vector<int> ist ein völlig anderer Typ als vector<float>, und jeder muss separat kompiliert werden.

Hinzu kommt, dass Vorlagen eine vollständige Turing-vollständige "Sub-Sprache" bilden, die der Compiler interpretieren muss, und dies kann lächerlich kompliziert werden. Sogar relativ einfacher Vorlagen-Metaprogrammcode kann rekursive Vorlagen definieren, die Dutzende von Vorlageninstanziierungen erstellen. Vorlagen können auch zu extrem komplexen Typen mit lächerlich langen Namen führen, was dem Linker viel zusätzliche Arbeit einbringt. (Es müssen viele Symbolnamen verglichen werden, und wenn diese Namen aus vielen tausend Zeichen bestehen, kann das ziemlich teuer werden.).

Und natürlich verschlimmern sie die Probleme mit Header-Dateien, da Vorlagen in der Regel in Headern definiert werden müssen, was bedeutet, dass für jede Kompilierungseinheit viel mehr Code analysiert und kompiliert werden muss. In einfachem C-Code enthält ein Header normalerweise nur Vorwärtsdeklarationen, aber nur sehr wenig tatsächlichen Code. In C++ ist es nicht ungewöhnlich, dass sich fast der gesamte Code in Headerdateien befindet.

Optimierung

C++ ermöglicht einige sehr dramatische Optimierungen. Mit C # oder Java) können Klassen nicht vollständig entfernt werden (sie müssen zu Reflektionszwecken vorhanden sein), aber selbst ein einfaches C++ - Vorlagenmetaprogramm kann problemlos Dutzende oder Hunderte von Klassen generieren die in der Optimierungsphase eingezeichnet und wieder beseitigt werden.

Darüber hinaus muss ein C++ - Programm vom Compiler vollständig optimiert werden. Ein C # -Programm kann sich darauf verlassen, dass der JIT-Compiler beim Laden zusätzliche Optimierungen vornimmt. C++ erhält keine solchen "zweiten Chancen". Was der Compiler generiert, ist so optimiert, wie es nur geht.

Maschine

C++ ist zu Maschinencode kompiliert, der etwas komplizierter sein kann als der Bytecode Java oder .Net Use (insbesondere im Fall von x86). (Dies wird nur aus Gründen der Vollständigkeit erwähnt, weil es erwähnt wurde.) In der Praxis ist es unwahrscheinlich, dass dieser Schritt mehr als einen winzigen Bruchteil der gesamten Kompilierungszeit in Anspruch nimmt.

Fazit

Die meisten dieser Faktoren werden von C-Code gemeinsam genutzt, der tatsächlich ziemlich effizient kompiliert wird. Der Parsing-Schritt ist in C++ viel komplizierter und kann erheblich mehr Zeit in Anspruch nehmen, aber der Haupttäter sind wahrscheinlich Vorlagen. Sie sind nützlich und machen C++ zu einer viel mächtigeren Sprache, fordern aber auch ihren Tribut in Bezug auf die Kompilierungsgeschwindigkeit.

769
jalf

Die Verlangsamung ist bei keinem Compiler unbedingt gleich.

Ich habe Delphi oder Kylix nicht verwendet, aber in den MS-DOS-Tagen kompilierte sich ein Turbo Pascal-Programm fast augenblicklich, während das entsprechende Turbo C++ - Programm nur crawlt.

Die beiden Hauptunterschiede waren ein sehr starkes Modulsystem und eine Syntax, die das Kompilieren in einem Durchgang ermöglichte.

Es ist sicher möglich, dass die Kompilierungsgeschwindigkeit für C++ - Compilerentwickler keine Priorität hatte, aber es gibt auch einige inhärente Komplikationen in der C/C++ - Syntax, die die Verarbeitung erschweren. (Ich bin kein Experte für C, aber Walter Bright ist es. Nachdem er verschiedene kommerzielle C/C++ - Compiler erstellt hatte, schuf er die Sprache D. Eine seiner Änderungen bestand darin, eine kontextfreie Grammatik durchzusetzen um die Sprache leichter zu analysieren.)

Außerdem werden Sie feststellen, dass Makefiles im Allgemeinen so eingerichtet sind, dass jede Datei separat in C kompiliert wird. Wenn also 10 Quelldateien dieselbe Include-Datei verwenden, wird diese Include-Datei zehnmal verarbeitet.

37
tangentstorm

Parsing und Code-Generierung sind eigentlich ziemlich schnell. Das eigentliche Problem ist das Öffnen und Schließen von Dateien. Denken Sie daran, dass der Compiler auch mit Include-Guards die .H-Datei noch geöffnet hat und jede Zeile liest (und dann ignoriert).

Ein Freund nahm einmal (bei der Arbeit gelangweilt) die Bewerbung seines Unternehmens und legte alles - alle Quell- und Header-Dateien - in einer großen Datei zusammen. Die Kompilierungszeit wurde von 3 Stunden auf 7 Minuten gesenkt.

36
James Curran

C++ wird in Maschinencode kompiliert. Sie haben also den Pre-Prozessor, den Compiler, den Optimierer und schließlich den Assembler, die alle ausgeführt werden müssen.

Java und C # werden in Bytecode/IL kompiliert, und die virtuelle Maschine Java/.NET Framework wird vor der Ausführung ausgeführt (oder JIT wird in Maschinencode kompiliert).

Python ist eine interpretierte Sprache, die auch in Bytecode kompiliert wird.

Ich bin mir sicher, dass es auch andere Gründe dafür gibt, aber im Allgemeinen spart es Zeit, wenn Sie nicht in die Maschinensprache kompilieren müssen.

15
Alan

Ein weiterer Grund ist die Verwendung des C-Preprozessors zum Auffinden von Deklarationen. Auch bei Header-Guards muss .h immer wieder analysiert werden, jedes Mal, wenn sie enthalten sind. Einige Compiler unterstützen vorkompilierte Header, die dabei helfen können, werden jedoch nicht immer verwendet.

Siehe auch: C++ - Antworten auf häufig gestellte Fragen

15
Dave Ray

Die größten Probleme sind:

1) Die unendliche Header-Reparsing. Schon erwähnt. Minderungen (wie #pragma einmal) funktionieren normalerweise nur pro Kompilierungseinheit, nicht pro Build.

2) Die Tatsache, dass die Toolchain häufig in mehrere Binärdateien unterteilt ist (make, preprocessor, compiler, assembler, archiver, impdef, linker und dlltool in extremen Fällen), die alle für jeden Aufruf den gesamten Status neu initialisieren und neu laden müssen ( Compiler, Assembler) oder alle paar Dateien (Archiver, Linker und dlltool).

Siehe auch diese Diskussion über Compiler: http://compilers.iecc.com/comparch/article/03-11-078 speziell diese:

http://compilers.iecc.com/comparch/article/02-07-128

Beachten Sie, dass John, der Moderator von comp.compilers, einverstanden zu sein scheint und dass dies bedeutet, dass es möglich sein sollte, ähnliche Geschwindigkeiten auch für C zu erzielen, wenn man die Toolchain vollständig integriert und vorkompilierte Header implementiert. Viele kommerzielle C-Compiler tun dies bis zu einem gewissen Grad.

Beachten Sie, dass das Unix-Modell, bei dem alles in eine separate Binärdatei zerlegt wird, eine Art Worst-Case-Modell für Windows ist (mit seiner langsamen Prozesserstellung). Dies ist beim Vergleich der GCC-Erstellungszeiten zwischen Windows und * nix sehr auffällig, insbesondere wenn das make/configure-System auch einige Programme aufruft, nur um Informationen zu erhalten.

12

C/C++ erstellen: Was passiert wirklich und warum dauert es so lange

Ein relativ großer Teil der Softwareentwicklungszeit wird nicht für das Schreiben, Ausführen, Debuggen oder Entwerfen von Code aufgewendet, sondern für das Warten, bis die Kompilierung abgeschlossen ist. Um die Dinge schneller zu machen, müssen wir zuerst verstehen, was passiert, wenn C/C++ - Software kompiliert wird. Die Schritte sind ungefähr wie folgt:

  • Aufbau
  • Start des Build-Tools
  • Abhängigkeitsprüfung
  • Zusammenstellung
  • Verknüpfen

Wir werden uns nun jeden Schritt genauer ansehen und uns darauf konzentrieren, wie sie schneller gemacht werden können.

Konfiguration

Dies ist der erste Schritt, wenn Sie mit dem Erstellen beginnen. In der Regel bedeutet dies, ein Konfigurationsskript oder CMake, Gyp, SCons oder ein anderes Tool auszuführen. Dies kann bei sehr großen Autotools-basierten Konfigurationsskripten zwischen einer Sekunde und mehreren Minuten dauern.

Dieser Schritt kommt relativ selten vor. Es muss nur ausgeführt werden, wenn Konfigurationen geändert oder die Build-Konfiguration geändert wird. Es ist nicht viel zu tun, um diesen Schritt zu beschleunigen, ohne die Build-Systeme zu ändern.

Start des Build-Tools

Dies passiert, wenn Sie make ausführen oder auf das Build-Symbol eines IDE (normalerweise ein Alias ​​für make) klicken. Die Binärdatei des Build-Tools startet und liest die Konfigurationsdateien sowie die Build-Konfiguration, die in der Regel das gleiche sind.

Abhängig von der Komplexität und Größe des Builds kann dies zwischen einem Sekundenbruchteil und mehreren Sekunden dauern. An sich wäre das nicht so schlimm. Leider führen die meisten make-basierten Build-Systeme dazu, dass make für jeden einzelnen Build zehn- bis hundertmal aufgerufen wird. Normalerweise wird dies durch die rekursive Verwendung von make verursacht (was schlecht ist).

Es sollte beachtet werden, dass der Grund, warum Make so langsam ist, kein Implementierungsfehler ist. Die Syntax von Makefiles hat einige Macken, die eine wirklich schnelle Implementierung so gut wie unmöglich machen. In Kombination mit dem nächsten Schritt macht sich dieses Problem noch deutlicher bemerkbar.

Abhängigkeitsprüfung

Nachdem das Build-Tool seine Konfiguration gelesen hat, muss festgestellt werden, welche Dateien geändert wurden und welche neu kompiliert werden müssen. Die Konfigurationsdateien enthalten ein gerichtetes azyklisches Diagramm, das die Build-Abhängigkeiten beschreibt. Dieses Diagramm wird normalerweise während des Konfigurationsschritts erstellt. Die Startzeit des Build-Tools und der Abhängigkeitsscanner werden bei jedem einzelnen Build ausgeführt. Ihre kombinierte Laufzeit bestimmt die Untergrenze des Zyklus zum Bearbeiten, Kompilieren und Debuggen. Bei kleinen Projekten beträgt diese Zeit normalerweise einige Sekunden. Das ist erträglich. Es gibt Alternativen zu Make. Das schnellste davon ist Ninja, das von Google-Ingenieuren für Chromium entwickelt wurde. Wenn Sie CMake oder Gyp zum Erstellen verwenden, wechseln Sie einfach zu den Ninja-Backends. Sie müssen nichts an den Build-Dateien selbst ändern. Genießen Sie einfach den Geschwindigkeitsschub. Da Ninja auf den meisten Distributionen nicht enthalten ist, müssen Sie es möglicherweise selbst installieren.

Zusammenstellung

Zu diesem Zeitpunkt rufen wir endlich den Compiler auf. Einige Ecken abschneiden, hier sind die ungefähren Schritte.

  • Zusammenführen von Includes
  • Code analysieren
  • Codegenerierung/Optimierung

Entgegen der landläufigen Meinung ist das Kompilieren von C++ eigentlich gar nicht so langsam. Die STL ist langsam und die meisten Build-Tools, die zum Kompilieren von C++ verwendet werden, sind langsam. Es gibt jedoch schnellere Tools und Möglichkeiten, um die langsamen Teile der Sprache zu entschärfen.

Ihre Verwendung erfordert etwas Ellenbogenfett, die Vorteile sind jedoch unbestreitbar. Kürzere Build-Zeiten führen zu glücklicheren Entwicklern, mehr Agilität und schließlich zu besserem Code.

11

Eine kompilierte Sprache erfordert immer einen höheren anfänglichen Aufwand als eine interpretierte Sprache. Außerdem haben Sie Ihren C++ - Code möglicherweise nicht sehr gut strukturiert. Zum Beispiel:

#include "BigClass.h"

class SmallClass
{
   BigClass m_bigClass;
}

Kompiliert viel langsamer als:

class BigClass;

class SmallClass
{
   BigClass* m_bigClass;
}
7
Andy Brice

Eine einfache Möglichkeit, die Kompilierungszeit in größeren C++ - Projekten zu verkürzen, besteht darin, eine * .cpp-Include-Datei zu erstellen, die alle cpp-Dateien in Ihrem Projekt enthält, und diese zu kompilieren. Dies reduziert das Header-Explosionsproblem auf einmal. Dies hat den Vorteil, dass Kompilierungsfehler weiterhin auf die richtige Datei verweisen.

Angenommen, Sie haben a.cpp, b.cpp und c.cpp. Erstellen Sie eine Datei: everything.cpp:

#include "a.cpp"
#include "b.cpp"
#include "c.cpp"

Kompilieren Sie dann das Projekt, indem Sie einfach everything.cpp erstellen

6
rileyberton

Einige Gründe sind:

1) C++ - Grammatik ist komplexer als C # oder Java) und benötigt mehr Zeit zum Parsen.

2) (Wichtiger) C++ - Compiler erzeugt Maschinencode und führt alle Optimierungen während der Kompilierung durch. C # und Java gehen nur halbwegs und überlassen diese Schritte JIT.

4

Der Nachteil, den Sie bekommen, ist, dass das Programm ein bisschen schneller läuft. Das mag ein kalter Trost für Sie während der Entwicklung sein, aber es könnte sehr wichtig sein, wenn die Entwicklung abgeschlossen ist und das Programm nur von Benutzern ausgeführt wird.

4
T.E.D.

Die meisten Antworten sind etwas unklar, wenn erwähnt wird, dass C # aufgrund der Kosten für die Ausführung von Aktionen, die in C++ nur einmal zur Kompilierungszeit ausgeführt werden, immer langsamer ausgeführt wird. Diese Leistungskosten werden auch durch Laufzeitabhängigkeiten beeinflusst (mehr Dinge müssen geladen werden, um zu können) nicht zu vergessen, dass C # -Programme immer einen höheren Speicherbedarf haben, was dazu führt, dass die Leistung enger mit der Kapazität der verfügbaren Hardware zusammenhängt. Gleiches gilt für andere Sprachen, die interpretiert werden oder von einer VM abhängig sind.

2
Panic

Ich kann mir zwei Probleme vorstellen, die sich auf die Geschwindigkeit auswirken, mit der Ihre Programme in C++ kompiliert werden.

MÖGLICHES PROBLEM 1 - KOMPILIEREN DES HEADERS: (Dies wurde möglicherweise bereits durch eine andere Antwort oder einen anderen Kommentar beantwortet.) Microsoft Visual C++ (A.K.A. VC++) unterstützt vorkompilierte Header, die ich sehr empfehle. Wenn Sie ein neues Projekt erstellen und die Art des Programms auswählen, das Sie erstellen, sollte ein Setup-Assistentenfenster auf Ihrem Bildschirm angezeigt werden. Wenn Sie unten auf die Schaltfläche "Weiter>" klicken, werden Sie zu einer Seite mit mehreren Funktionslisten geführt. Stellen Sie sicher, dass das Kontrollkästchen neben der Option "Vorkompilierter Header" aktiviert ist. (HINWEIS: Dies ist meine Erfahrung mit Win32-Konsolenanwendungen in C++, dies ist jedoch möglicherweise nicht bei allen Arten von Programmen in C++ der Fall.)

MÖGLICHE AUSGABE 2 - DER ORT WIRD ERSTELLT: In diesem Sommer nahm ich an einem Programmierkurs teil, und wir mussten alle unsere Projekte auf 8-GB-Flash-Laufwerken als Computer in dem von uns verwendeten Labor speichern wurde jede Nacht um Mitternacht abgewischt, was all unsere Arbeit gelöscht hätte. Wenn Sie aus Gründen der Portabilität/Sicherheit/usw. auf ein externes Speichergerät kompilieren, kann dies (auch bei vorkompiliertem Gerät) eine sehr lange Zeit in Anspruch nehmen Header, die ich oben erwähnt habe), damit Ihr Programm kompiliert werden kann, besonders wenn es ein ziemlich großes Programm ist. Mein Rat für Sie in diesem Fall wäre, Programme auf der Festplatte des verwendeten Computers zu erstellen und zu kompilieren, und wann immer Sie die Arbeit an Ihren Projekten aus irgendeinem Grund einstellen möchten/müssen, übertragen Sie sie auf Ihr externes System Klicken Sie auf das Symbol „Hardware sicher entfernen und Medien auswerfen“, das als kleines Flash-Laufwerk hinter einem kleinen grünen Kreis mit einem weißen Häkchen angezeigt wird, um die Verbindung zu trennen.

Ich hoffe das hilft dir; Lass es mich wissen! :)

1
cjor530

Wie bereits erwähnt, verbringt der Compiler viel Zeit damit, die Vorlagen zu instanziieren und erneut zu instanziieren. In einem solchen Ausmaß, dass es Projekte gibt, die sich auf diesen speziellen Punkt konzentrieren und in einigen wirklich günstigen Fällen eine beobachtbare 30-fache Beschleunigung beanspruchen. Siehe http://www.zapcc.com .

0
akim