- 條件內(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指示符將被清除。
- 選擇性內(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)生唯一實例。
- 遞歸內(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)容
- 對靜態(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>