Beschleunigung eines auf CMake + GCC basierenden Projekts: Vorkompilierung von Header-Dateien

Es gibt mehrere Gründe, warum ein C ++ - Projekt im Durchschnitt länger dauert als vergleichbare Projekte in anderen Sprachen wie Java oder C #. Dementsprechend gibt es verschiedene Möglichkeiten, die Montagezeit zu verkürzen. Eine der bekanntesten ist die Verwendung vorkompilierter Header. Heute werde ich Ihnen erzählen, wie ich mit dieser Methode die Erstellungszeit meines Projekts erheblich verkürzen konnte.


Ein bisschen Geschichte und Theorie


Seit einigen Jahren bin ich an der Entwicklung eines Projekts in C ++ beteiligt. Das plattformübergreifende Projekt auf CMake verwendet GCC als Hauptcompiler für Linux. Derzeit ist das Projekt auf mehr als hunderttausende Codezeilen angewachsen, die Boost-Bibliothek und einige andere werden intensiv genutzt. Mit der Zeit nahm die Montage des Projekts immer mehr Zeit in Anspruch. Infolgedessen dauerte die vollständige Montage des gesamten Projekts von Grund auf auf dem Integrationsserver fast 45 Minuten.


Es ist Zeit, über die Optimierung des Erstellungsprozesses nachzudenken, und ich habe mich entschlossen, die vorläufige Kompilierung der Header-Dateien zu versuchen. Darüber hinaus wurde kürzlich die CMake 3.16-Version veröffentlicht, die eine integrierte Unterstützung für diese Technik bietet.


Ich werde nicht im Detail beschreiben, wie die Unterstützung vor der Kompilierung implementiert wird, da die Details dieser Implementierung zwischen den Compilern variieren. Im Allgemeinen funktioniert die Vorkompilierung jedoch wie folgt. Es wird eine Header-Datei erstellt (nennen wir es precompiled.h), die die Header-Dateien für die vorläufige Kompilierung enthält. Es erzeugt spezielle pch-Datei auf der Grundlage dieser Header - Datei ( .pch, .gch, .pchi- je nach Compiler), die das Ergebnis der vorkompilierte Header enthält verbunden precompiled.h. Wenn der Compiler die Einbeziehung beim Erstellen der nächsten Einheit siehtprecompiled.hDann werden diese Datei und alle darin enthaltenen Header-Dateien nicht gelesen und analysiert, sondern das Ergebnis der vorläufigen Kompilierung aus der pch-Datei verwendet.


, ( precompiled.h), . . pch- , . -, pch- — . -, pch- , . — . , . , , — .


. , . , . , Visual C++ :


//   
#include "stdafx.h"
#include "internal-header.h"
...

( stdafx.h — precompiled.h) , . . , stdafx.h . . .


Visual C++ :


//   
#include <vector>
#include <map>
#include "stdafx.h" // :    
                    //      
#include "internal-header.h"
...

, -, , — . , , , . stdafx.h #ifdef', .


, , GCC , stdafx.h . , #ifdef' stdafx.h, :


//   
#include "stdafx.h"
#include <vector>
#include <map>
#include "internal-header.h"
...

