正文

最近北京房?jī)r(jià)蹭蹭猛漲,買了房子的人心花怒放,沒(méi)買的人心驚肉跳,咬牙切齒,樓主作為北漂無(wú)房一族,著實(shí)又亞歷山大了一把,這些天晚上睡覺(jué)總是很難入睡,即使入睡,也是浮夢(mèng)連篇,即使亞歷山大,對(duì)C++的熱情和追求還是不減,應(yīng)該是感動(dòng)了周公吧,夢(mèng)境從此處開始,大師入場(chǎng)來(lái)給我安慰了。。。

11點(diǎn)躺在床上了,腦子里總結(jié)一下最近的工作:最近的開發(fā)用到inline函數(shù)比較多,眾所周知,inline的使用是為了提高程序性能,可結(jié)果卻總不盡如人意,這個(gè)捉急啊,嗯?怎么突然到了山腳下,周邊樹木林立,郁郁蔥蔥,鳥兒委婉啼叫,花兒盛開綻放,好愜意啊,向遠(yuǎn)處望去,青山聳入云霄,山腳下有一石門,突然發(fā)現(xiàn)旁邊坐著一位白衣人,像是在練太極,走近一看,怎么是藍(lán)眼睛,黃頭發(fā),再一定睛,我靠。。這不是傳說(shuō)中的斯考特大師么?我快步向前,用自己蹩腳的英文問(wèn)候了一句:

我:Hello,are you Scott Meyers?

大師:是的,恨高星認(rèn)識(shí)你,我認(rèn)識(shí)你,你是在博客園上又把我的書籍重新翻譯了一遍的那個(gè),你是HarlanC。你是不是有問(wèn)題要問(wèn)我呢?

我:(心理雞凍難耐,斯考特竟然會(huì)中文)Y..yes(不要結(jié)巴了),I have one question…..

大師:你還是用中文吧。

我:好吧,最近使用inline比較多,但效率卻總是不盡人意,您說(shuō)是用inline好呢還是不用好呢。

大師:跟我來(lái)。

打開山門,一個(gè)胖胖的女人站在院子里,她前面有一張桌子,桌子上面放了兩個(gè)盒子,盒子上都寫著字和標(biāo)點(diǎn)符號(hào)。一個(gè)是:巧克力?,一個(gè)是:蔬菜?大師望了我一眼,欲言又止。

我心里突然一亮,馬上回復(fù):大師,您的意思是讓胖女人猜測(cè),猜出哪個(gè)吃那個(gè)?

大師伸出食指,面帶微笑,邊搖邊說(shuō):No.No,No.胖女人代表程序,盒子里的食物代表inline后的函數(shù),你需要自己判斷這個(gè)函數(shù)是“巧克力”還是“蔬菜”,巧克力會(huì)讓胖女人的身材更加臃腫,蔬菜能夠讓胖女人瘦身。

我問(wèn)道:看您在練太極,是不是氣功能看穿盒子,教教我吧。

大師看了看我,扎下了馬步,開始運(yùn)氣了。這是要發(fā)功了吧。我心想。

運(yùn)氣完畢,大師走向盒子,用手抓住它們,用力一撕,盒子打開了,大師回頭望了我一眼,說(shuō)到:

要多動(dòng)手。

 

自己意淫了一把,現(xiàn)在開始進(jìn)入正題:

回到頂部

1. inline函數(shù)的優(yōu)缺點(diǎn)

內(nèi)聯(lián)函數(shù)——一個(gè)多么美妙的想法!它們看上去像函數(shù),行為表現(xiàn)也像函數(shù),它們總是比宏要優(yōu)秀許多(Item 2),你能調(diào)用它們卻沒(méi)有引入函數(shù)調(diào)用的開銷,行為表現(xiàn)如此,夫復(fù)何求?

