# Alle CPP-Dateien in eine einzige Kompilierungseinheit einschließen?

74

Ich hatte kürzlich Grund, mit einigen Visual Studio C ++ - Projekten mit den üblichen Debug- und Release-Konfigurationen zu arbeiten, aber auch mit 'Release All' und 'Debug All', die ich noch nie zuvor gesehen hatte.

Es stellt sich heraus, dass der Autor der Projekte eine einzige ALL.cpp hat, die alle anderen .cpp-Dateien enthält. Die * Alle Konfigurationen erstellen nur diese eine ALL.cpp-Datei. Es ist natürlich von den regulären Konfigurationen ausgeschlossen, und reguläre Konfigurationen erstellen nicht ALL.cpp

Ich habe mich nur gefragt, ob dies eine gängige Praxis ist. Welche Vorteile bringt es? (Meine erste Reaktion war, dass es schlecht roch.)

Auf welche Fallstricke werden Sie wahrscheinlich stoßen? Eine, an die ich denken kann, ist, wenn Sie anonyme Namespaces in Ihren .cpps haben, diese für diese cpp nicht mehr "privat" sind, sondern jetzt auch in anderen cpps sichtbar sind?

Alle Projekte erstellen DLLs, daher wäre es keine gute Idee, Daten in anonymen Namespaces zu haben, oder? Aber Funktionen wären OK?

Prost.

Steve Folly
quelle
2
Auf jeden Fall pathologisch; Ich kann nur raten, warum jemand das tun möchte (wenn Sie sie andererseits direkt fragen können, sollten Sie es tun). Normalerweise möchten Sie in C ++ das Gegenteil tun, indem Sie nicht nur Implementierungsdateien, sondern auch Header gut voneinander trennen. (Eine häufige Falle bei C ++ - Projekten ist "#include spaghetti", wobei jede Header-Datei von jeder anderen abhängt.) Vielleicht, um den Compiler einem Stresstest zu unterziehen?
Morendil
1
Es gibt ein kurzes Video , in dem der Zeitunterschied bei Unitybuild dargestellt wird.
Özgür
2
Eine Einführung zu "Unity Builds" mit Vor- und Nachteilen sowie einer vollständigen CMake-Integration finden Sie unter cheind.wordpress.com . hth, Christoph
cheind
Unser offizieller Build muss immer neu erstellt werden, daher glaube ich, dass dieser Ansatz die Build-Leistung erheblich verbessern kann. Da die offiziellen Builds jedoch hauptsächlich von Entwicklern verwendet werden, können die von UnityBuild generierten PDFs für Code ohne Unitybuild möglicherweise ungültig sein. (Wir wollen nicht mit einer Unity-Build-Konfiguration entwickeln, oder?)
Baiyan Huang
Ein völlig anderer Grund, einige Implementierungsdateien in eine andere Implementierungsdatei aufzunehmen, ist: Diese Dateien können automatisch generiert werden. Es ist viel einfacher, eine gesamte Datei automatisch zu generieren, als Änderungen in den vorhandenen Code einzufügen.
Sergey K.

Antworten:

61

Es wird von einigen (und Google-fähigen) als "Unity Build" bezeichnet. Es verbindet sich wahnsinnig schnell und kompiliert auch ziemlich schnell. Es eignet sich hervorragend für Builds, auf denen Sie nicht iterieren müssen, wie z. B. ein Release-Build von einem zentralen Server, aber nicht unbedingt für inkrementelle Builds.

Und es ist eine PITA zu pflegen.

BEARBEITEN: Hier ist der erste Google-Link für weitere Informationen: http://buffered.io/posts/the-magic-of-unity-builds/

Das macht es schnell, dass der Compiler alles nur einmal einlesen, kompilieren und dann verknüpfen muss, anstatt dies für jede CPP-Datei zu tun.

Bruce Dawson hat dies in seinem Blog viel besser beschrieben: http://randomascii.wordpress.com/2014/03/22/make-vc-compiles-fast-through-parallel-compilation/

