|
C++ 代碼優化 | ||||
C++ 代碼優化 優化是一個非常大的主題,本文并不是去深入探討性能分析理論,算法的效率,況且我也沒有這個能力。我只是想把一些可以簡單的應用到你的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運算符。 | ||||
|