C++程序代碼優化的方法

1、選擇合適的算法和數據結構  程序員

  選擇一種合適的數據結構很重要,若是在一堆隨機存放的數中使用了大量的插入和刪除指令,那使用鏈表要快得多。數組與指針語句具備十分密切的關係,通常來講,指針比較靈活簡潔,而數組則比較直觀,容易理解。對於大部分的編譯器,使用指針比使用數組生成的代碼更短,執行效率更高。  算法

  在許多種狀況下,能夠用指針運算代替數組索引,這樣作經常能產生又快又短的代碼。與數組索引相比,指針通常能使代碼速度更快,佔用空間更少。使用多維數組時差別更明顯。下面的代碼做用是相同的,可是效率不同。 編程

 數組索引 指針運算  數組

For(;;)緩存

{ p=array;數據結構

 A=array[t++];模塊化

for(;;)函數

{  a=*(p++);  。。。。。。。。。 。。。。。。  }oop

}  佈局

指針方法的優勢是,array的地址每次裝入地址p後,在每次循環中只需對p增量操做。在數組索引方法中,每次循環中都必須根據t值求數組下標的複雜運算。

2、使用盡可能小的數據類型  

可以使用字符型(char)定義的變量,就不要使用整型(int)變量來定義;可以使用整型變量定義的變量就不要用長整型(long int),能不使用浮點型(float)變量就不要使用浮點型變量。固然,在定義變量後不要超過變量的做用範圍,若是超過變量的範圍賦值,C編譯器並不報錯,但程序運行結果卻錯了,並且這樣的錯誤很難發現。  

ICCAVR中,能夠在Options中設定使用printf參數,儘可能使用基本型參數(%c%d%x%X%u%s格式說明符),少用長整型參數(%ld%lu%lx%lX格式說明符),至於浮點型的參數(%f)則儘可能不要使用,其它C編譯器也同樣。在其它條件不變的狀況下,使用%f參數,會使生成的代碼的數量增長不少,執行速度下降。  

3、減小運算的強度  

1查表(遊戲程序員必修課)  一個聰明的遊戲大蝦,基本上不會在本身的主循環裏搞什麼運算工做,絕對是先計算好了,再到循環裏查表。若是表很大,很差寫,就寫一個init函數,在循環外臨時生成表格。

2)求餘運算  

  a=a%8;  能夠改成:  a=a&7;  

  說明:位操做只需一個指令週期便可完成,而大部分的C編譯器的「%」運算均是調用子程序來完 成,代碼長、執行速度慢。一般,只要求是求2n方的餘數,都可使用位操做的方法來代替。  

3)平方運算

 a=pow(a, 2.0);  能夠改成:  a=a*a;

 說明:在有內置硬件乘法器的單片機中(51系列),乘法運算比求平方運算快得多,由於浮點數的求平方是經過調用子程序來實現的,在自帶硬件乘法器的AVR單片機中,如ATMega163中,乘法運算只需2個時鐘週期就能夠完成。既使是在沒有內置硬件乘法器的AVR單片機中,乘法運算的子程序比平方運算的子程序代碼短,執行速度快。  

4)用移位實現乘除法運算  

a=a*4;  b=b/4;  能夠改成:  a=a<<2;  b=b>>2;  

一般若是須要乘以或除以2n,均可以用移位的方法代替。在ICCAVR中,若是乘以2n,均可以生成左移的代碼,而乘以其它的整數或除以任何數,均調用乘除法子程序。用移位的方法獲得代碼比調用乘除法子程序生成的代碼效率高。實際上,只要是乘以或除以一個整數,都可以用移位的方法獲得結果。  

5)避免沒必要要的整數除法

 整數除法是整數運算中最慢的,因此應該儘量避免。一種可能減小整數除法的地方是連除,這裏除法能夠由乘法代替。這個替換的反作用是有可能在算乘積時會溢出,因此只能在必定範圍的除法中使用。  

6)使用增量和減量操做符  

在使用到加一和減一操做時儘可能使用增量和減量操做符,由於增量符語句比賦值語句更快,緣由在於對大多數CPU來講,對內存字的增、減量操做沒必要明顯地使用取內存和寫內存的指令。顯然,不用取指令和存指令,增、減量操做執行的速度加快,同時長度也縮短了。  

