前文聲明:這篇文章最大的特點是幾乎原文照抄!
有一種說法是,由於臨時對象的產生,c++的執行會浪費掉不少效率,因此在以前的計算機界,c++只能成爲FORTRAN等主流語言之外的第二語言。還有不少人認爲,這種效率上的浪費足以掩蓋c++在抽象對象化上做出的貢獻。
在FORTRAN-77和c++的一場較量中,Kent Budge和他的助手分別FORTRAN和c++寫了一個複數測試程序,在FORTRAN中的複數是內建類型,而在c++中它是一個具體類,有兩個members,一個代表實數,一個代表虛數【現在的c++ standard已經把複數類放在標準鏈接庫中】。c++的實現inline如下:
friend complex operator+(complex, complex);//參數的傳遞是形參傳遞,而非引用!
請注意,被傳遞的class object【內帶constructor和destructor】是以by value而非by reference的方式傳遞。
除了“copy by value possibly large class object”這個主題之外,每一個正式參數的局部實體都要被copy constructor和destructor,並且可能導致臨時對象的產生。在這個測試例子中,作者聲稱把正式參數轉換爲const reference並不會明顯改變效率,這是因爲每一個函數是inline之故。
測試程序看起來像這樣:
void func( complex *a, const complex *b, const complex &c, int N)
{
for( int i=0; i<N; i++)
a[i] = b[i]+c[i] - b[i]*c[i];
}
其中對於複數的+、-、*以及assignment運算符,都是用inline函數,c++編譯器會產生五個臨時對象:
1):第一個臨時對象,用來放置【b[i]+c[i]】;
2):第二個臨時對象,用來放置【b[i]*c[i]】;
3):第三個臨時對象,用來替換第一個臨時對象;
4):第四個臨時對象,用來替換第二個臨時對象;
5):第五個臨時對象,用來放置第三個臨時對象和第四個臨時對象的相減結果。
測試結果表明,FORTRAN-77要比c++快兩倍。對於這個結果,人們的第一個假設是把黑鍋甩給臨時對象,爲了驗證假設正確與否,前輩們用手工方式把cfront中介輸出代碼中的所有臨時對象都消除,最後的實驗結果表明,人們的假設是對的,手工c++的效率和FORTRAN-77不相上下。
然而測試並沒有就此止住,Budge和助手用另一種方法來實驗:這一次他們採用反聚合【disaggregated】手法,既把所有的臨時對象拆開爲一對一對的臨時性double變量。。。最後的實驗結果表明,這種做法也可以提升效率,就和先前消除所有臨時對象的方式一樣。從這個實驗中,他們總結的心得如下:【我們所測試的編譯系統很明顯能夠消除內建類型【如char】的局部變量,但對於class類型的局部變量就行不通。這是C++ back-ends(而不是C++ cfront-end)的限制。這似乎是很普遍的情況,因爲在Sun CC、GUN g++以及HP CC編譯器中都會發生。[BUDGE94]】
這篇文章分析了被產生出來的assembly【彙編】碼,並表示效率降低的原因是因爲程序中存在大量的堆棧存取操作(用來讀寫個別的class member),經過反聚合【disassregated】以及把個別的members放在緩存器中,就能達到幾乎快兩倍的效率!最後他們得出這樣的結論:加上適度的努力,反聚合【disaggregated】大有可爲。但是一般的C++編譯器並沒有重視這方面的優化技術】
這份研究對於良好的優化策略提供了一個具有說服力的證明,目前存在一些優化工具,確實把臨時對象的一些成分放進了緩存器中。當編譯器廠商把他們的焦點從對語言特性的支持轉移到對實現技術的優化上時,如反聚合【disaggregated】這樣的技術就更普遍了。