C++代碼優化經驗總結_第1頁
C++代碼優化經驗總結_第2頁
C++代碼優化經驗總結_第3頁
C++代碼優化經驗總結_第4頁
C++代碼優化經驗總結_第5頁
已閱讀5頁,還剩28頁未讀 繼續免費閱讀

付費下載

下載本文檔

版權說明:本文檔由用戶提供并上傳,收益歸屬內容提供方,若內容存在侵權,請進行舉報或認領

文檔簡介

1、C+代碼優化經驗總結優化是一個非常大的主題,本文并不是去深入探討性能分析理論,算法的效率,況且我也沒有這個能力。我只是想把一些可以簡單的應用到你的C+代碼中的優化技術總結在這里,這樣,當你遇到幾種不同的編程策略的時候,就可以對每種策略的性能進行一個大概的估計。這也是本文的目的之所在. 目錄: 一. 優化之前 二. 聲明的放置 三. 內聯函數 四. 優化你的內存使用 五. 速度優化 六. 最后的求助 一. 優化之前 在進行優化之前,我們首先應該做的是發現我們代碼的瓶頸(bottleneck)在哪里。 然而當你做這件事情的時候切忌從一個debug-version進行推斷,因為debug-versi

2、on中包 含了許多額外的代碼。一個debug-version可執行體要比release-version大出40%。那些額 外的代碼都是用來支持調試的,比如說符號的查找。大多數實現都為debug-version和rele ase-version提供了不同的operator new以及庫函數。而且,一個release-version的執行 體可能已經通過多種途徑進行了優化,包括不必要的臨時對象的消除,循環展開,把對象 移入寄存器,內聯等等。 另外,我們要把調試和優化區分開來,它們是在完成不同的任務。 debug-version 是 用來追捕bugs以及檢查程序是否有邏輯上的問題。release-v

3、ersion則是用來做一些性能上 的調整以及進行優化。 下面就讓我們來看看有哪些代碼優化技術吧! 二. 聲明的放置 程序中變量和對象的聲明放在什么位置將會對性能產生顯著影響。同樣,對postfix和 prefix運算符的選擇也會影響性能。這一部分我們集中討論四個問題:初始化v.s 賦值, 在程序確實要使用的地方放置聲明,構造函數的初始化列表,prefix v.s postfix運算符 。 (1) 請使用初始化而不是賦值 在C語言中只允許在一個函數體的開頭進行變量的聲明,然而在C+中聲明可以出現在 程序的任何位置。這樣做的目的是希望把對象的聲明拖延到確實要使用它的時候再進行。 這樣做可以有兩個好

4、處:1. 確保了對象在它被使用前不會被程序的其他部分惡意修改。如 果對象在開頭就被聲明然而卻在20行以后才被使用的話,就不能做這樣的保證。2. 使我們 有機會通過用初始化取代賦值來達到性能的提升,從前聲明只能放在開頭,然而往往開始 的時候我們還沒有獲得我們想要的值,因此初始化所帶來的好處就無法被應用。但是現在 我們可以在我們獲得了想要的值的時候直接進行初始化,從而省去了一步。注意,或許對 于基本類型來說,初始化和賦值之間可能不會有什么差異,但是對于用戶定義的類型來說 ,二者就會帶來顯著的不同,因為賦值會多進行一次函數調用-operator =。因此當我 們在賦值和初始化之間進行選擇的話,初始化