你實(shí)際上比你想象的要獲取的更多,因?yàn)楸苊夂瘮?shù)調(diào)用的開銷只是這個(gè)故事的一部分。編譯器最優(yōu)化是為了濃縮沒(méi)有函數(shù)調(diào)用的代碼而設(shè)計(jì),所以當(dāng)你inline一個(gè)函數(shù)時(shí),你可能使編譯器在函數(shù)體上執(zhí)行特定場(chǎng)景下的優(yōu)化操作。大多數(shù)編譯器不會(huì)在outlined的函數(shù)調(diào)用上執(zhí)行這樣的優(yōu)化。

然而編程猶如生活,沒(méi)有免費(fèi)的午餐,inline函數(shù)也不例外。Inline函數(shù)的內(nèi)部機(jī)制是用函數(shù)體替換函數(shù)調(diào)用,即使沒(méi)有統(tǒng)計(jì)學(xué)的博士學(xué)位你也能看到這似乎增加了你的目標(biāo)碼的大小。在內(nèi)存有限的機(jī)器上,過(guò)度的inlining會(huì)造成占用空間過(guò)大的問(wèn)題。即使使用虛擬內(nèi)存,inline代碼造成的膨脹也會(huì)導(dǎo)致額外的分頁(yè),指令緩存命中率降低以及隨之而來(lái)的性能損耗。

另一方面,如果inline函數(shù)體非常短小,函數(shù)體本身生成的代碼可能比函數(shù)調(diào)用生成的代碼體積要小。如果是這種情況,inlining函數(shù)使目標(biāo)代碼體積更小以及指令緩存命中率更高!

回到頂部

2. Inline函數(shù)的顯示和隱式實(shí)現(xiàn)方式

需要注意的是inline是對(duì)編譯器的請(qǐng)求而不是命令。請(qǐng)求可以顯示或者隱式的提出來(lái)。隱式的方法通過(guò)在類定義內(nèi)定義一個(gè)函數(shù):

大數(shù)據(jù)培訓(xùn),云培訓(xùn),數(shù)據(jù)挖掘培訓(xùn),云計(jì)算培訓(xùn),高端軟件開發(fā)培訓(xùn),項(xiàng)目經(jīng)理培訓(xùn)

1 class Person {2 public:3 ...4 int age() const { return theAge; } // an implicit inline request: age is5 ... // defined in a class definition6 private:7 int theAge;8 };

大數(shù)據(jù)培訓(xùn),云培訓(xùn),數(shù)據(jù)挖掘培訓(xùn),云計(jì)算培訓(xùn),高端軟件開發(fā)培訓(xùn),項(xiàng)目經(jīng)理培訓(xùn)

 

這樣的函數(shù)通常是成員函數(shù),但是Item 46中解釋道friend函數(shù)也能在類中定義。如果是這樣,它們也會(huì)被隱式聲明成inline。

顯示的聲明一個(gè)inline函數(shù)的方法是在函數(shù)定義之前加上關(guān)鍵字inline。舉個(gè)例子,下面是標(biāo)準(zhǔn)的max模板實(shí)現(xiàn)方式:

1 template<typename T> // an explicit inline2 inline const T& std::max(const T& a, const T& b) // request: std::max is3 { return a < b ? b : a; } // preceded by “inline”

 

回到頂部

3. 函數(shù)模板必須inline么?

max被實(shí)現(xiàn)為模板函數(shù)的事實(shí)讓我們聯(lián)想到inline函數(shù)和模板都是需要被定義在頭文件中的。因此一些程序要就下結(jié)論函數(shù)模板就必須是inline的。這個(gè)結(jié)論既無(wú)效并可能會(huì)有潛在的危害,讓我們分析分析吧。

Inline函數(shù)是必須被定義在頭文件中的,因?yàn)榇蠖鄶?shù)編譯環(huán)境在編譯時(shí)執(zhí)行函數(shù)的內(nèi)聯(lián)。為了將函數(shù)調(diào)用替換為函數(shù)體,編譯器必須了解這個(gè)函數(shù)長(zhǎng)成什么樣子。(一些編譯環(huán)境能夠在鏈接的時(shí)候執(zhí)行內(nèi)聯(lián),甚至有一些能夠在運(yùn)行時(shí)進(jìn)行內(nèi)聯(lián)(如基于.NET CLI的托管環(huán)境),這樣的環(huán)境都是例外,但不是通用規(guī)則。在大多數(shù)C++程序中inline是編譯時(shí)活動(dòng)。)

