如何加快C++代碼的編譯速度 (轉)

C++代碼一直以其運行時的高性能高調面對世人, 可是提及編譯速度,卻只有低調的份了。好比我如今工做的源代碼,哪怕使用Incredibuild調動近百臺機子,一個完整的build也須要四個小時,恐怖!!!雖然平時開發通常不須要在本地作完整的build,但編譯幾個相關的工程就夠你等上好一段時間的了(老外管這個叫monkey around,至關形象)。想一想若干年在一臺單核2.8GHZ上工做時的場景 - 面前放本書,一點build按鈕,就低頭讀一會書~~~往事不堪回首。html

能夠想象,若是不加以重視,編譯速度極有可能會成爲開發過程當中的一個瓶頸。那麼,爲何C++它就編譯的這麼慢呢?apache

我想最重要的一個緣由應該是C++基本的"頭文件-源文件"的編譯模型:網絡

  1. 每一個源文件做爲一個編譯單元,可能會包含上百甚至上千個頭文件,而在每個編譯單元,這些頭文件都會被從硬盤讀進來一遍,而後被解析一遍。
  2. 每一個編譯單元都會產生一個obj文件,而後因此這些obj文件會被link到一塊兒,而且這個過程很難並行。

這裏,問題在於無數頭文件的重複load與解析,以及密集的磁盤操做。框架

下面從各個角度給出一些加快編譯速度的作法,主要仍是針對上面提出的這個關鍵問題。分佈式

1、代碼角度模塊化

  • 在頭文件中使用前置聲明,而不是直接包含頭文件。
    不要覺得你只是多加了一個頭文件,因爲頭文件的"被包含"特性,這種效果可能會被無限放大。因此,要盡一切可能使頭文件精簡。不少時候前置申明某個namespace中的類會比較痛苦,而直接include會方便不少,千萬要抵制住這種誘惑;類的成員,函數參數等也儘可能用引用,指針,爲前置聲明創造條件。
  • 使用Pimpl模式
    Pimpl全稱爲Private Implementation。傳統的C++的類的接口與實現是混淆在一塊兒的,而Pimpl這種作法使得類的接口與實現得以徹底分離。如此,只要類的公共接口保持不變,對類實現的修改始終只需編譯該cpp;同時,該類提供給外界的頭文件也會精簡許多。
  • 高度模塊化
    模塊化就是低耦合,就是儘量的減小相互依賴。這裏其實有兩個層面的意思。一是文件與文件之間,一個頭文件的變化,儘可能不要引發其餘文件的從新編譯;二是工程與工程之間,對一個工程的修改,儘可能不要引發太多其餘工程的編譯。這就要求頭文件,或者工程的內容必定要單一,不要什麼東西都往裏面塞,從而引發沒必要要的依賴。這也能夠說是內聚性吧。

    以頭文件爲例,不要把兩個不相關的類,或者沒什麼聯繫的宏定義放到一個頭文件裏。內容要儘可能單一,從而不會使包含他們的文件包含了不須要的內容。記得咱們曾經作過這麼一個事,把代碼中最"hot"的那些頭文件找出來,而後分紅多個獨立的小文件,效果至關可觀。svn

    其實咱們去年作過的refactoring,把衆多DLL分離成UI與Core兩個部分,也是有着相同的效果的 - 提升開發效率。函數

  • 刪除冗餘的頭文件
    一些代碼通過上十年的開發與維護,經手的人無數,頗有可能出現包含了沒用的頭文件,或重複包含的現象,去掉這些冗餘的include是至關必要的。固然,這主要是針對cpp的,由於對於一個頭文件,其中的某個include是否冗餘很難界定,得看是否在最終的編譯單元中用到了,而這樣又可能出如今一個編譯單元用到了,而在另一個編譯單元中沒用到的狀況。
    以前曾寫過一個Perl腳本用來自動去除這些冗餘的頭文件,在某個工程中居然去掉多達了5000多個的include。
  • 特別注意inline和template
    這是C++中兩種比較"先進"的機制,可是它們卻又強制咱們在頭文件中包含實現,這對增長頭文件的內容,從而減慢編譯速度有着很大的貢獻。使用以前,權衡一下。