5、應該是我們的首選。 (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 c11000;我想就不是說浪費就浪費了。但是我們可以通過移動聲明c1的位置

6、來改變這種情 況: 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數據成員。但是由于 他自身的性質,我們可以通過使用初始化列表來實現性能的提升。我們先來看一段程序:

7、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; 那么究竟二者會帶來什么樣的性能差異呢,要想搞清楚這個問題,我們首先要搞清楚二者 是如何執行的,先來看初始化列表:數據成員的聲明操作都是在構造函數執行之前就完成 了,在構造函數中往往完成的只是賦值操作,然而初始化列表直接是

8、在數據成員聲明的時 候就進行了初始化,因此它只執行了一次copy constructor。再來看在構造函數中賦值的 情況:首先,在構造函數執行前會通過default constructor創建數據成員,然后在構造函 數中通過operator =進行賦值。因此它就比初始化列表多進行了一次函數調用。性能差異 就出來了。但是請注意,如果你的數據成員都是基本類型的話,那么為了程序的可讀性就 不要使用初始化列表了,因為編譯器對兩者產生的匯編代碼是相同的。 (4) postfix VS prefix 運算符 prefix運算符+和比它的postfix版本效率更高,因為當postfix運算符被使用的時 候,

9、會需要一個臨時對象來保存改變以前的值。對于基本類型,編譯器會消除這一份額外 的拷貝,但是對于用戶定義類型,這似乎是不可能的。因此請你盡可能使用prefix運算符 。 三. 內聯函數 內聯函數既能夠去除函數調用所帶來的效率負擔又能夠保留一般函數的優點。然而, 內聯函數并不是萬能藥,在一些情況下,它甚至能夠降低程序的性能。因此在使用的時候 應該慎重。 1我們先來看看內聯函數給我們帶來的好處:從一個用戶的角度來看,內聯函數看起來和 普通函數一樣,它可以有參數和返回值,也可以有自己的作用域,然而它卻不會引入一般 函數調用所帶來的負擔。另外,它可以比宏更安全更容易調試。 當然有一點應該意識到,inlin

10、e specifier僅僅是對編譯器的建議,編譯器有權利忽 略這個建議。那么編譯器是如何決定函數內聯與否呢?一般情況下關鍵性因素包括函數體 的大小,是否有局部對象被聲明,函數的復雜性等等。 2那么如果一個函數被聲明為inline但是卻沒有被內聯將會發生什么呢?理論上,當編譯 器拒絕內聯一個函數的時候,那個函數會像普通函數一樣被對待,但是還會出現一些其他 的問題。例如下面這段代碼: / Time.h #include<ctime> #include<iostream> using namespace std; class Time public: inline void

11、Show() for (int i = 0; i<10; i+) cout<<time(0)<<endl; ; 因為成員函數Time:Show()包括一個局部變量和一個for循環,所以編譯器一般拒絕inline ,并且把它當作一個普通的成員函數。但是這個包含類聲明的頭文件會被單獨的#include 進各個獨立的編譯單元中: / f1.cpp #include "Time.hj" void f1() Time t1; t1.Show(); / f2.cpp #include "Time.h" void f2() Time t2

12、; t2.Show(); 結果編譯器為這個程序生成了兩個相同成員函數的拷貝: void f1(); void f2(); int main() f1(); f2(); return 0; 當程序被鏈接的時候,linker將會面對兩個相同的Time:Show()拷貝,于是函數重定 義的連接錯誤發生。但是老一些的C+實現對付這種情況的辦法是通過把一個un-inlined函 數當作static來處理。因此每一份函數拷貝僅僅在自己的編譯單元中可見,這樣鏈接錯誤 就解決了,但是在程序中卻會留下多份函數拷貝。在這種情況下,程序的性能不但沒有提 升,反而增加了編譯和鏈接時間以及最終可執行體的大小。 但是幸運

13、的是,新的C+標準中關于un-inlined函數的說法已經改變。一個符合標準C+ +實現應該只生成一份函數拷貝。然而,要想所有的編譯器都支持這一點可能還需要很長時 間。 另外關于內聯函數還有兩個更令人頭疼的問題。第一個問題是該如何進行維護。一個 函數開始的時候可能以內聯的形式出現,但是隨著系統的擴展,函數體可能要求添加額外 的功能,結果內聯函數就變得不太可能,因此需要把inline specifier去除以及把函數體 放到一個單獨的源文件中。另一個問題是當內聯函數被應用在代碼庫的時候產生。當內聯 函數改變的時候,用戶必須重新編譯他們的代碼以反映這種改變。然而對于一個非內聯函 數,用戶僅僅需要重

14、新鏈接就可以了。 這里想要說的是,內聯函數并不是一個增強性能的靈丹妙藥。只有當函數非常短小的 時候它才能得到我們想要的效果,但是如果函數并不是很短而且在很多地方都被調用的話 ,那么將會使得可執行體的體積增大。最令人煩惱的還是當編譯器拒絕內聯的時候。在老 的實現中,結果很不盡人意,雖然在新的實現中有很大的改善,但是仍然還是不那么完善 的。一些編譯器能夠足夠的聰明來指出哪些函數可以內聯哪些不能,但是,大多數編譯器 就不那么聰明了,因此這就需要我們的經驗來判斷。如果內聯函數不能增強行能,就避免 使用它! 四. 優化你的內存使用 通常優化都有幾個方面:更快的運行速度,有效的系統資源使用,更小的內存使用

15、。 一般情況下,代碼優化都是試圖在以上各個方面進行改善。重新放置聲明技術被證明是消 除多余對象的建立和銷毀,這樣既減小了程序的大小又加快了運行速度。然而其他的優化 技術都是基于一個方面-更快的速度或者是更小的內存使用。有時,這些目標是互斥 的,壓縮了內存的使用往往卻減慢了代碼速度,快速的代碼卻又需要更多的內存支持。下 面總結兩種在內存使用上的優化方法: 1 Bit Fields 在C/C+中都可以存取和訪問數據的最小組成單元:bit。因為bit并不是C/C+基本的 存取單元,所以這里是通過犧牲運行速度來減少內存和輔助存儲器的空間的使用。注意: 一些硬件結構可能提供了特殊的處理器指令來存取bit

16、,因此bit fields是否影響程序的速 度取決于具體平臺。 在我們的現實生活中,一個數據的許多位都被浪費了,因為某些應用根本就不會有那 么大的數據范圍。也許你會說,bit是如此之小,通過它就能減小存儲空間的使用嗎?的確 ,在數據量很小的情況下不會看出什么效果,但是在數據量驚人的情況下,它所節省的空 間還是能夠讓我們的眼睛為之一亮的。也許你又會說,現在內存和硬盤越來越便宜,何苦 要費半天勁,這省不了幾個錢。但是還有另外一個原因一定會使你信服,那就是數字信息 傳輸。一個分布式數據庫都會在不同的地點有多份拷貝。那么數百萬的紀錄傳輸就會顯得 十分昂貴。Ok,現在我們就來看看該如何做吧,首先看下面這

17、段代碼: struct BillingRec long cust_id; long timestamp; enum CallType toll_free, local, regional, long_distance, international, cellular type; enum CallTariff off_peak, medium_rate, peak_time tariff; ; 上面這個結構體在32位的機器上將會占用16字節,你會發現其中有許多位都被浪費了,尤 其是那兩個enum型,浪費更是嚴重,所以請看下面做出的改進: struct BillingRec int cust_i

18、d: 24; / 23 bits + 1 sign bit int timestamp: 24; enum CallType /. ; enum CallTariff /. ; unsigned call: 3; unsigned tariff: 2; ; 現在一個數據從16字節縮減到了8字節,減少了一半,怎么樣,效果還是顯著的吧:) 2 Unions Unions通過把兩個或更多的數據成員放置在相同地址的內存中來減少內存浪費,這就 要求在任何時間只能有一個數據成員有效。Union 可以有成員函數,包括構造函數和析構 函數,但是它不能有虛函數。C+支持anonymous unions。anon