模板也是被定義在頭文件中的,因?yàn)榫幾g器為了對(duì)其進(jìn)行實(shí)例化時(shí)需要知道這個(gè)模板是什么樣子的。(這種情況也有例外,一些編譯環(huán)境在鏈接期間執(zhí)行模板實(shí)例化。然而編譯時(shí)實(shí)例化是最常見的。)

模板實(shí)例化和inline是相互獨(dú)立的。如果你實(shí)現(xiàn)一個(gè)函數(shù)模板,而需要此模版實(shí)例化的所有函數(shù)都是inline的,那么將其聲明成inline。上面的std::max就是這么實(shí)現(xiàn)的。但如果你將函數(shù)實(shí)現(xiàn)成模板,而此函數(shù)不需要inline,那么避免將模板聲明成inline(無(wú)論是顯示的還是隱式的)。使用inline是有代價(jià)的,不要在沒(méi)有進(jìn)行考慮周詳之前使用inline。我們已經(jīng)提及了inline是如何導(dǎo)致代碼膨脹的(Item 44中為模板作者描述了一個(gè)特別重要的注意點(diǎn)),但也會(huì)有其他的開銷,我們一會(huì)討論。

回到頂部

4. 深入理解inline

在我們進(jìn)行討論之前,先讓我們了解如下事實(shí):inline只是一個(gè)對(duì)編譯器的請(qǐng)求,而編譯器可能會(huì)將其忽略。大多數(shù)編譯器會(huì)拒絕為看上去特別復(fù)雜的函數(shù)進(jìn)行inline(例如,包含循環(huán)或者迭代的函數(shù)),需要調(diào)用虛函數(shù)的函數(shù)也不能進(jìn)行inline,不要感到吃驚。virtual意味著“只有在運(yùn)行時(shí)才能決定調(diào)用哪個(gè)函數(shù),”而inline意味著“執(zhí)行程序之前,在調(diào)用點(diǎn)處用函數(shù)體進(jìn)行替換”。如果編譯器不知道將會(huì)調(diào)用哪個(gè)函數(shù),你就不能因?yàn)榫芙^為函數(shù)體內(nèi)聯(lián)而責(zé)備它。

我們總結(jié)一下:一個(gè)定義成inline的函數(shù)是否真正被inline取決于你所使用的編譯環(huán)境——而這個(gè)編譯環(huán)境主要是只編譯器。幸運(yùn)的是,編譯器會(huì)對(duì)這個(gè)過(guò)程進(jìn)行診斷,如果inline一個(gè)函數(shù)失敗了,它會(huì)發(fā)出一個(gè)警告(Item 53)。

有時(shí)候即使編譯器迫切的希望對(duì)函數(shù)進(jìn)行inline,它們也會(huì)為其生成一個(gè)單獨(dú)的函數(shù)體。例如,如果你的程序需要獲知內(nèi)聯(lián)函數(shù)的地址,編譯器就必須為其生成一個(gè)outline的函數(shù)體。它們不能使用一個(gè)不存在的函數(shù)指針吧?加上如下事實(shí):編譯器使用函數(shù)指針進(jìn)行函數(shù)調(diào)用時(shí)不會(huì)為其進(jìn)行inline,這意味著對(duì)內(nèi)聯(lián)函數(shù)的調(diào)用可能會(huì)被內(nèi)聯(lián)也可能不會(huì),取決于函數(shù)調(diào)用是如何進(jìn)行的:

