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

一仍律、正文

C++代碼一直以其運(yùn)行時的高性能高調(diào)面對世人钧栖, 但是說起編譯速度,卻只有低調(diào)的份了婆翔。比如我現(xiàn)在工作的源代碼拯杠,哪怕使用Incredibuild調(diào)動近百臺機(jī)子,一個完整的build也需要四個小時啃奴,恐怖L杜恪!最蕾!雖然平時開發(fā)一般不需要在本地做完整的build依溯,但編譯幾個相關(guān)的工程就夠你等上好一段時間的了(老外管這個叫monkey around,相當(dāng)形象)瘟则。想想若干年在一臺單核2.8GHZ上工作時的場景 - 面前放本書黎炉,一點(diǎn)build按鈕,就低頭讀一會書~~~往事不堪回首醋拧。
可以想象慷嗜,如果不加以重視,編譯速度極有可能會成為開發(fā)過程中的一個瓶頸丹壕。那么庆械,為什么C++它就編譯的這么慢呢?
我想最重要的一個原因應(yīng)該是C++基本的"頭文件-源文件"的編譯模型:
每個源文件作為一個編譯單元菌赖,可能會包含上百甚至上千個頭文件缭乘,而在每一個編譯單元,這些頭文件都會被從硬盤讀進(jìn)來一遍琉用,然后被解析一遍堕绩。
每個編譯單元都會產(chǎn)生一個obj文件策幼,然后所以這些obj文件會被link到一起,并且這個過程很難并行逛尚。

這里垄惧,問題在于無數(shù)頭文件的重復(fù)load與解析,以及密集的磁盤操作绰寞。
下面從各個角度給出一些加快編譯速度的做法到逊,主要還是針對上面提出的這個關(guān)鍵問題。
代碼角度
在頭文件中使用前置聲明滤钱,而不是直接包含頭文件觉壶。不要以為你只是多加了一個頭文件,由于頭文件的"被包含"特性件缸,這種效果可能會被無限放大铜靶。所以,要盡一切可能使頭文件精簡他炊。很多時候前置申明某個namespace中的類會比較痛苦争剿,而直接include會方便很多,千萬要抵制住這種誘惑痊末;類的成員蚕苇,函數(shù)參數(shù)等也盡量用引用,指針凿叠,為前置聲明創(chuàng)造條件涩笤。
使用Pimpl模式Pimpl全稱為Private Implementation。傳統(tǒng)的C++的類的接口與實(shí)現(xiàn)是混淆在一起的盒件,而Pimpl這種做法使得類的接口與實(shí)現(xiàn)得以完全分離蹬碧。如此,只要類的公共接口保持不變炒刁,對類實(shí)現(xiàn)的修改始終只需編譯該cpp恩沽;同時,該類提供給外界的頭文件也會精簡許多切心。
高度模塊化模塊化就是低耦合飒筑,就是盡可能的減少相互依賴。這里其實(shí)有兩個層面的意思绽昏。一是文件與文件之間协屡,一個頭文件的變化,盡量不要引起其他文件的重新編譯全谤;二是工程與工程之間肤晓,對一個工程的修改,盡量不要引起太多其他工程的編譯。這就要求頭文件补憾,或者工程的內(nèi)容一定要單一漫萄,不要什么東西都往里面塞,從而引起不必要的依賴盈匾。這也可以說是內(nèi)聚性吧腾务。以頭文件為例,不要把兩個不相關(guān)的類削饵,或者沒什么聯(lián)系的宏定義放到一個頭文件里岩瘦。內(nèi)容要盡量單一,從而不會使包含他們的文件包含了不需要的內(nèi)容窿撬。記得我們曾經(jīng)做過這么一個事启昧,把代碼中最"hot"的那些頭文件找出來,然后分成多個獨(dú)立的小文件劈伴,效果相當(dāng)可觀密末。
其實(shí)我們?nèi)ツ曜鲞^的refactoring,把眾多DLL分離成UI與Core兩個部分跛璧,也是有著相同的效果的 - 提高開發(fā)效率严里。

