如何加速C++文件的編譯速度?

一、爲什麼慢?

重要的一個原因是C++的基本 頭文件-源文件的編譯模型:

  • 每個源文件爲一個編譯單元
    • 頭文件數量多,可能會包含上百甚至上千個頭文件
    • 存在重複解析,每個編譯單元中,這些頭文件都要從硬盤裏讀取然後被解析
  • 每個編譯單元都會產生一個obj文件
    • 這些obj文件被link到一起,此過程很難做到並行

二、如果加快編譯?

1. 代碼角度

1.1 使用前置聲明

不要直接包含頭文件,推薦使用前置聲明。

前置聲明只是在代碼中引入了類Foo,是一個不完全類型——因爲我們不知道它具體包含了哪些成員。

不完全類型只能在非常有限的情況下使用:

  • 只能定義指向這種不完全類型的指針和引用(因爲不知道多大?)
  • 只能聲明(但不可以定義)以不完全類型作爲參數或者返回類型的函數(也是因爲不知道多大?)

原因:

  • 任何一個多餘的頭文件,都可能會被無限放大。
  • 雖然很多時候前置聲明某個namespace中的類比較痛苦,但你仍值得這麼做
  • 類的成員、函數參數等儘量使用引用、指針,爲前置聲明創造條件

壞處:

  • 隱藏了依賴關係,頭文件改動時,用戶代碼會跳過必要的重編過程
  • 前置聲明可能會被庫的後續更改破壞
  • 前置聲明來自命名空間std::的symbol時,其行爲是未定義的

使用原則:

  • 儘量避免前置聲明那些定義在其他項目中的實體
  • 函數:總是使用#include
  • 類模板:優先使用#include

1.2 使用Pimpl模式

全稱 Private Implemention

原因:

  • 傳統的C++類接口與實現是放在一起的;Pimpl可實現二者完全分離
  • 只要公共接口不變,對類實現的修改,始終只需要編譯此CPP,不涉及頭文件的重編
  • 此外,對外界提供的頭文件內容也會精簡很多

1.3 高度模塊化

儘量低耦合,儘可能減少相互依賴。

原因:

  • 文件與文件之間,一個文件的變化,儘量不要引起其他文件的重編
  • 工程與工程之間,一個工程的修改,儘量不要引起其他工程的編譯
  • 要求頭文件內容儘量單一,保證內聚性
  • 優化思路:可以把代碼中最hot的頭文件找出來,拆分成高內聚的獨立小文件

1.4 刪除冗餘的頭文件

項目大了,又涉及團隊協作,很有必要通過自動化腳本識別和刪除冗餘的頭文件包含

1.5 注意inline和template

這兩種機制都會強制我們在頭文件中包含實現,會增加頭文件的內容。在使用這兩個新特性之前,需要仔細權衡一下。

2. 綜合技巧

2.1 預編頭文件(PCH)

**把一些常用的、但不常改動的頭文件放到預編譯頭文件中。 **

好處:在單個工程中,就不需要在每個編譯單元裏一遍又一遍的load與解析

2. 2 Unity Build

考慮將所有的CPP包含在一個CPP中(如all.cpp),然後只編譯all.cpp

好處:只存在一個編譯單元,不會重複的load與解析,同時只產生一個obj,鏈接時也不需要密集的磁盤操作。

2.3 ccache

全稱 compiler cache,藉助上一次編譯的結果,使rebuild在保持結果相同的情況下,極大地提高速度。

好處:ccache是根據文件內容爲判斷原則,而非更新時間(git clone時不可靠)

2.4 避免過多的Additional Include Directories

編譯器在定位include的頭文件時,是根據你提供的include directories進行搜索。如果提供的目錄過多,則在搜索定位時耗費的時間就會增加。

3. 編譯資源

3.1 並行編譯

通過 make -j4開啓4線程並行編譯。

3.2 升級磁盤

編譯速度比較依賴磁盤的讀寫性能,在多線程並行編譯時,磁盤性能可能存在瓶頸,可以升級更高轉數、或者SSD、或者RAID0

3.3 分佈式編譯

利用多態機器同時編譯,通常在比較大的工程上使用

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章