大數(shù)據(jù)培訓(xùn),云培訓(xùn),數(shù)據(jù)挖掘培訓(xùn),云計(jì)算培訓(xùn),高端軟件開發(fā)培訓(xùn),項(xiàng)目經(jīng)理培訓(xùn)

1 inline void f() {...}       // assume compilers are willing to inline calls to f2 3 void (*pf )() = f;          // pf points to f4 5 ...6 f();                   // this call will be inlined, because it’s a “normal” call7 8 pf(); // this call probably won’t be, because it’s through9 // a function pointer

大數(shù)據(jù)培訓(xùn),云培訓(xùn),數(shù)據(jù)挖掘培訓(xùn),云計(jì)算培訓(xùn),高端軟件開發(fā)培訓(xùn),項(xiàng)目經(jīng)理培訓(xùn)

未被inline的inline函數(shù)會(huì)像幽靈一樣縈繞在你周圍,即使你從未使用函數(shù)指針也是如此,因?yàn)椴⒉皇侵挥谐绦騿T才會(huì)需要函數(shù)指針。有時(shí)候編譯器也會(huì)為構(gòu)造函數(shù)和析構(gòu)函數(shù)生成一份out-of-line函數(shù)體,因?yàn)閷?duì)數(shù)組中的對(duì)象進(jìn)行構(gòu)造和析構(gòu)時(shí)需要使用指向它們的指針。

 

回到頂部

5. 構(gòu)造函數(shù)和析構(gòu)函數(shù)該不該被inline?

事實(shí)上,構(gòu)造函數(shù)和析構(gòu)函數(shù)通常情況下是inline函數(shù)的槽糕候選人,而不像表面看上去那樣,考慮類Derived類的構(gòu)造函數(shù):