7)使用複合賦值表達式  複合賦值表達式(a-=1a+=1)都可以生成高質量的程序代碼。  

8)提取公共的子表達式  

  在某些狀況下,C++編譯器不能從浮點表達式中提出公共的子表達式,由於這意味着至關於對錶達式從新排序。須要特別指出的是,編譯器在提取公共子表達式前不能按照代數的等價關係從新安排表達式。這時,程序員要手動地提出公共的子表達式。  

4、結構體成員的佈局  

不少編譯器有「使結構體字,雙字或四字對齊」的選項。可是,仍是須要改善結構體成員的對齊,有些編譯器可能分配給結構體成員空間的順序與他們聲明的不一樣。可是,有些編譯器並不提供這些功能,或者效果很差。因此,要在付出最少代價的狀況下實現最好的結構體和結構體成員對齊,建議採起下列方法:  

1)按數據類型的長度排序

 把結構體的成員按照它們的類型長度排序,聲明成員時把長的類型放在短的前面。編譯器要求把長型數據類型存放在偶數地址邊界。在申明一個複雜的數據類型 (既有多字節數據又有單字節數據) 時,應該首先存放多字節數據,而後再存放單字節數據,這樣能夠避免內存的空洞。編譯器自動地把結構的實例對齊在內存的偶數邊界。  

2)把結構體填充成最長類型長度的整倍數

 把結構體填充成最長類型長度的整倍數。照這樣,若是結構體的第一個成員對齊了,全部整個結構體天然也就對齊了。  

3)按數據類型的長度排序本地變量  

當編譯器分配給本地變量空間時,它們的順序和它們在源代碼中聲明的順序同樣,和上一條規則同樣,應該把長的變量放在短的變量前面。若是第一個變量對齊了,其它變量就會連續的存放,並且不用填充字節天然就會對齊。有些編譯器在分配變量時不會自動改變變量順序,有些編譯器不能產生4字節對齊的棧,因此4字節可能不對齊  

4)把頻繁使用的指針型參數拷貝到本地變量  

避免在函數中頻繁使用指針型參數指向的值。由於編譯器不知道指針之間是否存在衝突,因此指針型參數每每不能被編譯器優化。這樣數據不能被存放在寄存器中,並且明顯地佔用了內存帶寬。注意,不少編譯器有「假設不衝突」優化開關(在VC裏必須手動添加編譯器命令行/Oa/Ow),這容許編譯器假設兩個不一樣的指針老是有不一樣的內容,這樣就不用把指針型參數保存到本地變量。不然,請在函數一開始把指針指向的數據保存到本地變量。若是須要的話,在函數結束前拷貝回去。

 5、循環優化  

1)充分分解小的循環  

要充分利用CPU的指令緩存,就要充分分解小的循環。特別是當循環體自己很小的時候,分解循環能夠提升性能。注意:不少編譯器並不能自動分解循環。  

2)提取公共部分  

對於一些不須要循環變量參加運算的任務能夠把它們放到循環外面,這裏的任務包括表達式、函數的調用、指針運算、數組訪問等,應該將沒有必要執行屢次的操做所有集合在一塊兒,放到一個init的初始化程序中進行。  (3)延時函數

 一般使用的延時函數均採用自加的形式:  

void delay(void)  

{  unsigned int i;

 for (i=0;i<1000;i++) ;

 }  

將其改成自減延時函數:  

void delay (void)

 {  unsigned int i;

 for (i=1000;i>0;i--) ;  }

 兩個函數的延時效果類似,但幾乎全部的C編譯對後一種函數生成的代碼均比前一種代碼少1~3個字節,由於幾乎全部的MCU均有爲0轉移的指令,採用後一種方式可以生成這類指令。在使用while循環時也同樣,使用自減指令控制循環會比使用自加指令控制循環生成的代碼更少1~3個字母。可是在循環中有經過循環變量「i」讀寫數組的指令時,使用預減循環有可能使數組超界,要引發注意。

 (4while循環和dowhile循環  

在這兩種循環中,使用dowhile循環編譯後生成的代碼的長度短於while循環。  

5)循環展開  

這是經典的速度優化,但許多編譯程序(gcc -funroll-loops)能自動完成這個事,因此如今你本身來優化這個顯得效果不明顯。  

6)循環嵌套  

把相關循環放到一個循環裏,也會加快速度。