MSN
quelle
27
Es liegt nicht nur an I / O. Jede einzelne CPP-Datei enthält häufig viele Header-Dateien. Wenn Sie sie separat kompilieren, kompilieren Sie den Code in den Header-Dateien mehrmals - einmal für jede .o-Datei. Wenn Sie "Unity Build" verwenden, werden diese Header-Dateien und alles andere nur einmal kompiliert.
Frank
6
Äh ... das fällt unter I / O. Aber ja, das ist genauer.
MSN
8
Kompilierungen sind normalerweise nicht an E / A gebunden, es sei denn, Sie haben nicht genügend Speicher. Siehe meine Antwort unten. Der Festplatten-Cache funktioniert. Der verlinkte Beitrag verbringt viel Zeit mit Sophistik, um zu erklären, wie schlecht die Festplatten-E / A sind, ist jedoch aufgrund des Festplatten-Cache irrelevant. Das mehrmalige Kompilieren des Codes in den Header-Dateien unterscheidet sich grundlegend von E / A. Die redundante Kompilierung ist das Problem, die redundante E / A passiert eigentlich nie. Einige der Gefahren von Unity Builds werden hier behandelt: randomascii.wordpress.com/2014/03/22/…
Bruce Dawson
1
Ich frage mich, ob Sie bei Verwendung von CMake die Unity Build-CPP-Datei aus der Liste der Quellen generieren können, die in ein Ziel eingegeben wurden. Immerhin hat CMake bereits alle diese Informationen. Ich frage mich auch, wie sich all dies ändert, wenn Flash-Speicherlaufwerke anstelle von sich drehenden Laufwerken verwendet werden, aber die meisten Leute haben bereits kommentiert, dass die rohen E / A nicht das Problem sind, sondern die wiederholte Kompilierung. Bei einem Build ohne Einheit habe ich sicherlich eine Verbesserung bei Flash-Laufwerken für die rohen E / A festgestellt.
Legalisieren Sie den
3
eine weitere Sache - ein Unity-Build muss nicht mit einer Unity-Quelldatei durchgeführt werden (zum Beispiel kann ein 100-Datei-Ziel als 10 Unity-Dateien erstellt werden, die jeweils 10 der ursprünglichen Dateien enthalten). Mit https://github.com/sakra/cotire können Sie steuern, wie viele Unity-Quelldateien verwendet werden. Schauen Sie sich auch https://github.com/onqtam/ucm an - eine Hülle aus Cotire, um die Verwendung von Unity-Builds (und mehr) zu vereinfachen.
Onqtam
56

Unity erstellt aus drei Hauptgründen verbesserte Build-Geschwindigkeiten. Der erste Grund ist, dass alle freigegebenen Header-Dateien nur einmal analysiert werden müssen. Viele C ++ - Projekte enthalten viele Header-Dateien, die in den meisten oder allen CPP-Dateien enthalten sind, und das redundante Parsen dieser Dateien ist der Hauptaufwand für die Kompilierung, insbesondere wenn Sie viele kurze Quelldateien haben. Vorkompilierte Header-Dateien können bei diesen Kosten helfen, aber normalerweise gibt es viele Header-Dateien, die nicht vorkompiliert sind.

Der nächste Hauptgrund dafür, dass Unity-Builds die Build-Geschwindigkeit verbessern, liegt darin, dass der Compiler weniger oft aufgerufen wird. Das Aufrufen des Compilers verursacht einige Startkosten.

Schließlich bedeutet die Reduzierung der redundanten Header-Analyse eine Reduzierung des redundanten Code-Gens für Inline-Funktionen, sodass die Gesamtgröße der Objektdateien kleiner ist, was die Verknüpfung beschleunigt.

Unity-Builds können auch zu einem besseren Code-Gen führen.

Unity-Builds sind aufgrund reduzierter Festplatten-E / A NICHT schneller. Ich habe viele Builds mit xperf profiliert und weiß, wovon ich spreche. Wenn Sie über ausreichend Speicher verfügen, vermeidet der Betriebssystem-Festplatten-Cache die redundante E / A - nachfolgende Lesevorgänge eines Headers werden aus dem Betriebssystem-Festplatten-Cache gesendet. Wenn Sie nicht über genügend Speicher verfügen, können Unity-Builds die Build-Zeiten sogar verschlechtern, da der Speicherbedarf des Compilers den verfügbaren Speicher überschreitet und ausgelagert wird.

Festplatten-E / A sind teuer, weshalb alle Betriebssysteme Daten aggressiv zwischenspeichern, um redundante Festplatten-E / A zu vermeiden.

Bruce Dawson
quelle
1
Die richtigste Antwort hier. Für massive Projekte gibt es keine Alternative für die Build-Geschwindigkeit. Übrigens schlug msbuild / LTCG vor, die Verbindungszeit von 4 Minuten auf über Nacht zu erhöhen! (Clang war besser).
ChocoBilly
Beachten Sie, dass VS 2015 den unten aufgeführten Fehler behebt, durch den LTCG-Links erheblich schneller ausgeführt werden - bei einigen kürzlich durchgeführten umfangreichen Tests dreimal schneller. connect.microsoft.com/VisualStudio/feedback/details/1064219/…
Bruce Dawson
Richtig. Die C ++ - Kompilierung ist langsam, da jede Kompilierungseinheit separat kompiliert werden muss. Warum dauert die C ++ - Kompilierung so lange?
Phuclv
7