大數(shù)據(jù)培訓(xùn),云培訓(xùn),數(shù)據(jù)挖掘培訓(xùn),云計(jì)算培訓(xùn),高端軟件開發(fā)培訓(xùn),項(xiàng)目經(jīng)理培訓(xùn)

 1 class Base { 2 public: 3 ... 4 private: 5  6 std::string bm1, bm2;              // base members 1 and 2 7  8 };                                            
 9 10 class Derived: public Base {    
11 12 public:                                    
13 14 Derived() {}                             // Derived’s ctor is empty — or is it?15 16 ...                                            
17 18 private:                                  
19 20  21 22 std::string dm1, dm2, dm3;    // derived members 1–323 24 };

大數(shù)據(jù)培訓(xùn),云培訓(xùn),數(shù)據(jù)挖掘培訓(xùn),云計(jì)算培訓(xùn),高端軟件開發(fā)培訓(xùn),項(xiàng)目經(jīng)理培訓(xùn)

                      

 這個(gè)構(gòu)造函數(shù)看上去像是inline函數(shù)的杰出候選人,因?yàn)樗话魏未a。但是不要被表面現(xiàn)象蒙蔽。

 

當(dāng)對(duì)象被創(chuàng)建或者析構(gòu)的時(shí)候C++必須保證一些事情的發(fā)生。例如,當(dāng)你使用new的時(shí)候,你的動(dòng)態(tài)創(chuàng)建的對(duì)象由它們的構(gòu)造函數(shù)自動(dòng)初始化;當(dāng)你使用delete時(shí),對(duì)應(yīng)的析構(gòu)函數(shù)要被觸發(fā)。當(dāng)你創(chuàng)建一個(gè)對(duì)象時(shí),對(duì)象的基類部分和它的每個(gè)數(shù)據(jù)成員都會(huì)被自動(dòng)構(gòu)建,當(dāng)對(duì)象被銷毀的時(shí)候相反的過(guò)程也就是自動(dòng)析構(gòu)就會(huì)發(fā)生。如果在構(gòu)造或者析構(gòu)的時(shí)候拋出異常,已經(jīng)被構(gòu)建出來(lái)的對(duì)象的任何部分都應(yīng)該被自動(dòng)釋放。在所有這些場(chǎng)景中,c++指出什么必須發(fā)生,但沒(méi)說(shuō)明如何發(fā)生。這就是編譯器實(shí)現(xiàn)人員要做的了,但是應(yīng)該清楚的是這些事情是不會(huì)自己發(fā)生的。你必須在你的程序中寫一些代碼來(lái)讓這些事情發(fā)生,這些在編譯過(guò)程中一定會(huì)插入到你的代碼的某些地方。有時(shí)候在構(gòu)造函數(shù)和析構(gòu)函數(shù)的結(jié)尾處,所以我們可以想象一個(gè)空的Derived 構(gòu)造函數(shù)實(shí)際上會(huì)是什么樣子的:

大數(shù)據(jù)培訓(xùn),云培訓(xùn),數(shù)據(jù)挖掘培訓(xùn),云計(jì)算培訓(xùn),高端軟件開發(fā)培訓(xùn),項(xiàng)目經(jīng)理培訓(xùn)

 1 Derived::Derived() // conceptual implementation of 2 { // “empty” Derived ctor 3  4 Base::Base();                       // initialize Base part 5  6 try { dm1.std::string::string(); }          // try to construct dm1 7  8  9 catch (...) { // if it throws,10 Base::~Base(); // destroy base class part and11 throw; // propagate the exception12 }13 try { dm2.std::string::string(); } // try to construct dm214 catch(...) { // if it throws,15 dm1.std::string::~string(); // destroy dm1,16 Base::~Base(); // destroy base class part, and17 18 throw;                     // propagate the exception19 20 }                             
21 22 23 try { dm3.std::string::string(); } // construct dm324 catch(...) { // if it throws,25 dm2.std::string::~string(); // destroy dm2,26 dm1.std::string::~string(); // destroy dm1,27 Base::~Base(); // destroy base class part, and28 throw; // propagate the exception29 }30 }

大數(shù)據(jù)培訓(xùn),云培訓(xùn),數(shù)據(jù)挖掘培訓(xùn),云計(jì)算培訓(xùn),高端軟件開發(fā)培訓(xùn),項(xiàng)目經(jīng)理培訓(xùn)

 

這么寫并不代表著編譯器一定會(huì)這么做,因?yàn)榫幾g器處理異常的方式更加復(fù)雜。但是這精確的反映出Derived的空構(gòu)造函數(shù)必須提供什么。不管編譯器對(duì)異常處理的實(shí)現(xiàn)多么復(fù)雜,Derived的構(gòu)造函數(shù)必須為其數(shù)據(jù)成員和基類調(diào)用構(gòu)造函數(shù),這些調(diào)用(可能它們本身是inline的)會(huì)影響inline的吸引力。

 

同樣的原因適用于基類構(gòu)造函數(shù),因此如果它被inline了,它里面的代碼同樣會(huì)被插入到Derived構(gòu)造函數(shù)中(Derived構(gòu)造函數(shù)會(huì)調(diào)用基類構(gòu)造函數(shù)。)。并且如果string構(gòu)造函數(shù)恰恰也被inline了,Derived構(gòu)造函數(shù)會(huì)增加5份函數(shù)代碼的拷貝(對(duì)應(yīng)Derived中的5個(gè)string),現(xiàn)在你應(yīng)該明白了為什么對(duì)Derived構(gòu)造函數(shù)進(jìn)行inline是一個(gè)沒(méi)腦子的決定。同樣的考慮也適用于Derived析構(gòu)函數(shù),我們必須看到被Derived構(gòu)造函數(shù)初始化的對(duì)象被合適的銷毀掉。

 

回到頂部

6. Inline對(duì)客戶造成的影響

 