7Switch語句中根據發生頻率來進行case排序

 Switch 可能轉化成多種不一樣算法的代碼。其中最多見的是跳轉表和比較鏈/樹。當switch用比較鏈的方式轉化時,編譯器會產生if-else-if的嵌套代碼,並按照順序進行比較,匹配時就跳轉到知足條件的語句執行。因此能夠對case的值依照發生的可能性進行排序,把最有可能的放在第一位,這樣能夠提升性能。此外,在case中推薦使用小的連續的整數,由於在這種狀況下,全部的編譯器均可以把switch 轉化成跳轉表。  

8)將大的switch語句轉爲嵌套switch語句  

switch語句中的case標號不少時,爲了減小比較的次數,明智的作法是把大switch語句轉爲嵌套switch語句。把發生頻率高的case 標號放在一個switch語句中,而且是嵌套switch語句的最外層,發生相對頻率相對低的case標號放在另外一個switch語句中。

9)循環轉置  

有些機器對JNZ(0轉移)有特別的指令處理,速度很是快,若是你的循環對方向不敏感,能夠由大向小循環。不過千萬注意,若是指針操做使用了i值,這種方法可能引發指針越界的嚴重錯誤(i = MAX+1;)。固然你能夠經過對i作加減運算來糾正,可是這樣就起不到加速的做用。  

10)公用代碼塊  

一些公用處理模塊,爲了知足各類不一樣的調用須要,每每在內部採用了大量的if-then-else結構,這樣很很差,判斷語句若是太複雜,會消耗大量的時間的,應該儘可能減小公用代碼塊的使用。(任何狀況下,空間優化和時間優化都是對立的--東樓)。固然,若是僅僅是一個(3==x)之類的簡單判斷,適當使用一下,也仍是容許的。記住,優化永遠是追求一種平衡,而不是走極端。  

11)提高循環的性能

 要提高循環的性能,減小多餘的常量計算很是有用(好比,不隨循環變化的計算)。  若是已經知道if()的值,這樣能夠避免重複計算。雖然很差的代碼中的分支能夠簡單地預測,可是因爲推薦的代碼在進入循環前分支已經肯定,就能夠減小對分支預測的依賴。  

12)選擇好的無限循環  

在編程中,咱們經常須要用到無限循環,經常使用的兩種方法是while (1) for (;;)。這兩種方法效果徹底同樣,但那一種更好呢?編譯後,for (;;)指令少,不佔用寄存器,並且沒有判斷、跳轉,比while (1)好。

6、提升CPU的並行性  

1)使用並行代碼  

儘量把長的有依賴的代碼鏈分解成幾個能夠在流水線執行單元中並行執行的沒有依賴的代碼鏈。不少高級語言,包括C++,並不對產生的浮點表達式從新排序,由於那是一個至關複雜的過程。須要注意的是,重排序的代碼和原來的代碼在代碼上一致並不等價於計算結果一致,由於浮點操做缺少精確度。在一些狀況下,這些優化可能致使意料以外的結果。幸運的是,在大部分狀況下,最後結果可能只有最不重要的位(即最低位)是錯誤的。  

2)避免沒有必要的讀寫依賴  

當數據保存到內存時存在讀寫依賴,即數據必須在正確寫入後才能再次讀取。雖然AMD AthlonCPU有加速讀寫依賴延遲的硬件,容許在要保存的數據被寫入內存前讀取出來,可是,若是避免了讀寫依賴並把數據保存在內部寄存器中,速度會更快。在一段很長的又互相依賴的代碼鏈中,避免讀寫依賴顯得尤爲重要。若是讀寫依賴發生在操做數組時,許多編譯器不能自動優化代碼以免讀寫依賴。因此推薦程序員手動去消除讀寫依賴,舉例來講,引進一個能夠保存在寄存器中的臨時變量。這樣能夠有很大的性能提高。

 7、循環不變計算  

對於一些不須要循環變量參加運算的計算任務能夠把它們放到循環外面,如今許多編譯器仍是能本身幹這件事,不過對於中間使用了變量的算式它們就不敢動了,因此不少狀況下你還得本身幹。對於那些在循環中調用的函數,凡是不必執行屢次的操做統統提出來,放到一個init函數裏,循環前調用。另外儘可能減小餵食次數,不必的話儘可能不給它傳參,須要循環變量的話讓它本身創建一個靜態循環變量本身累加,速度會快一點。  還有就是結構體訪問,東樓的經驗,凡是在循環裏對一個結構體的兩個以上的元素執行了訪問,就有必要創建中間變量了(結構這樣,那C++的對象呢?想一想看)。  

