《提高C++性能的編程技巧》—— 第十章 內(nèi)聯(lián)技巧

  1. 條件內(nèi)聯(lián):編譯,調(diào)試和配置等過程與內(nèi)聯(lián)存在一定的沖突,因此做這些工作時,都希望將內(nèi)聯(lián)決策推遲到開發(fā)周期的后期。
    思路:利用編譯行參數(shù)向編譯器傳遞一個宏定義。
    輸入?yún)?shù)用來定義名為INLINE的宏,外聯(lián)方法包含于標(biāo)準(zhǔn)的.c文件,內(nèi)聯(lián)的方法放置在.inl文件中,如果需要對.inl中的方法內(nèi)聯(lián),可以在編譯命令中使用-D選項來定義INLINE宏。
    編譯時內(nèi)聯(lián)開關(guān)示例:
文件x.h
if !defined(_X_H)
#define _X_H
class X {
  int y (int a);
};
#if defined(INLINE)
#include x.inl
#endif
#endif   //_X_H

文件x.inl
#if !defined(INLINE)
#define inline
#endif
inline
int X::y (int a) {
  ...
}

文件x.c
#if !defined(INLINE)
#includex.inl
#endif

.h文件如一貫所示。如果INLINE被定義,則.h文件將包含.inl文件,且各個方法之前的inline指示符將不受影響、若為定義INLINE,.h文件將不會包含內(nèi)聯(lián)方法,這些方法會包含在.c文件中,同時每個方法前面的inline指示符將被清除。

  1. 選擇性內(nèi)聯(lián):針對一個方法,某些地方內(nèi)聯(lián),某些位置正常調(diào)用。
    可以寫兩個版本直接解決:
文件x.h
class X {
  int inline_y (int a);
  int y (int a);
};
#include x.inl

文件x.inl
inline
int X::y (int a) {
  ...  //y的原始實現(xiàn)
}

文件x.c
int X::y (int a) {
  return inline_y(a);
}

通過以上代碼,獲得兩種版本的y。外聯(lián)方法y()中的return inline_y(a);存在好處:對于方法內(nèi)的任何靜態(tài)變量,單個方法體可以為其產(chǎn)生唯一實例。

  1. 遞歸內(nèi)聯(lián):直接遞歸是無法內(nèi)聯(lián)的。尾部遞歸是遞歸方法的一種,他變現(xiàn)為方法在達(dá)到它的基線之前一直遞歸下降。
    當(dāng)達(dá)到基線條件后會執(zhí)行一些操作并終止方法。典型的二叉搜索樹就是一個很好的尾部遞歸的例子。
binary_tree* binary_tree::find(int key){
    if(key == id){
        return this;
    }
    else if(key > id){
        if(right) return right.find(key);
    }
    else {
        if(left) return left.find(key);
    }
    return 0;
}

正如所見,當(dāng)程序滿足基線后,除返回一個指向?qū)ο蟮闹羔樛猓瑳]有執(zhí)行任何操作。因為遞歸生成的調(diào)用堆棧的上下文微不足道。
實際上,如果編譯器能夠簡單的預(yù)留一個變量以保存this,那么在不生成新方法上下文的情況下,該方法就可以被執(zhí)行。
binary_tree* binary_tree::find(int key){
binary_tree *temp = this;
while(temp){
if(key == temp->id){
return this;
}
else if(key > temp->id){
temp = right;
}
else {
temp = left;
}
}
return 0;
}

還有一種針對遞歸最為普遍的方法:將該遞歸方法重新展開一次并命名,然后內(nèi)聯(lián)該新方法,新方法又會調(diào)用老方法,例,二叉樹中生成id的中綴列表的方法:

void binary_tree::key_out(){
    if(left) left->key_out();
    cout << id << endl;
    if(right) right->key_out();
}

使用內(nèi)聯(lián)把key_out方法按常用方式展開一次,這樣導(dǎo)致代碼版本為原先2倍,速度比原始版本快2~3倍:

inline 
void binary_tree::UNROLLED_key_out(){
    if(left) left->key_out();
    cout<< id << endl;
    if(right) right->key_out();
}

void binary_tree::key_out(){
    if(left) left->UNROLLED_key_out();
    cout << id << endl;
    if(right) right->UNROLLED_key_out();
}

單層展開提供了最佳性價比,然而如果必要,可以進一步展開,如4次迭代等
使用舊式的C #define 宏擴展可以將展開的方法統(tǒng)一起來。

#define KEY_OUT_MACRO(inline_arg, my_label, call_label) \
                                                        \
inline_arg                                              \
void binary_tree::UNROLLED##my_label##_key_out(){       \
    if(left) left->UNROLLED_key_out##call_label##_key_out(); \
    cout << id << endl; \
    if(right) right->UNROLLED_key_out##call_label##_key_out(); \ 
}

KEY_OUT_MACRO(inline, 3, 0)
KEY_OUT_MACRO(inline, 2, 3)
KEY_OUT_MACRO(inline, 1, 2)
KEY_OUT_MACRO(\\t, 0, 1)