庫(kù)設(shè)計(jì)者必須估計(jì)將函數(shù)聲明成inline會(huì)造成的影響,因?yàn)樵谝粋€(gè)庫(kù)中為客戶可見的inline函數(shù)提供二進(jìn)制更新(binary upgrade)是不可能的。用其他的話來(lái)說(shuō),如果f是一個(gè)庫(kù)中的inline函數(shù),這個(gè)庫(kù)的客戶將f這個(gè)函數(shù)體編譯進(jìn)了自己的應(yīng)用中。如果庫(kù)實(shí)現(xiàn)者過(guò)后決定修改f,所有使用f的客戶都必須重新編譯。這是不受歡迎的做法。另外一方面,如果f不是inline函數(shù),對(duì)f的修改只需要重新鏈接就可以了。這實(shí)際上比重新編譯減少了負(fù)擔(dān),如果包含這個(gè)函數(shù)的庫(kù)是被動(dòng)態(tài)鏈接的,更新版本會(huì)被客戶不知不覺(jué)的吸收。

 

回到頂部

7. Inline對(duì)調(diào)試器(debugger)產(chǎn)生的影響

 

為了更好的開發(fā)程序,將上面的考慮都記在腦海中,但在編碼過(guò)程中從實(shí)用的角度來(lái)說(shuō),一個(gè)事實(shí)支配了其他所有問(wèn)題:大多數(shù)調(diào)試器不能很好的應(yīng)用在inline函數(shù)上。這應(yīng)該也不是什么出乎意料的事。你如何才能在一個(gè)并沒(méi)有那里的函數(shù)設(shè)定斷點(diǎn)呢?雖然一些編譯環(huán)境支持對(duì)inline函數(shù)的調(diào)試,許多編譯環(huán)境只是在生成調(diào)試版本的時(shí)候禁止inline。

 

回到頂部

8. 總結(jié)

決定哪些函數(shù)應(yīng)該被聲明為inline的哪些不應(yīng)該是一個(gè)邏輯策略問(wèn)題。首先,不要inline任何東西,或者只將inline限定在那些必須被inline的函數(shù)(Item 46)或者很小的函數(shù)上面。通過(guò)謹(jǐn)慎的使用inline,你能很好的使用你的調(diào)試器,但是這樣做你也同樣將inline放在了合適的位置:作為手工優(yōu)化的方法。不要忘記80-20法則,這意味著一個(gè)特定的程序會(huì)用80%的時(shí)間來(lái)執(zhí)行20%的代碼。這是個(gè)重要法則,因?yàn)樗嵝蚜四悖鳛橐粋€(gè)軟件工程師識(shí)別這20%的代碼并進(jìn)行優(yōu)化會(huì)對(duì)程序的性能有整體的提升。你可以對(duì)你的函數(shù)進(jìn)行inline或者去掉inline,直到性能滿足要求,當(dāng)然這需要你在那20%的函數(shù)上努力,否則就是浪費(fèi)精力。

 

需要你記住的: 

  • 將inline限定在最小的,最頻繁調(diào)用的函數(shù)上面。這會(huì)使你的調(diào)試,二進(jìn)制升級(jí)變得容易,并能將潛在的代碼膨脹問(wèn)題最小化,提高程序運(yùn)行速度可能性最大化。

  • 不要僅僅因?yàn)楹瘮?shù)模板出現(xiàn)在頭文件中就將其聲明成內(nèi)聯(lián)函數(shù)。


作者: HarlanC 

博客地址: http://www.cnblogs.com/harlanc/ 
個(gè)人博客: http://www.harlancn.me/ 
本文版權(quán)歸作者和博客園共有,歡迎轉(zhuǎn)載,但未經(jīng)作者同意必須保留此段聲明,且在文章頁(yè)面明顯位置給出, 原文鏈接 . 

http://www.cnblogs.com/harlanc/p/6523201.html