最近在耐著性子,看了大半的《C++應用程式效能最佳化》和《Efficient C++ Performance Programming Techniques》。總體感覺,雖然內容不一樣,但前者就像是後者的中文化版及針對VC2003的版本。想來漢語本應言簡意賅,但前者寫得似乎有些囉嗦,看著反不如後者順暢。
一直用C++,大部分時間寫數值計算的程式,所以C++的效能常常掛在心中。各種語言的效能比較,各種編譯器的比較,看了研究了比較了,最後的結論無非就是那幾條看似沒有深刻原理的經驗性質的說明。當逃離BBS上的是非之地,靜下心來關注一具體細節時,才發現自己的認識是多少膚淺,道聽途說的東西,不可盡信。
曾以為,虛函數是OO中效能降低的原由,模板及範型編程是實現高效的唯一曙光。但真實的情況卻告訴我,虛表與虛方法的尋找,並未讓程式降低太多效能,而這恰是保持編程通用性,可擴充性,重用性及效能的一種語言上的優良特性;“降低效能的地方”,確切地說,“沒能讓程式進一步提高效能的地方”是動態綁定讓編譯器不能內聯方法,從而失去了內聯帶來的效能提高。
也曾以為,內聯就是把方法裡的代碼移過來,沒有了寄存器儲存,沒有了call,於是就沒有了這些調用的開銷。但事實又告訴我,這僅僅是內聯最基本的好處,更多的好處在於,代碼移動到調用的地方,使編譯器能進行根據“上下文”進行全面的最佳化。
例如這樣的代碼: Class GatedInt ... {
int x;
public:
int get () ...{
return x;
}
void set (int arg) ...{
x = arg;
}
}
int main ()
... {
GatedInt gi;
gi.set(12);
cout << gi.get();
}
內聯後,會最佳化為: int main ()
... {
cout << 12;
}
內聯被稱為C++程式獲得效能提高的最簡單途徑。但實際上,是否內聯的標準是比較複雜的。更有趨勢說,內聯與否是應由編譯器自己確立的事情,終有一天我們會忽略inline。
現在終於明白,如果能真正看清C++代碼的每一句,對應會產生多少行指命,最佳化效能也不是問題。事實是除了編譯器,人是很難做到這一點的。於是只有總結各點,以不斷提醒自己,注意自己寫的每一行代碼,究竟做了什麼事情:
建構函式與解構函式。建立對象,只是簡單的一句。Object obj或Object *pObj = new Object。但構造與析構裡,做了多少耗時的操作,這是一個問題。另外,建構函式裡,初使化對象的順序,是由成員的聲明的順序唯一決定的,與初使化列表無關。但使用初使化列表,是非常好的習慣。 臨時變數。臨時變數的產生很難發現,最典型的便是矩陣加法的代碼:Matrix A,B,C; C=A+B;這裡不知道有多少人能清楚地指出操作中產生臨時變數的個數。返回對象是產生臨時變數的常見情況,還好有一“傳回值最佳化”的方法,可以在一定程度上減少臨時變數的產生。 new/delete,預設的庫除非你真正地瞭解它,否則為了效能,它也是不可考慮的,即便它是預設的。new/delete是安全執行緒的。但這在有些情況下,並不需要。除去lock之後,很容易發現,程式效能會有很大提高。 堆和棧。棧有cpu級的操作指令支援,因此速度很快;而堆需要一向的演算法來維護,與new/delete的實現有關。因此較慢。 不要完全相信STL。不過,多研究一下Effective STL是有益的。
總地說來,最佳化程式的效能的方法並不多。但寫出一個高效的程式,仍不是一件容易掌握的事情。曾經見過一網友通過自己的實驗驗證了i++與++i在效能上並無大異,大膽地懷疑“++i”這一經驗之談。現在看來真是好笑。每次看完書,都會發覺其實只要瀏覽每章最後的小節,大致的內容便能瞭然於心。但如果只是瀏覽這些關鍵點,就如同在BBS中看別的人經驗貼一樣,只會產生不成熟的懷疑或認識,稍作深入,便會發現自己的膚淺與浮躁。