C語言程序優化方法

C語言程序優化方法


轉載請註明來源:http://blog.csdn.net/letian0805/article/details/17191797

        作爲一個忠實的C語言程序員,經常要因爲各種需要優化程序,比如:內存限制、CPU限制、網絡限制、磁盤空間限制等。最近在優化公司的程序,順便將一些優化心得總結出來和大家分享。

一、GCC自帶的優化選項

        gcc 作爲一款著名的C編譯器,自帶了一些優化選項。在gcc參數里加上-On (n爲0、1、2、3、s)就可以開啓gcc的優化選項。

        1、-O0

        -O0是gcc的默認選項,意思是不做任何優化。這個選項一般用不到,只有一種情況下:我們不希望gcc做任何優化的時候(比如我們的程序用於調試,需要保留調試信息),加上該選項關閉gcc的優化。

      2、-O1

        -O1允許gcc從運行效率和程序大小上做少量優化,不進行比較耗編譯時間的優化。當程序源碼比較多需要用很長時間來編譯時,如果程序對優化要求不高,可以用該選項。該選項不會展開循環和內聯函數。

      3、-O2

        -O2允許gcc進行比-O1選項更深一步的優化,進一步減少程序運行效率,但會增加編譯時間。對程序運行效率以及大小都有嚴格要求的可以使用該選項。該選項是推薦選項。

      4、-O3

        -O3選項會打開所有-O2支持的選項,但-O3更注重優化運行效率,所以該選項會打開-finline-functions,-funswitch-loops以及-fgcse-after-reload 選項,將內聯函數和循環展開,程序大小會有所增加。但該選項可能會因爲循環的展開而導致一些問題,並不推薦對所有程序使用該選項。

測試結果:https://wiki.edubuntu.org/GccOptimizationTests

     5、-Os

        -Os選項是在-O2的基礎上對程序大小的進一步優化,主要是關閉了-O2中的數據對齊。按理說速度會比-O2慢,但某些情況下速度卻比使用-O2快(內存讀寫速度成爲瓶頸時),比如:程序涉及到大量的內存讀寫,內存對齊後會增加數據量導致運行時間增加。所以,當數據量比較大時,推薦使用該選項。該選項除了取消數據對齊外,還會將部分函數合併爲一個函數(比如該函數是個static函數而且只被調用了一次或者函數功能非常簡單),會去掉沒有被調用到的函數,甚至會去掉一些即使被調用了但基本沒啥用的函數。通過對編譯結果反彙編我們可以看到區別。

二、register關鍵字

        register關鍵字的作用是定義一個寄存器變量,對該變量的操作實際上是直接操作了某個寄存器。要注意的是需要有足夠的寄存器,如果寄存器不夠用了,或者被修飾的變量所佔比特數大於CPU的位數時,register關鍵字將失效。那麼register關鍵字將不起作用。register關鍵字一般用於修飾局部變量,因爲局部變量通常要經過壓棧的操作,每次對局部變量的操作都涉及到對內存的操作。使用register關鍵字後,省去了壓棧的操作,節省了操作內存的時間。當函數需要頻繁操作內存時,使用register關鍵字將會節省大量的時間。在優化公司的程序的過程中,通過測試發現使用register關鍵字可以將一個函數的效率提高至少4倍。

三、內聯函數(inline 關鍵字)

        普通函數在函數調用時會設計到參數、函數地址的壓棧、出棧操作,入棧、出棧操作就是對內存的操作。如果能減少對內存的操作,程序的效率會有所提升。減少壓棧、出棧操作可以通過使用宏或者內聯函數,但有時候我們需要將函數的參數限定爲某一類型,所以我們會需要檢測參數的類型,而宏有時候會有副作用(典型的例子是MAX宏的參數裏使用了i++之類的),所以這個時候宏就不大適合了,那麼我們就需要用內聯函數。在static(如果有的話)和函數定義之間加入inline關鍵字則是定義了一個內聯函數,內聯函數需要配合gcc編譯器的-finline-functions選項,否則編譯器不會對內聯函數進行展開。

四、減少條件判斷帶來的開銷

        CPU會對條件判斷進行預測,如果預測失敗,則會帶來額外的開銷。對於條件與,我們要將判假概率高的條件放在前面;對於條件或,我們要將判真概率高的條件放前面。另外,我們要將執行概率高的分支放到第一個分支的位置。在做這些之前,我們需要對我們寫的程序有充分的瞭解,能夠人爲預測到大概的概率,也可以通過多次的測試來發現程序的運行規律。另外,如果知道條件是取固定的幾個值,最好是選用switch...case而不是if...else。在循環中,最好是將循環因子和 0 比較。

五、手動展開循環

        循環中對循環因子的操作(自增、自減、比較)都需要花費一定的時間,減少循環次數可以提高程序的運行效率。當我們害怕編譯器的自動展開帶來的副作用時,我們可以手動展開循環。循環的展開一般適用於循環體比較簡單的情況,若循環體本身就比較複雜,展開會降低代碼的可讀性。


六、減少內存的分配-釋放次數

       動態內存的分配開銷比較大,特別是內存碎片比較多的時候。因爲動態分配內存時系統需要遍歷內存鏈表,如果分配的次數比較多,會浪費很多時間。假如程序中分配內存的次數比較多,而且內存大小相差不大,可以將用完的內存用自己實現的棧管理起來,下次分配的時候直接從棧裏獲取內存地址。也就是說:釋放內存的時候不馬上釋放,而是將內存地址壓入棧中,如果超過棧空間,則釋放內存;下次分配內存的時候直接從棧裏獲取內存地址。


七、更細粒度的加鎖

      在多線程編程時,難免會需要對某些資源的操作進行加鎖。加鎖時要注意這幾點:1)不要在加鎖後進行睡眠、阻塞等操作。2)不要對整個循環體加鎖。3)當鎖的時間比較長,使用互斥鎖,鎖的時間比較短時用自旋鎖。合理使用鎖,可以提高多線程效率,反之無法體現多線程的優點。



(未完待續)






發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章