刪除冗余的頭文件一些代碼經(jīng)過上十年的開發(fā)與維護(hù),經(jīng)手的人無數(shù)追城,很有可能出現(xiàn)包含了沒用的頭文件田炭,或重復(fù)包含的現(xiàn)象,去掉這些冗余的include是相當(dāng)必要的漓柑。當(dāng)然,這主要是針對cpp的叨吮,因?yàn)閷τ谝粋€頭文件辆布,其中的某個include是否冗余很難界定,得看是否在最終的編譯單元中用到了茶鉴,而這樣又可能出現(xiàn)在一個編譯單元用到了锋玲,而在另外一個編譯單元中沒用到的情況。之前曾寫過一個Perl腳本用來自動去除這些冗余的頭文件涵叮,在某個工程中竟然去掉多達(dá)了5000多個的include惭蹂。
特別注意inline和template這是C++中兩種比較"先進(jìn)"的機(jī)制,但是它們卻又強(qiáng)制我們在頭文件中包含實(shí)現(xiàn)割粮,這對增加頭文件的內(nèi)容盾碗,從而減慢編譯速度有著很大的貢獻(xiàn)。使用之前舀瓢,權(quán)衡一下廷雅。

綜合技巧
預(yù)編譯頭文件(PCH)把一些常用但不常改動的頭文件放在預(yù)編譯頭文件中。這樣,至少在單個工程中你不需要在每個編譯單元里一遍又一遍的load與解析同一個頭文件了航缀。
Unity BuildUnity Build做法很簡單商架,把所有的cpp包含到一個cpp中(all.cpp) ,然后只編譯all.cpp。這樣我們就只有一個編譯單元芥玉,這意味著不需要重復(fù)load與解析同一個頭文件了蛇摸,同時因?yàn)橹划a(chǎn)生一個obj文件,在鏈接的時候也不需要那么密集的磁盤操作了,估計(jì)能有10x的提高灿巧,看看這個視頻感受一下其做法與速度吧赶袄。
ccachecompiler cache, 通過cache上一次編譯的結(jié)果,使rebuild在保持結(jié)果相同的情況下砸烦,極大的提高速度弃鸦。我們知道如果是build,系統(tǒng)會對比源代碼與目標(biāo)代碼的時間來決定是否要重新編譯某個文件幢痘,這個方法其實(shí)并不完全可靠(比如從svn上拿了上個版本的代碼)唬格,而ccache判斷的原則則是文件的內(nèi)容,相對來講要可靠的多颜说。很可惜的是购岗,Visual Studio現(xiàn)在還不支持這個功能 - 其實(shí)完全可以加一個新的命令,比如cache build门粪,介于build與rebuild之間喊积,這樣,rebuild就可以基本不用了玄妈。
不要有太多的Additional Include Directories編譯器定位你include的頭文件乾吻,是根據(jù)你提供的include directories進(jìn)行搜索的∧怛撸可以想象绎签,如果你提供了100個包含目錄,而某個頭文件是在第100個目錄下酝锅,定位它的過程是非常痛苦的诡必。組織好你的包含目錄,并盡量保持簡潔搔扁。

