تسريع بناء المشروع على CMake + GCC: تجميع مسبق لملفات الرأس

هناك العديد من الأسباب التي تجعل مشروع C ++ في المتوسط ​​أطول من المشاريع المماثلة بلغات أخرى ، مثل Java أو C #. وفقًا لذلك ، هناك عدة طرق لتقليل وقت التجميع. أحد أشهرها هو استخدام الرؤوس المترجمة مسبقًا. اليوم سأخبرك كيف سمح لي استخدام هذه الطريقة بتقليل وقت بناء مشروعي بشكل ملحوظ.


القليل من التاريخ والنظرية


منذ سنوات عديدة وأنا أشارك في تطوير مشروع في C ++. يستخدم المشروع متعدد المنصات ، على CMake ، دول مجلس التعاون الخليجي كمترجم رئيسي لنظام لينكس. في الوقت الحالي ، نما المشروع إلى أكثر من مئات الآلاف من أسطر التعليمات البرمجية ومكتبة Boost وبعضها الآخر يتم استخدامه بشكل مكثف. بمرور الوقت ، بدأ تجميع المشروع يأخذ المزيد والمزيد من الوقت ، ونتيجة لذلك ، استغرق التجميع الكامل للمشروع بأكمله من البداية على خادم التكامل حوالي 45 دقيقة.


حان الوقت للتفكير في تحسين عملية البناء ، وقررت أن أحاول تثبيت التجميع الأولي لملفات الرأس. علاوة على ذلك ، تم إصدار إصدار CMake 3.16 مؤخرًا ، مما أضاف دعمًا مدمجًا لهذه التقنية.


لن أصف بالتفصيل كيفية تنفيذ دعم التجميع المسبق ، حيث تختلف تفاصيل هذا التنفيذ بين المجمعين. ولكن بعبارات عامة ، تعمل الترجمة المسبقة على النحو التالي. يتم إنشاء ملف رأس (دعونا نطلق عليه precompiled.h) ، والذي يتضمن ملفات الرأس لتجميع أولي. لقد أنتج ملف pch خاصًا يعتمد على ملف الرأس هذا ( .pch، .gch، .pchi- اعتمادًا على المحول البرمجي) ، والذي يحتوي على نتيجة رؤوس مترجمة مسبقًا متصلة بـ precompiled.h. علاوة على ذلك ، إذا رأى المترجم التضمين عند بناء الوحدة التاليةprecompiled.h، ثم لا يقرأ ويحلل هذا الملف وجميع ملفات الرأس المدرجة فيه ، ولكنه يستخدم بدلاً من ذلك نتيجة التجميع الأولي من ملف pch.


المعايير التي يتم من خلالها تضمين ملف رأس معين في المرشحين لتجميع أولي (يقع فيprecompiled.h)، بعض. بادئ ذي بدء ، نادرًا ما يتم تغيير هذه الملفات نسبيًا. خلاف ذلك ، سيتم إعادة إنشاء ملف pch في كثير من الأحيان ، الأمر الذي يمكن أن يبطل جميع مزايا استخدام الترجمة المسبقة. أولاً ، يعد إنشاء ملف pch عملية طويلة نسبيًا في حد ذاتها. وثانيًا ، بعد تغيير ملف pch ، ستحتاج إلى إعادة بناء جميع الملفات التي تعتمد عليه. معيار آخر هو تكرار استخدام ملف الرأس في ملفات المصدر. كلما زاد عدد الوحدات اعتمادًا على ملف الرأس ، زاد معنى ترجمة ملف الرأس مسبقًا. وبالمثل ، كلما زاد حجم ملف الرأس نفسه ، أو كلما زاد عدد ملفات الرأس الأخرى التي يتضمنها ، كلما كان الربح عادةً أكبر من الترجمة المسبقة.


. , . , . , Visual C++ :


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

( stdafx.hprecompiled.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 .


بالإضافة إلى الرؤوس المسبقة ، هناك طرق أخرى لتسريع الإنشاءات الكاملة أو الجزئية. يتطلب بعضها تحرير وتنظيم الملفات المصدر بطريقة معينة (على سبيل المثال ، تقليل اتصال ملفات العناوين غير الضرورية في رؤوس أخرى ونقلها إلى ملفات المصدر .cpp). يستخدم البعض الآخر أساليب لا تتطلب تحرير المصدر (على سبيل المثال ، ccache). سمح Ccache ، على سبيل المثال ، بتقليل الوقت لتجميع مشروع كامل من 35 إلى 3 دقائق ، ولكن المزيد عن ذلك ، ربما في المرة القادمة.


أما بالنسبة لاستخدام التجميع الأولي لملفات العناوين ، فهذه طريقة فعالة للغاية لتقليل وقت بناء المشروع.


All Articles