19、ymous union是一個未命名 類型的未命名對象。例如: union long n; void * p; / anonymous n = 1000L; / members are directly accessed p = 0; / n is now also 0 不像命名的union,它不能有成員函數以及非public的數據成員。 那么unions什么時候是有用的呢?下面這個類從數據庫中獲取一個人的信息。關鍵字既可 以是一個特有的ID或者人名,但是二者卻不能同時有效: class PersonalDetails private: char * name; long ID; /. publ

20、ic: PersonalDetails(const char *nm); /key is of type char * used PersonalDetails(long id) : ID(id) /numeric key used ; 上面這段代碼中就會造成內存的浪費,因為在一個時間只能有一個關鍵字有效。anonymous union可以在這里使用來減少內存的使用,例如: class PersonalDetails private: union /anonymous char * name; long ID; ; public: PersonalDetails(const char *nm)

21、; PersonalDetails(long id) : ID(id) /*/ / direct access to a member /. ; 通過使用union,PersonalDetails類的大小被減半。但是這里要說明的是,節省4 個字節 內存并不值得引入union所帶來的麻煩,除非這個類作為數百萬數據庫記錄的類型或者紀錄 在一條很慢的通信線路傳輸。值得注意的是unions并不引入任何運行期負擔,所以這里不 會有什么速度上的損失。anonymous union的優點就是它的成員可以被直接訪問。 五. 速度優化 在一些對速度要求非常苛刻的應用系統中,每一個CPU周期都是要爭取的。這個部分

22、展 現了一些簡單方法來進行速度優化。 1 使用類來包裹長的參數列表 一個函數調用的負擔將會隨著參數列表的增長而增加。運行時系統不得不建立堆棧來 存儲參數值;通常,當參數很多的時候,這樣一個操作就會花費很長的時間。 把參數列表包裹進一個單獨的類中并且通過引用進行傳遞,這樣將會節省很多的時間 。當然,如果函數本身就很長,那么建立堆棧的時間就可以忽略了,因此也就沒有必要這 樣做。然而,對于那些執行時間很短而且經常被調用的函數來說,包裹一個長的參數列表 在對象中并且通過引用傳遞將會提高性能。 2 寄存器變量 register specifier被用來告訴編譯器一個對象將被會非常多的使用,可以把它放入

23、寄存器中。例如: void f() int *p = new int3000000; register int *p2 = p; /store the address in a register for (register int j = 0; j<3000000; j+) *p2+ = 0; /.use p delete p; 循環計數是應用寄存器變量的最好的候選者。當它們沒有被存入一個寄存器中,大部 分的循環時間都被用在了從內存中取出變量和給變量賦新值上。如果把它存入一個寄存器 中的話,將會大大減少這種負擔。需要注意的是,register specifier僅僅是對編譯器的 一個建議

24、。就好比內聯函數一樣,編譯器可以拒絕把一個對象存儲到寄存器中。另外,現 代的編譯器都會通過把變量放入寄存器中來優化循環計數。Register storage specifier 并不僅僅局限在基本類型上,它能夠被應用于任何類型的對象。如果對象太大而不能裝進 寄存器的話,編譯器仍然能夠把它放入一個高速存儲器中,例如cache。 用register storage specifier聲明函數型參將會是建議編譯器把實參存入寄存器中 而不是堆棧中。例如: void f(register int j, register Date d); 3 把那些保持不變的對象聲明為const 通過把對象聲明為cons

25、t,編譯器就可以利用這個聲明把這樣一個對象放入寄存器中。 4 Virtual function的運行期負擔 當調用一個virtual function,如果編譯器能夠解決調用的靜態化,將不會引入額外 的負擔。另外,一個非常短的虛函數可以被內聯處理。在下面這個例子中,一個聰明的編 譯器能夠做到靜態調用虛函數: #include <iostream> using namespace std; class V public: virtual void show() const cout<<"I'm V"<<endl; ; class W

26、 : public V public: void show() const cout<<"I'm W"<<endl; ; void f(V & v, V *pV) v.show(); pV->show(); void g() V v; f(v, &v); int main() g(); return 0; 如果整個程序出現在一個單獨的編譯單元中,編譯器能夠對main()中的g()進行內聯替 換。并且在g()中f()的調用也能夠被內聯處理。因為傳給f()的參數的動態類型能夠在編譯 期被知曉,因此編譯器能夠把對虛函數的調用靜態化。但是不能保證每個編譯器都這樣做 。然而,一些編譯器確實能夠利用在編譯期獲得參數的動態類型從而使得函數的調用在編 譯期間就確定了下來,避免了動態綁定的負擔。 5 Function objects VS function pointers 用function objects取代function pointers的好處不僅僅局限在能夠泛化和簡單的維護性 上。而且編譯器能夠對function

溫馨提示

  • 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯系上傳者。文件的所有權益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網頁內容里面會有圖紙預覽,若沒有圖紙預覽就沒有圖紙。
  • 4. 未經權益所有人同意不得將文件中的內容挪作商業或盈利用途。
  • 5. 人人文庫網僅提供信息存儲空間,僅對用戶上傳內容的表現方式做保護處理,對用戶上傳分享的文檔內容本身不做任何修改或編輯,并不能對任何下載內容負責。
  • 6. 下載文件中如有侵權或不適當內容,請與我們聯系,我們立即糾正。
  • 7. 本站不保證下載資源的準確性、安全性和完整性, 同時也不承擔用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。

評論

0/150

提交評論