8、函數優化  

1Inline函數  在C++中,關鍵字Inline能夠被加入到任何函數的聲明中。這個關鍵字請求編譯器用函數內部的代碼替換全部對於指出的函數的調用。這樣作在兩個方面快於函數調用:第一,省去了調用指令須要的執行時間;第二,省去了傳遞變元和傳遞過程須要的時間。可是使用這種方法在優化程序速度的同時,程序長度變大了,所以須要更多的ROM。使用這種優化在Inline函數頻繁調用而且只包含幾行代碼的時候是最有效的。  (2)不定義不使用的返回值

 函數定義並不知道函數返回值是否被使用,假如返回值歷來不會被用到,應該使用void來明確聲明函數不返回任何值。  

3)減小函數調用參數  

使用全局變量比函數傳遞參數更加有效率。這樣作去除了函數調用參數入棧和函數完成後參數出棧所須要的時間。然而決定使用全局變量會影響程序的模塊化和重入,故要慎重使用。  

4)全部函數都應該有原型定義  

通常來講,全部函數都應該有原型定義。原型定義能夠傳達給編譯器更多的可能用於優化的信息。  

5)儘量使用常量(const)  儘量使用常量(const)C++ 標準規定,若是一個const聲明的對象的地址不被獲取,容許編譯器不對它分配儲存空間。這樣可使代碼更有效率,並且能夠生成更好的代碼。  

(6)把本地函數聲明爲靜態的(static)  若是一個函數只在實現它的文件中被使用,把它聲明爲靜態的(static)以強制使用內部鏈接。不然,默認的狀況下會把函數定義爲外部鏈接。這樣可能會影響某些編譯器的優化——好比,自動內聯。

 9、採用遞歸  

LISP 之類的語言不一樣,C語言一開始就病態地喜歡用重複代碼循環,許多C程序員都是除非算法要求,堅定不用遞歸。事實上,C編譯器們對優化遞歸調用一點都不反感,相反,它們還很喜歡幹這件事。只有在遞歸函數須要傳遞大量參數,可能形成瓶頸的時候,才應該使用循環代碼,其餘時候,仍是用遞歸好些。

 10、變量  

1register變量  

在聲明局部變量的時候可使用register關鍵字。這就使得編譯器把變量放入一個多用途的寄存器中,而不是在堆棧中,合理使用這種方法能夠提升執行速度。函數調用越是頻繁,越是可能提升代碼的速度。  在最內層循環避免使用全局變量和靜態變量,除非你能肯定它在循環週期中不會動態變化,大多數編譯器優化變量都只有一個辦法,就是將他們置成寄存器變量,而對於動態變量,它們乾脆放棄對整個表達式的優化。儘可能避免把一個變量地址傳遞給另外一個函數,雖然這個還很經常使用。C語言的編譯器們老是先假定每個函數的變量都是內部變量,這是由它的機制決定的,在這種狀況下,它們的優化完成得最好。可是,一旦一個變量有可能被別的函數改變,這幫兄弟就不再敢把變量放到寄存器裏了,嚴重影響速度。看例子:  a = b();  c(&d);  由於d 的地址被c函數使用,有可能被改變,編譯器不敢把它長時間的放在寄存器裏,一旦運行到c(&d),編譯器就把它放回內存,若是在循環裏,會形成N 次頻繁的在內存和寄存器之間讀寫d的動做,衆所周知,CPU在系統總線上的讀寫速度慢得很。好比你的賽楊300CPU主頻300,總線速度最多66M,爲了一個總線讀,CPU可能要等4-5個週期,得。。得。。得。。想起來都打顫。  

2)同時聲明多個變量優於單獨聲明變量  

3)短變量名優於長變量名,應儘可能使變量名短一點

4)在循環開始前聲明變量  

11、使用嵌套的if結構  在if結構中若是要判斷的並列條件較多,最好將它們拆分紅多個if結構,而後嵌套在一塊兒,這樣能夠避免無謂的判斷。  

轉載自曉的blog

相關文章
相關標籤/搜索