Ich hab hier ein relativ grosses Projekt das lange zum kompilieren
braucht
Windows x64
VStudio 2017, mit Solution
+500k LOC
40min+ Kompilierzeit :(
viele Expression-Templates
sehr viel Header-Only Code
alles was eben nicht so gut für die Kompilierzeit ist
leider kompilieren grosse Teile des Projektes (noch) nicht mit Clang
sonst würde ich komplett das neue -ftime-trace
(https://aras-p.info/blog/2019/01/16/time-trace-timeline-flame-chart-profiler-for-Clang/)
nutzen
also probieren ich mit den Flags
/d2cgsummary
(https://aras-p.info/blog/2017/10/23/Best-unknown-MSVC-flag-d2cgsummary/)
/Bt+
(https://devblogs.microsoft.com/cppblog/vc-tip-get-detailed-build-throughput-diagnostics-using-msbuild-compiler-and-linker/)
/d1reportTime
(https://aras-p.info/blog/2019/01/21/Another-cool-MSVC-flag-d1reportTime/)
und
unter VStudio: "Tools" -> "Options" -> "Projects and Solutions" -> "VC++
Project Settings" -> Tick "Build Timing": "Yes"
den bösen auf die schliche zu kommen - damit ich mit den richtigen
Übeltätern anfange kann (z.B. in dem ich die Templates unter Funktionen
mit spezialisiertem Interface verberge - was bei diesem Projekt kein
Problem ist)
Mir fehlen nur irgendwie die einzelnen Kompilierzeiten der cpp - als
Codegenerierungs-Zeit, und ich möchte nicht unbedingt meinen Build
komplett Umbauen damit ich die einzelnen Cpps timen kann
Hat hier jemand Erfahrungen mit dem VStudio und
Compiletime-Benchmarking?
Ich würde spontan nach der "vorkompilierte Header" Option schauen. Ist
mitunter deaktiviert, weil ältere VS Versionen da subtil andere Syntax
hatten.
Ansonsten mal mit Task Manager oder Ressourcen Monitor nachschauen wo
der Flaschenhals ist. Bei 40 Minuten ist das oft nicht die CPU sondern
externe Ressourcen - oder eine halb kaputte Platte/SSD die ewig braucht
bis sie bestimmte Sektoren beim Lesen liefert.
Hi,
nun ja als Compilier-Maschine ist Windows natürlich suboptimal.
Ich übersetze mein Projekt das eine ähnliche Größe hat mit mingw-gcc
unter Linux 64bit Kernel 5.5.7. Der Rechner hat einen Hexa-Core
Prozessor mit HT. 32 GB RAM. 2 TB NVMe SSD. Make wird mit Parameter -j
10 gestartet.
Was hast du denn für eine Maschine zur Verfügung?
Gruß
Olaf
Jim M. schrieb:> Ich würde spontan nach der "vorkompilierte Header" Option schauen.> Ist> mitunter deaktiviert, weil ältere VS Versionen da subtil andere Syntax> hatten.>> Ansonsten mal mit Task Manager oder Ressourcen Monitor nachschauen wo> der Flaschenhals ist. Bei 40 Minuten ist das oft nicht die CPU sondern> externe Ressourcen - oder eine halb kaputte Platte/SSD die ewig braucht> bis sie bestimmte Sektoren beim Lesen liefert.
PCHs sind aktiv, bringen bei Expression Templates (vergleichbar mit der
Magie von Eigen) aber nicht so viel - deswegen will ja nach einer
Analyse das anfangen zurueck zu bauen, was wohl ein paar Wochen dauern
wird
Es gibt 15 Entwickler mit identischen Maschinen i5 oder i7 6 Kerne, 16GB
RAM, SSDs, also keine langsamen Kisten
Ich bin als externer da um das Kind aus dem Brunnen zu holen
Olaf D. schrieb:> Hi,>> nun ja als Compilier-Maschine ist Windows natürlich suboptimal.> Ich übersetze mein Projekt das eine ähnliche Größe hat mit mingw-gcc> unter Linux 64bit Kernel 5.5.7. Der Rechner hat einen Hexa-Core> Prozessor mit HT. 32 GB RAM. 2 TB NVMe SSD. Make wird mit Parameter -j> 10 gestartet.>> Was hast du denn für eine Maschine zur Verfügung?>> Gruß> Olaf
Davon sind aber bestimmt nicht mind. 100kLOC Header only Expression
Template Libs, die sind der Teufel für die Kompilierzeit
Leider ist das Projekt noch zu Microsoft spezifisch fuer den gcc
Es gibt einen (Monster!!) 56 Core Buildserver, der bringt den lokalen
Entwicklung aber nicht so viel, Incredibuild ist in Planung - ich will
aber das Problem in der Basis lösen und nicht mit Hardware drauf hauen,
die skalierung mit den Templates ist so mies und es ist dann auch nur
eine Frage der Zeit bis es wieder lange dauert
Einige template Konstrukte sind ziemliche Compiler-Bremsen, die kann man
verbessern. Faktor 3 oder so kann drin sein... Das zu optimieren ist
aber eine Menge Try&Error. Einzelne Teile rausnehmen, kompilieren und
schauen was bremst. Rekursive template Iterationen sind oft ein Problem,
die durch fold expressions zu ersetzen bringt einiges. Auch schlecht
sind verschachtelte templates wie foo<foo<foo<foo ... Listen
Konkatenation kann man beschleunigen indem man für viele Fälle
Spezialisierungen codiert, z.B. für 1-32 Sub-Listen o.ä. (ggf.
Code-Generierung). Indem man andere Algorithmen auf dieser Basis baut
werden die auch schneller. Sowas wie Suchen in Typlisten geht mit dem
Multi-Basis-Klassen-Trick sehr effizient. Geshickte Anwendung von
std::apply statt wiederholtes std::get auf std::tuple hilft auch. Usw...
Wenn du konkrete einzelne Bestandteile identifiziert hast die besonders
langsam sind kannst du die ja ggf. auch posten.
Programmierer schrieb:> Einige template Konstrukte sind ziemliche Compiler-Bremsen, die> kann man> verbessern. Faktor 3 oder so kann drin sein... Das zu optimieren ist> aber eine Menge Try&Error. Einzelne Teile rausnehmen, kompilieren und> schauen was bremst. Rekursive template Iterationen sind oft ein Problem,> die durch fold expressions zu ersetzen bringt einiges. Auch schlecht> sind verschachtelte templates wie foo<foo<foo<foo ... Listen> Konkatenation kann man beschleunigen indem man für viele Fälle> Spezialisierungen codiert, z.B. für 1-32 Sub-Listen o.ä. (ggf.> Code-Generierung). Indem man andere Algorithmen auf dieser Basis baut> werden die auch schneller. Sowas wie Suchen in Typlisten geht mit dem> Multi-Basis-Klassen-Trick sehr effizient. Geshickte Anwendung von> std::apply statt wiederholtes std::get auf std::tuple hilft auch. Usw...> Wenn du konkrete einzelne Bestandteile identifiziert hast die besonders> langsam sind kannst du die ja ggf. auch posten.
Danke für die Tips
Ich bin auch schon ein bisschen weiter gekommen, mit /Bt+ bekomme ich
auch das Timing der im Backend und Frontend gebraucht wird, jetzt muss
ich einen kleinen Parser schreiben der die Infomationen sauber
verarbeitet
Hi,
überprüfe penibel alle Header auf das vorhandensein von Include-Guards.
Auch ist nicht hilfreich alle in einer Klassen-Definition benötigten
Header
im Header der Klasse zu includieren.
Besser wo möglich eine Vorwärtsdeklaration im Header verwenden und den
benötigten Header im Cpp File includieren.
Gruß
Olaf
Olaf D. schrieb:> Hi,>> überprüfe penibel alle Header auf das vorhandensein von Include-Guards.> Auch ist nicht hilfreich alle in einer Klassen-Definition benötigten> Header> im Header der Klasse zu includieren.>> Besser wo möglich eine Vorwärtsdeklaration im Header verwenden und den> benötigten Header im Cpp File includieren.>> Gruß> Olaf
Da wird penibel drauf geachtet, wäre a er auch toll ein Tool zu haben
das die Einhaltung prüfbar macht - gibt es so ein Tool oder kann das
z.B. CppDepend?
rbx schrieb im Beitrag #6162648:
> cppbert schrieb:>> Das ist sehr schön für Java>> Hast du zufällig was (passendes) zu "templated Lambdas" ? Ich würde> gerne mehr darüber lernen, wie man sowas (aber vor allem für parallel,> z.B. Roboter) sinnvoll nutzen kann.
bitte kein Post-Hijacking (d.h. neuen Post erstellen)
und Templates sind im allgemeinen zur Code-Reduzierung oder
Policy-Forcierung gedacht
Da gibt es erstmal keine direkte Ausrichtung auf parallel (oder z.B.
Roboter?)
Olaf D. schrieb:> Hi,>> hier finden sich auch einige Punkte zu Kompilierzeiten und Templates:>> https://stackoverflow.com/questions/26569744/include-guards-why-doesnt-the-c-compiler-automatically-include-each-header-f>> Man sollte bedenken, Templates blähen nicht nur die Kompilierzeiten auf,> sondern auch die Object-Dateien.>> Ich verwende gerne Mehrfachvererbung statt Templates.>> Gruß> Olaf
Ja das Linken dauert auch eine ganze Weile, viel zu viele Symbole wegen
den ganzen Template-Instanzen
Das mit der Mehrfachvererbung musst du genauer erklären
cppbert schrieb:> Olaf D. schrieb>> hier finden sich auch einige Punkte zu Kompilierzeiten und Templates
Explizite Templatespezialisierung wird schon teilweise gemacht
Naja Mehrfachvererbung.
Z. B. kannst du eine Tamplate-Klasse List<X> schreiben.
Der Code wird überall wo du das Template neu verwendest auch neu
erzeugt.
Du kannst aber auch eine Klasse List schreiben und sie als Basisklasse
verwenden:
class ChildWindowList : public ChildWindow, public List
{
}
Der Code von List wird nur einmal in der List.cpp Datei angelegt.
Ist nur ein Beispiel und sicher gibt es auch Nachteile.
Für kleine Controller ist der Platzbedarf aber schon entscheidend.
Aber du entwickelst ja für Windows.
Gruß
Olaf
Olaf D. schrieb:
Das grosse Problem die Implementierung zu verbergen loest das aber doch
auch nicht und ein Pimpl ist mir fuer den ganzen Mathekram der bei dem
Projekt gemacht wird (Hauptgrund fuer die ganzen Templates) zu langsam
> Aber du entwickelst ja für Windows.
Auch wenn ichs unter Linux kompiliere kommt doch bei deinem Beispiel der
selbe Code raus, oder was meinst du mit dem Satz
Das war nicht abwertend gemeint.
Codegröße ist nunmal zweitrangig, wenn man für die Windowsplatform
entwickelt.
Das hat mit dem Crosscompiling wie ich es mache natürlich nichts zu tun.
Gruß
Olaf
Olaf D. schrieb:> Das war nicht abwertend gemeint.
Hab ich so auch nicht verstanden
> Codegröße ist nunmal zweitrangig, wenn man für die Windowsplatform> entwickelt.
Wenn ich die selbe Hardware Platform mit Linux boote und dafür
kompiliere ändert das nicht viel an der Codegrösse - oder meinst du
verglichen mit einer Embedded Platform mit geringen Resourcen und
entsprechendem code?
> Das hat mit dem Crosscompiling wie ich es mache natürlich nichts zu tun.
Wenn ich mit dem Umbau fertig bin werde ich das auch machen ich verstehe
nur nicht was das für eine relevanz hat, der gcc,mingw,clang vollbringen
auch keine kompilierzeitwunder mit so einer Codebasis, egal welche
Platform
> Gruß> Olaf
Moin,
es ging in dem ganzen Thread doch um Kompilierzeiten.
Da kann man an drei Stellen ansetzen:
1. Laufzeitplatform des Compilers: Windows vs. Linux
Windows ist da nun mal grottig. Filezugriffe dauern länger als unter
Linux. Wenn das Buildsystem oft Kommandozeilenprogramme wie den
Compiler,
Linker, usw. aufruft, spielt auch die Performance des
Laufzeit-Linkerss
eine Rolle. Ich weiss jetzt nicht, wie der unter Windows heisst.
Unter Linux ist das "ld.so".
2. Buildsystem: Paralleles kompilieren, Projekt-Struktur
3. Code: Templates, Include-Guards, usw.
Habe das wohl ein wenig vermengt.
Gruß
Olaf
Eine weitere Idee:
Identifizier den Teil des Projektes, der stabil ist und lagere den in
eine statische Library aus. Dann musst Du zumindest den Teil nicht jedes
mal mitkompilieren.
Das setzt natürlich eine einigermaßen modulare Struktur des Programs
vorraus.
Olaf D. schrieb:> Moin,>> es ging in dem ganzen Thread doch um Kompilierzeiten.> Da kann man an drei Stellen ansetzen:>> 1. Laufzeitplatform des Compilers: Windows vs. Linux> Windows ist da nun mal grottig. Filezugriffe dauern länger als unter> Linux. Wenn das Buildsystem oft Kommandozeilenprogramme wie den> Compiler,> Linker, usw. aufruft, spielt auch die Performance des> Laufzeit-Linkerss> eine Rolle. Ich weiss jetzt nicht, wie der unter Windows heisst.> Unter Linux ist das "ld.so".>> 2. Buildsystem: Paralleles kompilieren, Projekt-Struktur>> 3. Code: Templates, Include-Guards, usw.>> Habe das wohl ein wenig vermengt.>> Gruß> Olaf
Punkte 1 und 2 sind mir klar (auch wenn das nicht mehr so drastische
Unterschiede sind wie vor 10 Jahren) - bringt leider nur nichts wenn
alle Entwickler unter Windows arbeiten und da die meisten Kompilierzeit
verbraten wird :)
Punkt 3 ist mir auch klar - ich brauche nur Zeit um das System um zu
stellen
aber mir geht es primär darum wie man die Kompiletime-Teufel findet
und vor einem Umbau genau fest stellen kann was genau böse an den
einzelnen Stellen ist
Ich kompiliere jetzt mit
/Bt+ /d1reportTime
und parse die Informationen raus, mal schauen wie weit mich das bringt
Nils schrieb:> Identifizier den Teil des Projektes, der stabil ist und lagere den in> eine statische Library aus. Dann musst Du zumindest den Teil nicht jedes> mal mitkompilieren.
Das ist meine Strategie
aber erst will ich alle bösen sauber Analysieren - ist ein bisschen viel
Code um einfach so los zu legen - da ich externer bin habe ich nicht
sooo viel Erfahrung mit dem Code
Interessant wäre auch ob ein Unity-Build (sprich ein Build in einer
einzelnen Translation-Unit) schneller wäre. Das ist auch recht flott
getestet weil man sich dafür nur via Script alle .c/.cpp Datein
zusammenfangen muss.
(und dann hoffen dass es in anonymen Namespaces keine Kollisionen
gibt...)
Vincent H. schrieb:> Das ist auch recht flott> getestet weil man sich dafür nur via Script alle .c/.cpp Datein> zusammenfangen muss.
ganz so einfach ist es dann aber doch wieder nicht, da man die
unterschiedlcihen Includepfade aus den c-Dateien untersdhiedlicher
verzeichnisse geradebiegen muss.
Vlad T. schrieb:> Vincent H. schrieb:>> Das ist auch recht flott>> getestet weil man sich dafür nur via Script alle .c/.cpp Datein>> zusammenfangen muss.>> ganz so einfach ist es dann aber doch wieder nicht, da man die> unterschiedlcihen Includepfade aus den c-Dateien untersdhiedlicher> verzeichnisse geradebiegen muss.
Ja stimmt, aber wenn man sich schon mit Python oder ähnlichem durch die
Ordner-Struktur wühlt, dann ist das ersetzen eines relativen durch einen
absoluten Include-Pfad auch kein so großes Thema mehr.
Hier ein Beispiel wie man C++ template Code optimieren kann:
Angenommen man möchte so etwas wie std::integer_sequence_t<N>
definieren, welches zu so etwas wie seq<0,1,2, ..., N-1> aufgelöst wird.
Die naive Variante wäre mit einer Template-Iteration für jeden Schritt;
die clevere arbeitet rekursiv auf Teil-Listen welche zusammen gesetzt
werden, wie hier gezeigt:
https://stackoverflow.com/a/17426611
Mit diesem Code:
1
#include<iostream>
2
#include<cstddef>
3
4
template<unsigned...>structseq{usingtype=seq;};
5
6
7
// Dumme iterative Version
8
9
template<unsignedN,unsignedM,unsigned...I>
10
structgen_seq_slow:gen_seq_slow<N,M+1,I...,M>{};
11
12
template<unsignedN,unsigned...I>
13
structgen_seq_slow<N,N,I...>:seq<I...>{};
14
15
template<unsignedN>
16
usingGenSeqSlow=typenamegen_seq_slow<N,0>::type;
17
18
// Clevere rekursive Version von https://stackoverflow.com/a/17426611
Die Grundlage der Beschleunigung sind vorkompilierte Header. Nur helfen
die nicht automatisch bei Templates. Damit auch Templates vorkompiliert
werden, muss man ihre Verwendungen in einer cpp Datei definieren. Z.B.
mit solchen Zeilen in der cpp.
template CViewMapProcedure<CViewMapMtn>;
template CViewMapProcedure<CViewMapBasics<CViewMapTransformer>>;
Die cpp Datei wird einmalig compiliert, der Compiler erfährt was aus den
Templates kreiert werden soll und macht das einmalig.
hercules schrieb:> Damit auch Templates vorkompiliert> werden, muss man ihre Verwendungen in einer cpp Datei definieren.
Das bringt aber auch nicht immer was. Nimmt man z.B. std::tuple als
Beispiel - das alleine vorzukompilieren bringt nix. Da std::tuple
typischerweise an vielen Stellen mit unterschiedlichen Parametern im
Code benutzt wird, kann man dessen einzelne Instanziierungen auch nicht
wirklich praktikabel vorkompilieren (à la std::tuple<float, char, int>
vorkompilieren, std::tuple<MeinTyp1, MeineKlasse2, float>
vorkompilieren, std::tuple<std::string> vorkompilieren...). Man kann
höchstens eigene Header/Module, welche std::tuple - und vermutlich viele
weitere Templates - nutzen vorkompilieren. Das ist dann auf
Anwendungs-Ebene, nicht auf template-Klassen-Ebene.
Anderes Beispiel: Boost.Spirit an sich kann man auch kaum sinnvoll
vorkompilieren. Die ganze Kompilier-Zeit geht dafür drauf, das für
konkrete Instanziierungen zu übersetzen. Man kann aber eine Verwendung
von Boost.Spirit für eine Grammatik komplett in eine .cpp Datei packen
und dafür ein Nicht-Template-Interface zur Verfügung stellen; dann muss
man bei Grammatik-Änderung nur diese eine Datei (langsam) neu
kompilieren, und das Kompilieren anderer Dateien welche den zugehörigen
Header inkludieren wird nicht ausgebremst.
Programmierer schrieb:> Anderes Beispiel: Boost.Spirit an sich kann man auch kaum sinnvoll> vorkompilieren. Die ganze Kompilier-Zeit geht dafür drauf, das für> konkrete Instanziierungen zu übersetzen. Man kann aber eine Verwendung> von Boost.Spirit für eine Grammatik komplett in eine .cpp Datei packen> und dafür ein Nicht-Template-Interface zur Verfügung stellen;
Das möchte ich für die meisten Sachen umsetzen - viel der
geschwindigkeits-relevanten Sachen bekommen schon einen Datenhaufen zum
crunshen - da bedeutet ein Nicht-Template-Interface kaum Probleme
btw: der Parser ist fertig, jetzt muss ich nur noch eine
Micro-Statemachine bauen und die Daten die aus dem Benchmarking
rauskommen richtig zusammenfassen - ich bin gespannt was da so raus
kommt
cppbert schrieb:> btw: der Parser ist fertig, jetzt muss ich nur noch eine> Micro-Statemachine bauen und die Daten die aus dem Benchmarking> rauskommen richtig zusammenfassen - ich bin gespannt was da so raus> kommt
Wir auch :)
Jan schrieb:> cppbert schrieb:>> btw: der Parser ist fertig, jetzt muss ich nur noch eine>> Micro-Statemachine bauen und die Daten die aus dem Benchmarking>> rauskommen richtig zusammenfassen - ich bin gespannt was da so raus>> kommt>> Wir auch :)
Alles fertig - schöne Daten, nur leider fehlen z.B.
Template-Instanzierungszeiten :(
jetzt probiere ich das Projekt auf clang-cl zu "portieren" damit ich mit
/ftime-trace abrbeiten kann
cppbert schrieb:> Hat hier jemand Erfahrungen mit dem VStudio und> Compiletime-Benchmarking?
Optimierungen ausschalten, dann geht es deutlich schneller (sieh Debug
Compile).
e
MaWin schrieb:> cppbert schrieb:>> Hat hier jemand Erfahrungen mit dem VStudio und>> Compiletime-Benchmarking?>> Optimierungen ausschalten, dann geht es deutlich schneller (sieh Debug> Compile).> e
Dann kannst du nicht mehr arbeiten weil die unoptimierten
Mathe-Templates (erst völlig aufgeblasen und dann nicht wegoptimiert)
viel länger zum rechnen brauchen als menschlich ertragbar :/