分析C++工程編譯時(shí)間過長的原因
【引言】
大家好,最近接到一個(gè)任務(wù),是分析一下為何一些C++工程編譯時(shí)間過長,是否有好的方案來改善一下編譯的速度。本文我們來探討一下可能影響到C++編譯時(shí)間的一些因素。
【頭文件相關(guān)問題】
【沒有把標(biāo)準(zhǔn)庫頭文件放在預(yù)編譯頭文件中】
預(yù)編譯頭文件中應(yīng)該放入很少改動(dòng)的那些頭文件,比如系統(tǒng)級(jí)的頭文件,標(biāo)準(zhǔn)庫的頭文件,從而節(jié)省反復(fù)編譯造成的時(shí)間開銷。
不要把已有頭文件放入預(yù)編譯頭文件中,由于私有頭文件會(huì)經(jīng)常改動(dòng),每次改動(dòng)都會(huì)導(dǎo)致整個(gè)預(yù)編譯頭文件編譯一次,會(huì)消耗很多時(shí)間。
【沒有把標(biāo)準(zhǔn)庫頭文件放在預(yù)編譯頭文件中的問題修復(fù)】
把不常改動(dòng)的標(biāo)準(zhǔn)庫頭文件和系統(tǒng)級(jí)頭文件放入預(yù)編譯頭文件中。
【頭文件在一個(gè)編譯單元中的多次引用編譯】
如果沒有預(yù)編譯頭判斷檢查守衛(wèi)的話,一個(gè)頭文件被引入幾次就會(huì)被編譯幾次,會(huì)消耗不必要的時(shí)間。
【頭文件在一個(gè)編譯單元中的編譯問題的修復(fù)】
在定義頭文件時(shí)使用如下格式:
#pragma?once
#ifndef?filename_h
#define?filename_h
//?Header?declarations?/?definitions
#endif
如上的示例是預(yù)編譯頭的條件判斷,用這樣的判斷可以避免一個(gè)頭文件在一個(gè)編譯單元中被編譯多次。
【引入了不需要的頭文件】
有時(shí)候在寫程序的時(shí)候,由于種種原因工程師引入了一些不需要的頭文件。這些引入的頭文件會(huì)消耗編譯的時(shí)間。
【引入了不需要的頭文件問題的修復(fù)】
刪除此種情況下引入的不必要頭文件。
【頭文件的重復(fù)多次編譯問題】
一個(gè)工程中一般存在很多個(gè)編譯單元,一個(gè)編譯單元對(duì)應(yīng)一個(gè)編譯輸出如obj, so文件,每個(gè)編譯單元會(huì)包含成百上千個(gè)頭文件,這些頭文件在編譯對(duì)應(yīng)編譯單元時(shí)會(huì)被加載并編譯。
一個(gè)很普遍的情況是一個(gè)工程中的頭文件會(huì)被多個(gè)編譯單元引用,這樣在編譯的時(shí)候,這些頭文件會(huì)被編譯多次。這些時(shí)間開銷累加起來是非常可觀的。
【頭文件的重復(fù)多次編譯問題修復(fù)】
要修復(fù)頭文件重復(fù)多次編譯的問題,我們需要對(duì)編譯單元進(jìn)行整合,盡可能地減少多個(gè)編譯單元對(duì)于某個(gè)頭文件的依賴情況。
【頭文件中使用using namespace的編譯問題】
這種情況可能會(huì)引起名字沖突的編譯錯(cuò)誤。所以要避免這種情況。
【頭文件中使用using namespace的編譯問題的修復(fù)】
可以這樣修復(fù):
class?MyClass
{
private:
NameSpace1::Class1?_member;
}
或者
class?MyClass
{
namespace?n=NameSpace1;
private:
n::Class1?_member;
}
【間接引用頭文件的編譯問題】
下面的例子是一個(gè)間接引用頭文件的例子。在ClassB的頭文件中引入ClassA的頭文件以后,編譯器會(huì)打開ClassA的頭文件進(jìn)行編譯。這個(gè)消耗實(shí)際上我們是可以避免的。
#include?"classa.h"
class?ClassB{
ClassA?*m_pClassA;
};
在上面的代碼中,我們引入了classa的頭文件;
【間接引用頭文件問題修復(fù)】
class?ClassA;
class?ClassB{
ClassA?*m_pClassA;
};
在上面的代碼中,我們通過前向聲明ClassA的方式避免引入classa的頭文件。
通過以上的改動(dòng)可以提高編譯的速度。
具體到數(shù)量級(jí)要根據(jù)具體項(xiàng)目來定,這里有個(gè)粗略的估算,如果你的頭文件比較龐大,而又在一個(gè)項(xiàng)目中被多個(gè)工程所引用,通過這個(gè)方法有可能幫助你減少幾個(gè)小時(shí)的編譯時(shí)間。
【一個(gè)頭文件種包含了多個(gè)不相關(guān)的功能組定義】
如果在一個(gè)頭文件中包含了多個(gè)不相關(guān)的功能組,這個(gè)頭文件必然會(huì)被不同的模塊功能組引用,這可能會(huì)導(dǎo)致這個(gè)頭文件被多次編譯。從而導(dǎo)致了編譯時(shí)間的增長。
【一個(gè)頭文件種包含了多個(gè)不相關(guān)的功能組定義問題修復(fù)】
修復(fù)這個(gè)問題的方法是把多個(gè)功能組從這一個(gè)頭文件中分離出來,創(chuàng)建對(duì)應(yīng)的功能單一的頭文件。然后在對(duì)應(yīng)的功能組中只引用對(duì)應(yīng)的那個(gè)頭文件。
【其他不好的頭文件使用習(xí)慣】
【頭文件中引入cpp文件】
在公共頭文件中引入源文件不是一個(gè)好習(xí)慣。有時(shí)候程序員希望在多個(gè)源文件中使用一些共享代碼,于是通過引入一個(gè)源文件的方式來做。
如果屬于類似情況的話,可以把這些共享代碼放在內(nèi)部的頭文件中。
【在公共頭文件中放置了過多的信息導(dǎo)致信息泄露】
在開發(fā)共享庫的時(shí)候,我們會(huì)把暴露出的頭文件如何調(diào)用的信息說清楚就行了,有時(shí)候畫蛇添足的說明這個(gè)功能是如何開發(fā)實(shí)現(xiàn)的。
刪除與公共頭文件調(diào)用無關(guān)的信息。
【頭文件不能自編譯】
把頭文件引入一個(gè)空的源文件進(jìn)行編譯測(cè)試,如果存在編譯錯(cuò)誤,說明該頭文件無法自編譯。
頭文件無法自編譯會(huì)在工程代碼龐大起來以后發(fā)生一些隨機(jī)的誤導(dǎo)性的編譯錯(cuò)誤,使得問題排查工作變得艱難。
需要通過自下而上的方法修復(fù)頭文件不能自編譯的問題。
【模板相關(guān)的問題】
【模板的使用會(huì)增加編譯時(shí)間】
【單層模板】
在C++中使用模板時(shí),編譯器會(huì)把每處使用模板的地方展開再編譯,這個(gè)展開的過程自然會(huì)增加時(shí)間的開銷。
【嵌套模板】
template
int?add(T?t,?ArgTypes...?args)
{
return?t?+?add(args...);
}
更為可怕的情況是當(dāng)在代碼中使用嵌套模板的時(shí)候,也就是模板中再使用另一層模板或者更多層模板,編譯器需要首先解釋并展開這些模板以及嵌套的模板,這個(gè)時(shí)候編譯器解釋和展開的時(shí)間消耗會(huì)是驚人的。
【多個(gè)模板參數(shù)】
template?
struct?foo?{?};
在上例中,如果T有8種可能,U有8種可能,編譯器會(huì)展開成64種實(shí)現(xiàn)。這會(huì)極大的增加編譯時(shí)間的開銷。
【模板會(huì)導(dǎo)致超復(fù)雜的變量類型和超長的變量函數(shù)名】
由于編譯器在解釋展開模板時(shí)是通過程序來做的,編譯程序可能會(huì)生成極其復(fù)雜的變量類型以及超級(jí)長度的變量名和函數(shù)名。這些程序生成的代碼會(huì)額外的增加編譯時(shí)間和鏈接時(shí)間。
【模板編譯問題的修復(fù)】
盡量不使用嵌套模板。
盡量使用一個(gè)模板參數(shù)。
【過長的命名】
com.sun.java.swing.plaf.nimbus.InternalFrameInternalFrameTitlePaneInternalFrameTitlePaneMaximizeButtonWindowNotFocusedState
上例是Java類的一個(gè)命名例子。
有時(shí)候程序員會(huì)選擇使用過長的命名,比如類名,函數(shù)名,變量名,文件名等等,這些也會(huì)導(dǎo)致編譯時(shí)間和鏈接時(shí)間的增長。
【過長命名問題的修復(fù)】
盡量使用短而又可理解的命名。
【過多的編譯單元】
過多的編譯單元無疑會(huì)增加編譯和鏈接的時(shí)間。
【過多的編譯單元問題修復(fù)】
可以通過腳本在編譯前把源文件合并。比如sqlite的編譯過程,?這樣可以節(jié)省編譯和鏈接的時(shí)間。
https://sqlite.org/src/doc/trunk/README.md
For example:
tar xzf sqlite.tar.gz??? ;#? Unpack the source tree into "sqlite"
mkdir bld??????????????? ;#? Build will occur in a sibling directory
cd bld?????????????????? ;#? Change to the build directory
../sqlite/configure????? ;#? Run the configure script
make???????????????????? ;#? Run the makefile.
make sqlite3.c?????????? ;#? Build the "amalgamation" source file
make test??????????????? ;#? Run some tests (requires Tcl)
以上展示的是sqlite的編譯過程。其中高亮部分就是編譯一個(gè)合并后的源文件。
【使用并行編譯機(jī)制】
很多編譯器和開發(fā)工具支持多內(nèi)核多CPU條件下的并行編譯。比如gnu make下使用-j[N]選項(xiàng): make –j 4,?同時(shí)啟動(dòng)4個(gè)任務(wù)。
Visual Studio下使用并行模式編譯。
在可以充分使用多核多CPU的情況下,并行編譯可以提高編譯速度。
【編譯優(yōu)化機(jī)制最小化】
總的規(guī)律是使用的優(yōu)化選項(xiàng)越多,編譯器做的事情就越多,花的時(shí)間就越長。
【共有代碼的共享最大化】
把不經(jīng)常更改的代碼放在庫里面,通過預(yù)編譯設(shè)置,可以提高編譯速度,同時(shí)把大量的共有代碼放在一個(gè)so或者dll?里面又可以提高鏈接速度。
【提高電腦配置】
通過使用更高的CPU,更多的內(nèi)存,以及固化硬盤等措施提高編譯電腦的性能來提高編譯速度。
【提高網(wǎng)絡(luò)速度】
如果在編譯過程中需要訪問網(wǎng)絡(luò),那么通過提高網(wǎng)速的方法也可以提高編譯的速度。
【小結(jié)】
以上內(nèi)容對(duì)于C++工程編譯過程中可能存在的問題,尤其是影響編譯速度的問題進(jìn)行了探索。?如果您所在的團(tuán)隊(duì)正在進(jìn)行相關(guān)的優(yōu)化,不妨做個(gè)參考,希望本文能有所幫助。歡迎大家評(píng)論,留言,批評(píng)和指正。
C++
版權(quán)聲明:本文內(nèi)容由網(wǎng)絡(luò)用戶投稿,版權(quán)歸原作者所有,本站不擁有其著作權(quán),亦不承擔(dān)相應(yīng)法律責(zé)任。如果您發(fā)現(xiàn)本站中有涉嫌抄襲或描述失實(shí)的內(nèi)容,請(qǐng)聯(lián)系我們jiasou666@gmail.com 處理,核實(shí)后本網(wǎng)站將在24小時(shí)內(nèi)刪除侵權(quán)內(nèi)容。