C++程式碼最佳化方法總結(一) (轉)

worldblog發表於2007-08-16
C++程式碼最佳化方法總結(一) (轉)[@more@]

  C++程式碼方法總結(一)
  cpp  to:cpp_bug@.com">cpp_bug@hotmail.com
最佳化是一個非常大的主題,本文並不是去深入探討分析理論,演算法的,況且我也沒有這個能力。我只是想把一些可以簡單的應用到你的C++程式碼中的最佳化技術總結在這裡,這樣,當你遇到幾種不同的策略的時候,就可以對每種策略的效能進行一個大概的估計。這也是本文的目的之所在。
一. 最佳化之前
在進行最佳化之前,我們首先應該做的是發現我們程式碼的瓶頸(bottleneck)在哪裡。然而當你做這件事情的時候切忌從一個debug-version進行推斷,因為debug-version中包含了許多額外的程式碼。一個debug-version可體要比release-version大出40%。那些額外的程式碼都是用來支援的,比如說符號的查詢。大多數實現都為debug-version和release-version提供了不同的operator new以及庫。而且,一個release-version的執行體可能已經透過多種途徑進行了最佳化,包括不必要的臨時的消除,迴圈展開,把物件移入暫存器,內聯等等。
另外,我們要把除錯和最佳化區分開來,它們是在完成不同的任務。 debug-version 是用來追捕bugs以及檢查是否有邏輯上的問題。release-version則是用來做一些效能上的調整以及進行最佳化。
下面就讓我們來看看有哪些程式碼最佳化技術吧:

二. 宣告的放置
程式中變數和物件的宣告放在什麼位置將會對效能產生顯著影響。同樣,對postfix和prefix運算子的選擇也會影響效能。這一部分我們集中討論四個問題:初始化v.s 賦值,在程式確實要使用的地方放置宣告,建構函式的初始化列表,prefix v.s postfix運算子。
(1) 請使用初始化而不是賦值
在C語言中只允許在一個函式體的開頭進行變數的宣告,然而在C++中宣告可以出現在程式的任何位置。這樣做的目的是希望把物件的宣告拖延到確實要使用它的時候再進行。這樣做可以有兩個好處:1. 確保了物件在它被使用前不會被程式的其他部分惡意修改。如果物件在開頭就被宣告然而卻在20行以後才被使用的話,就不能做這樣的保證。2. 使我們有機會透過用初始化取代賦值來達到效能的提升,從前宣告只能放在開頭,然而往往開始的時候我們還沒有獲得我們想要的值,因此初始化所帶來的好處就無法被應用。但是現在我們可以在我們獲得了想要的值的時候直接進行初始化,從而省去了一步。注意,或許對於基本型別來說,初始化和賦值之間可能不會有什麼差異,但是對於定義的型別來說,二者就會帶來顯著的不同,因為賦值會多進行一次函式----operator =。因此當我們在賦值和初始化之間進行選擇的話,初始化應該是我們的首選。
(2) 把宣告放在合適的位置上
在一些場合,透過移動宣告到合適的位置所帶來的效能提升應該引起我們足夠的重視。例如:
bool is_C_Needed();
void use()
{
  C c1;
  if (is_C_Needed() == false)
  {
  return; //c1 was not needed
  } 
  //use c1 here
  return;
}
上面這段程式碼中物件c1即使在有可能不使用它的情況下也會被建立,這樣我們就會為它付出不必要的花費,有可能你會說一個物件c1能浪費多少時間,但是如果是這種情況呢:C c1[1000];我想就不是說浪費就浪費了。但是我們可以透過移動宣告c1的位置來改變這種情況:
void use()
{
 if (is_C_Needed() == false)
  {
    return; //c1 was not needed
  } 
  C c1; //moved from the block's beginning
  //use c1 here
  return;
}
怎麼樣,程式的效能是不是已經得到很大的改善了呢?因此請仔細分析你的程式碼,把宣告放在合適的位置上,它所帶來的好處是你難以想象的。
(3) 初始化列表
  我們都知道,初始化列表一般是用來初始化const或者reference資料成員。但是由於他自身的性質,我們可以透過使用初始化列表來實現效能的提升。我們先來看一段程式:
   class Person
{
private:
   C c_1;
   C c_2;
public:
   Person(const C& c1, const C& c2 ): c_1(c1), c_2(c2) {}
};
當然建構函式我們也可以這樣寫:
Person::Person(const C& c1, const C& c2)
{
 c_1 = c1;
 c_2 = c2;
}
那麼究竟二者會帶來什麼樣的效能差異呢,要想搞清楚這個問題,我們首先要搞清楚二者是如何執行的,先來看初始化列表:資料成員的宣告操作都是在建構函式執行之前就完成了,在建構函式中往往完成的只是賦值操作,然而初始化列表直接是在資料成員宣告的時候就進行了初始化,因此它只執行了一次copy constructor。再來看在建構函式中賦值的情況:首先,在建構函式執行前會透過default constructor建立資料成員,然後在建構函式中透過operator =進行賦值。因此它就比初始化列表多進行了一次函式呼叫。效能差異就出來了。但是請注意,如果你的資料成員都是基本型別的話,那麼為了程式的可讀性就不要使用初始化列表了,因為對兩者產生的程式碼是相同的。
(4) postfix VS prefix 運算子
prefix運算子++和—比它的postfix版本效率更高,因為當postfix運算子被使用的時候,會需要一個臨時物件來儲存改變以前的值。對於基本型別,編譯器會消除這一份額外的複製,但是對於使用者定義型別,這似乎是不可能的。因此請你儘可能使用prefix運算子。


 


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/10752043/viewspace-962107/,如需轉載,請註明出處,否則將追究法律責任。

相關文章