2、綜合技巧 性能

  • 預編譯頭文件(PCH)
    把一些經常使用但不常改動的頭文件放在預編譯頭文件中。這樣,至少在單個工程中你不須要在每一個編譯單元裏一遍又一遍的load與解析同一個頭文件了。
  • Unity Build
    Unity Build作法很簡單,把全部的cpp包含到一個cpp中(all.cpp) ,而後只編譯all.cpp。這樣咱們就只有一個編譯單元,這意味着不須要重複load與解析同一個頭文件了,同時由於只產生一個obj文件,在連接的時候也不須要那麼密集的磁盤操做了,估計能有10x的提升,看看這個視頻感覺一下其作法與速度吧。
  • ccache
    compiler cache, 經過cache上一次編譯的結果,使rebuild在保持結果相同的狀況下,極大的提升速度。咱們知道若是是build,系統會對比源代碼與目標代碼的時間來決定是否要從新編譯某個文件,這個方法其實並不徹底可靠(好比從svn上拿了上個版本的代碼),而ccache判斷的原則則是文件的內容,相對來說要可靠的多。很惋惜的是,Visual Studio如今還不支持這個功能 - 其實徹底能夠加一個新的命令,好比cache build,介於build與rebuild之間,這樣,rebuild就能夠基本不用了。
  • 不要有太多的Additional Include Directories
    編譯器定位你include的頭文件,是根據你提供的include directories進行搜索的。能夠想象,若是你提供了100個包含目錄,而某個頭文件是在第100個目錄下,定位它的過程是很是痛苦的。組織好你的包含目錄,並儘可能保持簡潔。

3、編譯資源 ui

要提升速度,要麼減小任務,要麼加派人手,前面兩個方面講得都是減小任務,而事實上,在提升編譯速度這塊,加派人手仍是有着很是重要的做用的。

  • 並行編譯
    買個4核的,或者8核的cpu,每次一build,就是8個文件並行着編,那速度,看着都爽。 要是大家老闆不一樣意,讓他讀讀這篇文章:Hardware is Cheap, Programmers are Expensive
  • 更好的磁盤
    咱們知道,編譯速度慢很大一部分緣由是磁盤操做,那麼除了儘量的減小磁盤操做,咱們還能夠作的就是加快磁盤速度。好比上面8個核一塊工做的時候,磁盤極有可能成爲最大的瓶頸。買個15000轉的磁盤,或者SSD,或者RAID0的,總之,越快越好。
  • 分佈式編譯
    一臺機子的性能始終是有限的,利用網絡中空閒的cpu資源,以及專門用來編譯的build server來幫助你編譯才能從根本上解決咱們編譯速度的問題,想一想原來要build 1個多小時工程的在2分鐘內就能搞定,你就知道你必定不能沒有它 - Incredibuild
  • 並行,其實還能夠這麼作。
    這是一個比較極端的狀況,若是你用了Incredibuild,對最終的編譯速度仍是不滿意,怎麼辦?其實只要跳出思惟的框架,編譯速度仍是能夠有質的飛躍的 - 前提是你有足夠多的機器:

    假設你有solution A和solution B,B依賴於A,因此必須在A以後Build B。其中A,B Build各須要1個小時,那麼總共要2個小時。但是B必定要在A以後build嗎?跳出這個思惟框架,你就有了下述方案:

    • 同時開始build A和B 。
    • A的build成功,這裏雖然B的build失敗了,但都只是失敗在最後的link上。
    • 從新link B中的project。

    這樣,經過讓A的build與B的編譯並行,最後link一下B中的project,整個編譯速度應該可以控制在1個小時15分鐘以內。

另外,這本書談了不少這方面的內容:大規模C++程序設計

相關文章
相關標籤/搜索