AccĂ©lĂ©rer la construction d'un projet sur CMake + GCC: prĂ©compilation des fichiers d'en-tĂȘte

Il existe plusieurs raisons pour lesquelles un projet C ++ dure en moyenne plus longtemps que des projets comparables dans d'autres langages, tels que Java ou C #. En consĂ©quence, il existe plusieurs façons de rĂ©duire le temps d'assemblage. L'un des plus connus est l'utilisation d'en-tĂȘtes prĂ©compilĂ©s. Aujourd'hui, je vais vous dire comment l'utilisation de cette mĂ©thode m'a permis de rĂ©duire considĂ©rablement le temps de construction de mon projet.


Un peu d'histoire et de théorie


Depuis plusieurs années, je participe au développement d'un projet en C ++. Le projet multiplateforme, sur CMake, utilise GCC comme compilateur principal pour Linux. Actuellement, le projet a atteint plus de centaines de milliers de lignes de code, la bibliothÚque Boost et certaines autres sont intensivement utilisées. Au fil du temps, l'assemblage du projet a commencé à prendre de plus en plus de temps et, par conséquent, l'assemblage complet de l'ensemble du projet à partir de zéro sur le serveur d'intégration a pris prÚs de 45 minutes.


Il est temps de penser Ă  optimiser le processus de construction, et j'ai dĂ©cidĂ© d'essayer de visser la compilation prĂ©liminaire des fichiers d'en-tĂȘte. De plus, la version CMake 3.16 a rĂ©cemment Ă©tĂ© publiĂ©e, ce qui a ajoutĂ© la prise en charge intĂ©grĂ©e de cette technique.


Je ne dĂ©crirai pas en dĂ©tail comment le support de prĂ©-compilation est implĂ©mentĂ©, car les dĂ©tails de cette implĂ©mentation varient selon les compilateurs. Mais en termes gĂ©nĂ©raux, la prĂ©compilation fonctionne comme suit. Un fichier d'en-tĂȘte est crĂ©Ă© (appelons-le precompiled.h), qui comprend les fichiers d'en-tĂȘte pour la compilation prĂ©liminaire. Il a gĂ©nĂ©rĂ© pch fichier spĂ©cial basĂ© sur ce fichier d' en- tĂȘte ( .pch, .gch, .pchi- en fonction du compilateur), qui contient le rĂ©sultat des en- tĂȘtes prĂ©compilĂ©s connectĂ©s Ă  precompiled.h. De plus, si le compilateur voit l'inclusion lors de la construction de l'unitĂ© suivanteprecompiled.h, il ne lit pas et n'analyse pas Ă  nouveau ce fichier et tous les fichiers d'en-tĂȘte qui y sont inclus, mais utilise Ă  la place le rĂ©sultat de la compilation prĂ©liminaire Ă  partir du fichier pch.


, ( 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 .


En plus de prĂ©compiler les en-tĂȘtes, il existe d'autres moyens d'accĂ©lĂ©rer les builds complets ou partiels. Certains d'entre eux nĂ©cessitent l'Ă©dition et l'organisation des fichiers source d'une certaine maniĂšre (par exemple, en rĂ©duisant la connexion des fichiers d'en-tĂȘte inutiles dans d'autres en-tĂȘtes et en les dĂ©plaçant vers les fichiers source .cpp). D'autres utilisent des approches qui ne nĂ©cessitent pas de modifier la source (par exemple, ccache). Ccache, par exemple, a permis de rĂ©duire le temps pour un assemblage de projet complet de 35 Ă  3 minutes, mais plus Ă  ce sujet, peut-ĂȘtre la prochaine fois.


Quant Ă  l'utilisation de la compilation prĂ©liminaire des fichiers d'en-tĂȘte, c'est un moyen trĂšs efficace de rĂ©duire le temps de construction du projet.


All Articles