加快C++編譯速度的方法
編譯速度慢的原因
因為C++ .h
+ .cpp
的編譯模型
每個cpp檔可能會包含上百甚至上千個.h
檔,這些.h
檔都會被讀進來一遍,然後被解析一遍。
每個編譯單元都會產生一個.obj
文件,然後所以這些.obj
文件會被link到一起,並且這個過程很難平行。重複load與解析,以及密集的IO,使編譯速度很慢。
代碼角度
前置聲明
在.h
檔中使用前置聲明(forward declaration),而不是直接包含.h
檔。
1 | class A; //forward declaration, instead #include "a.h" |
1 |
|
要盡一切可能使.h
檔精簡。
很多時候前置聲明某個namespace中的class會比較痛苦,而直接include會方便很多,千萬要抵制住這種誘惑;class的成員,函數參數等也儘量用reference或pointer。
使用Pimpl模式
Pimpl為Private Implementation
傳統的C++的class的接口與實現是混淆在一起的,而Pimpl這種做法使得class的接口與實現得以完全分離。
如此,只要class的公共接口保持不變,對class實現的修改始終只需編譯該cpp;同時,該class提供給外界的.h檔也會精簡許多。
1 | class A |
高度模塊化
模塊化就是低耦合,就是儘可能的減少相互依賴。
- 文件與文件之間,一個
.h
檔的變化,儘量不要引起其他文件的重新編譯。 - 工程與工程之間,對一個工程的修改,儘量不要引起太多其他工程的編譯。這就要求
.h
檔,或者工程的內容一定要單一,不要什麼東西都往裡面塞,從而引起不必要的依賴。
不要把兩個不相關的class,或者沒什麼聯繫的macro定義放到一個.h
檔裡;內容要儘量單一。
把代碼中最常用到的那些.h
檔找出來,然後分成多個獨立的小文件,效果相當可觀。
刪除冗餘的header檔
一些代碼經過上十年的開發與維護,經手的人無數,很有可能出現包含了沒用的.h
檔,或重複包含的現象,去掉這些冗餘的include是相當必要的。
當然,這主要是針對.cpp
的,因為對於一個.h
檔,其中的某個include是否冗餘很難界定,得看是否在最終的編譯單元中用到了,而這樣又可能出現在一個編譯單元用到了,而在另外一個編譯單元中沒用到的情況。
特別注意inline和template
它們強制在.h
檔中包含實作,這會增加.h
檔的內容,從而減慢許多編譯速度,需權衡使用。
預編譯.h
檔
把一些常用但不常改動的.h
檔放在預編譯.h
檔中。這樣,至少在單個工程中你不需要在每個編譯單元裡一遍又一遍的load與解析同一個.h
檔了。
首次編譯source.cpp時,編譯器生成header.pch的預編譯header。以後再編譯該程式時,編譯器會比較該表頭檔的時間戳,如果表頭檔沒有改變,編譯器直接使用預編譯header。
1 | CORE_PCH_FILENAME=Core.h |
Guard Conditions
保證每個 header file 在每個編譯單元只被 include 一次
1 |
1 |
|
同時使用兩種方法以確保compiler的相容性
Unity Build
把所有的檔案包含到一個cpp中(如all.cpp
),然後只編譯all.cpp。這樣就只有一個編譯單元,這意味著不需要重複load與解析同一個.h
檔了,同時因為只產生一個obj文件,在link的時候也不需要那麼密集的IO
Compiler Cache
藉由快取上一次編譯的結果,使rebuild在保持結果相同的情況下,極大的提高速度。
不要有太多的Include Directories
編譯器定位你include的.h
檔,是根據你提供的include directories進行搜索的。
用 cpp -v
查看 #include "..." search starts here:
中的目錄
和 GNU Make 的 -I
選項
平行化及分佈式編譯
GNU Make 的 -j [N]
可以用N個核心編譯
Visual Studio 有 /MP
選項可做到檔案等級的平行
或是用空閒的機器來編譯
買更好的磁碟
編譯速度慢很大一部分原因是磁碟操作,那麼除了儘可能的減少磁碟操作,我們還可以做的就是加快磁碟速度。
參考資料
- What techniques can be used to speed up C++ compilation times?
- 如何加快C++代碼的編譯速度
- (Unity Build) CppCon 2014: Nicolas Fleury "C++ in Huge AAA Games"
- (Unity Build) Is Ubisoft's unity build for C++ worth?
- (pimpl) C++: 善用 PIMPL 技巧
- (去除重複) Perl腳本,用來自動去除這些冗餘的.h檔
- (預編譯.h檔) 終於搞懂了,預編譯header 檔(precompiled header)
- (分佈式編譯) Xoreax IncrediBuild
- How do I know the default include directories
- (Compiler Cache) ccache
- (預編譯.h檔) Speed up C++ compilation, part 1: precompiled headers
- (預編譯.h檔) makefile 範例