inline
void x::key_out(){
    UNROLLED_key_out();
}

盡管這些代碼看起來較少,但是當(dāng)C++的預(yù)處理器完成宏擴展、編譯器完成內(nèi)斂之后,就會像之前一樣產(chǎn)生代碼膨脹。
因此,推廣宏擴展這種脆弱的機制是很困難的,然而當(dāng)需要采取這種極端措施時,其優(yōu)點(只有遞歸一個版本)通常會蓋過缺點。

與4層展開相比,8層展開所得性能會提升2-3倍,然而4層展開代碼大小是原始版本的4倍,8層展開就是原來的64倍。
注:
3.1. #的功能是將其后面的宏參數(shù)進行字符串化操作(Stringizing operator),簡單說就是在它引用的宏變量的左右各加上一個雙引號。
例如#define STRING(x) #x
則:char *pChar = STRING(hello); 與char *pChar = "hello"; 一樣
3.2. ##進行拼接,就是消除中間的內(nèi)容

  1. 對靜態(tài)局部變量的內(nèi)聯(lián)
    部分編譯器會允許內(nèi)聯(lián)靜態(tài)變量,但是會出一個問題:錯誤地為這些內(nèi)聯(lián)變量創(chuàng)建多個實例。
    對于內(nèi)聯(lián)包含靜態(tài)變量的方法來說,問題是要結(jié)局靜態(tài)變量的唯一性以及保證靜態(tài)變量被初始化。
    對于限定范圍內(nèi),在邏輯上來說,局部靜態(tài)變量就是全局變量。這需要連接器足夠智能,檢測到使用這種靜態(tài)變量的各種情況,然后創(chuàng)建該變量,接著在全局?jǐn)?shù)據(jù)空間內(nèi)保留空間,進而為該變量創(chuàng)建初始化代碼(或確保已經(jīng)初始化),最后將所有對該變量的內(nèi)聯(lián)引用連接到全局?jǐn)?shù)據(jù)區(qū)域為該變量新建的唯一實例中。(在相互分離的編譯模塊領(lǐng)域內(nèi),燼燼確定動向靜態(tài)變量的問題就已經(jīng)讓不少編譯有苦不堪言。)
    判斷編譯器是否可以恰當(dāng)?shù)奶幚盱o態(tài)變量的內(nèi)聯(lián),示例:
z.h:
inline
int test () {
  static int i = 0;
  return ++i;
}
y.cpp:
#include "z.h"
void test_a () {
  int i = test();
  cout << i;
}
x.cpp:
#include "z.h"
void test_b () {
  int i = test();
  cout << i;
}
main.cpp
int main () {
  test_a();
  test_b();
  count << endl;
  return 0;
}

如果編譯器創(chuàng)建的代碼正確,則會分別對三個.cpp文件進行編譯及連接并產(chǎn)生一個可執(zhí)行程序,輸出結(jié)果為12。如果結(jié)果為11,表明編譯器在內(nèi)聯(lián)含有靜態(tài)變量的方法方面存在問題。(解決方法:類的內(nèi)部之用靜態(tài)成員變量即可)
總結(jié):
a. 條件內(nèi)聯(lián)可以阻止內(nèi)聯(lián)的發(fā)生,這樣可以減少編譯時間,同時簡化了開發(fā)前期的調(diào)試工作。
b. 選擇性內(nèi)聯(lián)是一種只在某些地方內(nèi)聯(lián)方法的技術(shù),他只在對性能有重大影響的路徑上對方法調(diào)用進行內(nèi)聯(lián)
c. 內(nèi)聯(lián)的目標(biāo)是消除調(diào)用開銷。在使用內(nèi)聯(lián)之前請弄清楚系統(tǒng)中真正的調(diào)用代價。
(例如SPARC體系結(jié)構(gòu),擁有多寄存器集,其方法調(diào)用代價較?。?/p>

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 題目類型 a.C++與C差異(1-18) 1.C和C++中struct有什么區(qū)別? C沒有Protection行為...
    阿面a閱讀 7,894評論 0 10
  • 內(nèi)聯(lián)的疑惑 寫這篇文章的初衷源自于對netdata項目把C函數(shù)聲明為static inline的用法不解。從語言特...
    typesafe閱讀 2,361評論 1 1
  • Swift1> Swift和OC的區(qū)別1.1> Swift沒有地址/指針的概念1.2> 泛型1.3> 類型嚴(yán)謹(jǐn) 對...
    cosWriter閱讀 11,675評論 1 32
  • 本應(yīng)用尊重并保護所有使用服務(wù)用戶的個人隱私權(quán)。為了給您提供更準(zhǔn)確、更有個性化的服務(wù),本應(yīng)用會按照本隱私權(quán)政策的規(guī)定...
    可以叫我湯包呀閱讀 148評論 1 0
  • 今天看節(jié)目時,偶然看到了那句“情不知所起,一往情深?!保|及所思,不禁問自己“為什么我的眼中 飽含淚水”。 時光飛...
    鐘離凌羽閱讀 442評論 0 0

友情鏈接更多精彩內(nèi)容