編譯資源
要提高速度爸舒,要么減少任務(wù),要么加派人手稿蹲,前面兩個方面講得都是減少任務(wù)扭勉,而事實(shí)上,在提高編譯速度這塊苛聘,加派人手還是有著非常重要的作用的剖效。
并行編譯買個4核的嫉入,或者8核的cpu,每次一build璧尸,就是8個文件并行著編咒林,那速度,看著都爽爷光。 要是你們老板不同意垫竞,讓他讀讀這篇文章:Hardware is Cheap, Programmers are Expensive
更好的磁盤我們知道,編譯速度慢很大一部分原因是磁盤操作蛀序,那么除了盡可能的減少磁盤操作欢瞪,我們還可以做的就是加快磁盤速度。比如上面8個核一塊工作的時候徐裸,磁盤極有可能成為最大的瓶頸遣鼓。買個15000轉(zhuǎn)的磁盤,或者SSD重贺,或者RAID0的骑祟,總之,越快越好气笙。
分布式編譯一臺機(jī)子的性能始終是有限的次企,利用網(wǎng)絡(luò)中空閑的cpu資源,以及專門用來編譯的build server來幫助你編譯才能從根本上解決我們編譯速度的問題潜圃,想想原來要build 1個多小時工程的在2分鐘內(nèi)就能搞定缸棵,你就知道你一定不能沒有它 - Incredibuild
并行谭期,其實(shí)還可以這么做堵第。這是一個比較極端的情況,如果你用了Incredibuild隧出,對最終的編譯速度還是不滿意型诚,怎么辦?其實(shí)只要跳出思維的框架鸳劳,編譯速度還是可以有質(zhì)的飛躍的 - 前提是你有足夠多的機(jī)器:假設(shè)你有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,整個編譯速度應(yīng)該能夠控制在1個小時15分鐘之內(nèi)翠储。

另外绘雁,這本書談了很多這方面的內(nèi)容:大規(guī)模C++程序設(shè)計(jì)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末援所,一起剝皮案震驚了整個濱河市庐舟,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌住拭,老刑警劉巖挪略,帶你破解...
    沈念sama閱讀 212,718評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異废酷,居然都是意外死亡瘟檩,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,683評論 3 385
  • 文/潘曉璐 我一進(jìn)店門澈蟆,熙熙樓的掌柜王于貴愁眉苦臉地迎上來墨辛,“玉大人,你說我怎么就攤上這事趴俘《么兀” “怎么了?”我有些...
    開封第一講書人閱讀 158,207評論 0 348
  • 文/不壞的土叔 我叫張陵寥闪,是天一觀的道長太惠。 經(jīng)常有香客問我,道長疲憋,這世上最難降的妖魔是什么凿渊? 我笑而不...
    開封第一講書人閱讀 56,755評論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮缚柳,結(jié)果婚禮上埃脏,老公的妹妹穿的比我還像新娘。我一直安慰自己秋忙,他們只是感情好彩掐,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,862評論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著灰追,像睡著了一般堵幽。 火紅的嫁衣襯著肌膚如雪狗超。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 50,050評論 1 291
  • 那天朴下,我揣著相機(jī)與錄音努咐,去河邊找鬼。 笑死桐猬,一個胖子當(dāng)著我的面吹牛麦撵,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播溃肪,決...
    沈念sama閱讀 39,136評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼免胃,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了惫撰?” 一聲冷哼從身側(cè)響起羔沙,我...
    開封第一講書人閱讀 37,882評論 0 268
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎厨钻,沒想到半個月后扼雏,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,330評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡夯膀,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,651評論 2 327
  • 正文 我和宋清朗相戀三年诗充,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片诱建。...
    茶點(diǎn)故事閱讀 38,789評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡蝴蜓,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出俺猿,到底是詐尸還是另有隱情茎匠,我是刑警寧澤,帶...
    沈念sama閱讀 34,477評論 4 333
  • 正文 年R本政府宣布押袍,位于F島的核電站诵冒,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏谊惭。R本人自食惡果不足惜汽馋,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,135評論 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望圈盔。 院中可真熱鬧豹芯,春花似錦、人聲如沸药磺。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,864評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽癌佩。三九已至木缝,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間围辙,已是汗流浹背我碟。 一陣腳步聲響...
    開封第一講書人閱讀 32,099評論 1 267
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留姚建,地道東北人矫俺。 一個月前我還...
    沈念sama閱讀 46,598評論 2 362
  • 正文 我出身青樓,卻偏偏與公主長得像掸冤,于是被迫代替她去往敵國和親厘托。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,697評論 2 351

推薦閱讀更多精彩內(nèi)容