天天看點

[翻譯] Effective C++, 3rd Edition, Item 30: 了解 inlining(内聯化)的來龍去脈(上)

Item 30: 了解 inlining(内聯化)的來龍去脈

作者:Scott Meyers

譯者:fatalerror99 (iTePub's Nirvana)

釋出:http://blog.csdn.net/fatalerror99/

inline functions ——多麼棒的主意啊!它們看起來像 functions,它們産生的效果也像 functions,它們在各方面都比 macros(宏)好得太多太多(參見 Item 2),而你卻可以在調用它們時不招緻函數調用的成本。你還有什麼更多的要求呢?

實際上你得到的可能比你想到的更多,因為避免函數調用的成本隻是故事的一部分。編譯器的優化一般說來是為了一段連續的沒有函數調用的代碼設計的,是以當你 inline 一個函數的時候,你就使得編譯器對函數體實行上下文相關的專有優化成為可能。大多數編譯器都不會對 "outlined" 函數調用實行這樣的優化。

然而,在程式設計中,就像在生活中,沒有免費午餐,而 inline functions 也不例外。一個 inline function 背後的思想是用函數的代碼本體代替每一處對這個函數的調用,而且不必取得統計學博士學位就可以看出這樣很可能會增加你的目标代碼的大小。在有限記憶體的機器上,過分熱衷于 inlining(内聯化)會使得程式對于可用空間來說過于龐大。即使使用了虛拟記憶體,inline 引起的代碼膨脹也會導緻額外的分頁排程,減少指令緩存命中率,以及随之而來的性能損失。

在另一方面,如果一個 inline function 本體很短,為函數本體生成的代碼可能比為一個函數調用生成的代碼還要小。如果是這種情況,inlining(内聯化)這個函數可以實際上導緻更小的目标代碼和更高的指令緩存命中率!

記住,inline 是向編譯器發出的一個請求,而不是一個指令。這個請求能夠以隐式的或顯式的方式提出。隐式的方法就是在一個 class definition(類定義)的内部定義一個函數:

class Person {

public:

  ...

  int age() const { return theAge; }    // an implicit inline request: age is

  ...                                   // defined in a class definition

private:

  int theAge;

};

這樣的函數通常是 member functions(成員函數),但是 Item 46 闡釋了 friend functions(友元函數)也能被定義在 classes 的内部,如果是這樣,它們也被隐式地聲明為 inline。

聲明一個 inline function 的顯式方法是在它的聲明之前加上 inline 關鍵字。例如,以下就是标準 max template(來自 <algorithm>)的通常的實作:

template<typename T>                               // an explicit inline

inline const T& std::max(const T& a, const T& b)   // request: std::max is

{ return a < b ? b : a; }                          // preceded by "inline"

max 是一個 template 的事實引出一個觀察結論:inline functions 和 templates 一般都是定義在頭檔案中的。這就使得一些程式員得出結論斷定 function templates(函數模闆)必須是 inline 的。這個結論是無效的而且有潛在的危害,是以它值得我們考察一下。

inline functions 一般必須在頭檔案内,因為大多數建構環境在編譯期間進行 inlining(内聯化)。為了用被調用函數的函數本體替換一個函數調用,編譯器必須知道函數看起來像什麼。(有一些建構環境可以在連接配接期間 inline,還有少數幾個——比如,基于 .NET Common Language Infrastructure (CLI) 的托管環境——居然能在運作時 inline。然而,這些環境都是例外,并非規定。inlining(内聯化)在大多數 C++ 程式中是一個 compile-time activity(編譯期行為)。)

templates 一般在頭檔案内,因為編譯器需要知道一個 template 看起來像什麼以便需要時對它進行執行個體化。(同樣,也不是全部如此。一些建構環境可以在連接配接期間進行 template instantiation(模闆執行個體化)。然而,compile-time instantiation(編譯期執行個體化)更為普遍。)

template instantiation(模闆執行個體化)與 inlining(内聯化)無關。如果你寫了一個 template,而且你認為所有從這個 template 執行個體化出來的函數都應該被内聯,那麼就聲明這個模闆為 inline,這就是上面的 std::max 的實作所做的事情。但是如果你為沒有理由被内聯的函數寫一個 template,就要避免聲明這個 template 為 inline(無論顯式的還是隐式的)。inlining(内聯化)是有成本的,而且你不希望在毫無預見的情況下遭遇它們。我們已經說到 inlining(内聯化)是如何引起代碼膨脹的(這對于 template 作者來說是極為重要的一個考慮事項——參見 Item 44),但是,還有其它的成本,過一會兒我們再讨論。

在做這件事之前,我們先來完成對這個結論的考察:inline 是一個編譯器可能忽略的請求。大多數編譯器拒絕它們認為太複雜的 inline functions(例如,那些包含循環或者遞歸的),而且,除了最瑣碎的以外的全部的對 virtual functions(虛拟函數)的調用都抗拒被 inlining(内聯化)。不應該對這後一個結論感到驚訝。virtual 意味着“等待,直到運作時再斷定哪一個函數被調用”,而 inline 意味着“執行之前,用被調用的函數取代調用的位置”。如果編譯器不知道哪一個函數将被調用,你很難責備它們拒絕内聯這個函數本體。

所有這些加在一起,就會得出:一個特定的 inline function 是否能真的被内聯,取決于你正在使用的建構環境——主要是編譯器。幸運的是,大多數編譯器都有一個診斷層次,在它們不能 inline 一個你提出請求的函數時,會導緻一個警告(參見 Item 53)。

(本篇未完,點選此處,接下篇)