Mempercepat pembangunan proyek di CMake + GCC: prakompilasi file header

Ada beberapa alasan mengapa proyek C ++ rata-rata berjalan lebih lama dari proyek yang sebanding dalam bahasa lain, seperti Java atau C #. Oleh karena itu, ada beberapa cara untuk mengurangi waktu perakitan. Salah satu yang paling dikenal adalah penggunaan header yang dikompilasi. Hari ini saya akan memberi tahu Anda bagaimana menggunakan metode ini memungkinkan saya untuk secara signifikan mengurangi waktu pembangunan proyek saya.


Sedikit sejarah dan teori


Selama beberapa tahun sekarang saya telah berpartisipasi dalam pengembangan proyek di C ++. Proyek lintas-platform, di CMake, menggunakan GCC sebagai kompiler utama untuk Linux. Saat ini, proyek ini telah berkembang menjadi lebih dari ratusan ribu baris kode, perpustakaan Boost dan beberapa lainnya sedang digunakan secara intensif. Seiring waktu, perakitan proyek mulai mengambil lebih banyak waktu, dan sebagai hasilnya, perakitan lengkap seluruh proyek dari awal pada server integrasi memakan waktu hampir 45 menit.


Saatnya berpikir untuk mengoptimalkan proses pembuatan, dan saya memutuskan untuk mencoba mengacaukan kompilasi awal dari file header. Selain itu, versi CMake 3.16 baru-baru ini dirilis, yang menambahkan dukungan bawaan untuk teknik ini.


Saya tidak akan menjelaskan secara terperinci bagaimana dukungan pra-kompilasi dilaksanakan, karena detail implementasi ini bervariasi di antara para penyusun. Namun secara umum, prakompilasi berfungsi sebagai berikut. File header dibuat (sebut saja precompiled.h), yang termasuk file header untuk kompilasi awal. Ini dihasilkan khusus pch-file berdasarkan file header ini ( .pch, .gch, .pchi- tergantung pada compiler), yang berisi hasil dari header dikompilasi terhubung ke precompiled.h. Selanjutnya, jika kompilator melihat inklusi saat membangun unit berikutnyaprecompiled.h, maka itu tidak membaca dan tidak menganalisis lagi file ini dan semua file header termasuk di dalamnya, melainkan menggunakan hasil kompilasi awal dari file-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 .


Selain header yang dikompilasi, ada cara lain untuk mempercepat build penuh atau parsial. Beberapa dari mereka memerlukan pengeditan dan pengorganisasian file sumber dengan cara tertentu (misalnya, mengurangi koneksi file header yang tidak perlu di header lain dan memindahkannya ke file sumber .cpp). Lainnya menggunakan pendekatan yang tidak memerlukan pengeditan sumber (misalnya, ccache). Ccache, misalnya, diizinkan untuk mengurangi waktu untuk perakitan proyek yang lengkap dari 35 hingga 3 menit, tetapi lebih banyak tentang itu, mungkin lain kali.


Adapun penggunaan kompilasi awal file header, ini adalah cara yang sangat efektif untuk mengurangi waktu pembangunan proyek.


All Articles