Ich frage mich, ob ALL.cpp versucht, das gesamte Projekt in einer einzigen Kompilierungseinheit zusammenzufassen, um die Fähigkeit des Compilers zu verbessern, das Programm auf Größe zu optimieren.

Normalerweise werden einige Optimierungen nur innerhalb bestimmter Kompilierungseinheiten durchgeführt, z. B. Entfernen von doppeltem Code und Inlining.

Trotzdem scheine ich mich daran zu erinnern, dass neuere Compiler (Microsoft, Intel, aber ich glaube nicht, dass dies GCC einschließt) diese Optimierung über mehrere Kompilierungseinheiten hinweg durchführen können, daher vermute ich, dass dieser "Trick" nicht notwendig ist.

Trotzdem wäre es neugierig zu sehen, ob es tatsächlich einen Unterschied gibt.

Arafangion
quelle
2
Visual C ++ kann die gesamte Programmoptimierung mit dem Schalter / LTCG (Link-Time-Code-Generierung) durchführen
Roger Lipscombe
1
In diesen Tagen unterstützen GCC und Clang auch LTO
Justin
Dann ist es sicher unnötig.
Arafangion
Dies reduziert die Kopfbewegungen von SPEED und Festplattenlaufwerk VIEL!
17етър Петров
1
Петър Петров: Haben Sie Benchmarks, die dies anzeigen? Es sollte die Geschwindigkeit erhöhen, da es nicht erforderlich ist, Header-Dateien rekursiv und wiederholt zu besuchen, zu öffnen und zu analysieren.
Arafangion
2

Ich stimme Bruce zu; Aus meiner Erfahrung heraus hatte ich versucht, den Unity Build für eines meiner DLL-Projekte zu implementieren, das eine Menge Header-Includes und viele CPPs enthielt. Um die Gesamtkompilierungszeit auf dem VS2010 zu verkürzen (hatte die inkrementellen Build-Optionen bereits ausgeschöpft), aber anstatt die Kompilierungszeit zu verkürzen, ging mir der Speicher aus und der Build konnte die Kompilierung nicht einmal beenden.

Jedoch hinzuzufügen; Ich habe festgestellt, dass das Aktivieren der Kompilierungsoption für mehrere Prozessoren in Visual Studio sehr hilfreich ist, um die Kompilierungszeit zu verkürzen. Ich bin nicht sicher, ob eine solche Option für andere Plattform-Compiler verfügbar ist.

spforay
quelle
2
Wenn Sie zustimmen und nichts hinzufügen, sollte dies keine Antwort sein. Das klingt eher nach einer Geschichte und einem Kommentar.
RacerNerd
1
Tatsächlich; Ich wollte einen Kommentar zu meiner eigenen Erfahrung mit dem Problem hinzufügen (auf industrieller Stärke und Codebasis). für jemanden, der das Gleiche versucht, weil die Idee von Unity Builds den Eindruck erweckt, dass dies fast zu einer geringeren Zusammenstellung führen würde; was bei großen Projekten nicht der Fall ist. Der Grund, warum wir dies überhaupt versuchen würden; aber ich bin niemand, der Ausnahmen und bestimmte Designüberlegungen ausschließt.
Spforay
0

Wir haben ein Multi-Plattform-Projekt für MacOS und Windows. Wir haben viele große Vorlagen und viele Header. Wir haben die Erstellungszeit mit Visual Studio 2017 msvc mithilfe vorkompilierter Header halbiert. Für MacOS haben wir mehrere Tage versucht, die Erstellungszeit zu reduzieren, aber vorkompilierte Header haben die Erstellungszeit nur auf 85% reduziert. Die Verwendung einer einzelnen CPP-Datei war der Durchbruch. Wir erstellen diese ALL.cpp einfach mit einem Skript. Unsere ALL.cpp enthält eine Liste der Includes aller .cpp-Dateien unseres Projekts. Mit XCode 9.0 reduzieren wir die Bauzeit auf 30%. Der einzige Nachteil war, dass wir allen privaten Namespaces für unbenannte Kompilierungseinheiten in .cpp-Files Namen geben mussten. Andernfalls kollidieren die lokalen Namen.

Stephanie
quelle