. , (#ifdef guard'), .


, . , precompiled.h stdafx.h, , (force include) (-include GCC /FI Visual C++). , , . .


CMake. CMake 3.16 target_precompiled_headers(). , (target') CMake-. , , stdafx.h precompiled.h, , pch-. -include /FI .


, target_precompiled_headers(<target1> REUSE FROM <target2>), pch- target1, target2. , , target1 target2 , (preprocessor defines).



. , , . , . CMake , "" :


set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE "${CMAKE_COMMAND} -E time")
set_property(GLOBAL PROPERTY RULE_LAUNCH_LINK "${CMAKE_COMMAND} -E time")

:


[ 60%] Building CXX object source1.cpp.o
Elapsed time: 3 s. (time), 0.002645 s. (clock)
[ 64%] Building CXX object source2.cpp.o
Elapsed time: 4 s. (time), 0.001367 s. (clock)
[ 67%] Linking C executable my_target
Elapsed time: 0 s. (time), 0.000672 s. (clock)

, GCC:


-Winvalid-pch -       gch-
-H -          

CMake :


add_compile_options(-Winvalid-pch)
add_compile_options(-H)

, GCC -ftime-report:


add_compile_options(-ftime-report)

, :


Execution times (seconds)
 phase setup             :   0.01 ( 4%) usr   0.00 ( 0%) sys   0.01 ( 3%) wall    1223 kB ( 8%) ggc
 phase parsing           :   0.21 (81%) usr   0.10 (100%) sys   0.33 (87%) wall   13896 kB (88%) ggc
 phase opt and generate  :   0.03 (12%) usr   0.00 ( 0%) sys   0.03 ( 8%) wall     398 kB ( 3%) ggc
 phase last asm          :   0.01 ( 4%) usr   0.00 ( 0%) sys   0.01 ( 3%) wall     237 kB ( 2%) ggc
 |name lookup            :   0.05 (19%) usr   0.02 (20%) sys   0.03 ( 8%) wall     806 kB ( 5%) ggc
 |overload resolution    :   0.00 ( 0%) usr   0.01 (10%) sys   0.02 ( 5%) wall      68 kB ( 0%) ggc
 dump files              :   0.00 ( 0%) usr   0.00 ( 0%) sys   0.01 ( 3%) wall       0 kB ( 0%) ggc
 preprocessing           :   0.06 (23%) usr   0.04 (40%) sys   0.12 (32%) wall    1326 kB ( 8%) ggc
 parser (global)         :   0.06 (23%) usr   0.02 (20%) sys   0.11 (29%) wall    6783 kB (43%) ggc
 ...
 TOTAL                 :   0.26             0.10             0.38              15783 kB

- , Python, -, .


, , ( ):


PHASES SUMMARY
   phase opt and generate                   : 1309.1 s. = 21.8 m. ( 50 %)  --->  1577.5 s. = 26.3 m. ( 74 %)
   deferred                                 :  135.0 s. =  2.3 m. (  5 %)  --->   221.4 s. =  3.7 m. ( 10 %)
   integration                              :   62.2 s. =  1.0 m. (  2 %)  --->    85.1 s. =  1.4 m. (  4 %)
   template instantiation                   :  224.3 s. =  3.7 m. (  9 %)  --->   246.5 s. =  4.1 m. ( 12 %)
   callgraph optimization                   :   32.9 s. =  0.5 m. (  1 %)  --->    48.5 s. =  0.8 m. (  2 %)
   unaccounted todo                         :   36.5 s. =  0.6 m. (  1 %)  --->    49.7 s. =  0.8 m. (  2 %)
   |overload resolution                     :   82.1 s. =  1.4 m. (  3 %)  --->    95.2 s. =  1.6 m. (  4 %)
                                                        ...
   parser enumerator list                   :    2.1 s. =  0.0 m. (  0 %)  --->     0.5 s. =  0.0 m. (  0 %)
   parser function body                     :   32.0 s. =  0.5 m. (  1 %)  --->     9.3 s. =  0.2 m. (  0 %)
   garbage collection                       :   55.3 s. =  0.9 m. (  2 %)  --->    16.7 s. =  0.3 m. (  1 %)
   |name lookup                             :  132.8 s. =  2.2 m. (  5 %)  --->    63.5 s. =  1.1 m. (  3 %)
   body                                     :   87.5 s. =  1.5 m. (  3 %)  --->    18.2 s. =  0.3 m. (  1 %)
   parser struct body                       :  113.4 s. =  1.9 m. (  4 %)  --->    21.1 s. =  0.4 m. (  1 %)
   parser (global)                          :  158.0 s. =  2.6 m. (  6 %)  --->    25.8 s. =  0.4 m. (  1 %)
   preprocessing                            :  548.1 s. =  9.1 m. ( 21 %)  --->    88.0 s. =  1.5 m. (  4 %)
   phase parsing                            : 1119.7 s. = 18.7 m. ( 43 %)  --->   228.3 s. =  3.8 m. ( 11 %)
  TOTAL : 2619.2 s. = 43.7 m.  --->  2118.4 s. = 35.3 m.

, (parsing, preprocessing). , . , , . , , .


. . Boost . , . , , Boost. . , Boost. — , Boost, , .


pch- , target_precompiled_headers(<target1> REUSE FROM <target2>). .



, , 43 35 .


Neben dem Vorkompilieren von Headern gibt es andere Möglichkeiten, um vollständige oder teilweise Builds zu beschleunigen. Einige von ihnen erfordern das Bearbeiten und Organisieren der Quelldateien auf eine bestimmte Weise (z. B. Reduzieren der Verbindung unnötiger Header-Dateien in anderen Headern und Verschieben dieser in die Quelldateien .cpp). Andere verwenden Ansätze, bei denen die Quelle nicht bearbeitet werden muss (z. B. ccache). Mit Ccache konnte beispielsweise die Zeit für eine vollständige Projektassemblierung von 35 auf 3 Minuten reduziert werden, aber mehr dazu, vielleicht beim nächsten Mal.


Die vorläufige Kompilierung von Header-Dateien ist eine sehr effektive Methode, um die Erstellungszeit des Projekts zu verkürzen.


All Articles