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.