標籤:程式員 編譯器 作業系統
第五部分:代碼改善
25 代碼調整策略
70年代,程式員意識到過分專註效能會損失程式的可讀性和可維護性。
對普通使用者來說,程式員按時交付軟體、一個清爽的介面、避免死機,常常比效能更重要。
最佳化效率前先考慮幾個問題:
1、程式需求(客戶的需求要求太高,其實根本沒有必要這麼高)
2、程式的設計(當前的設計很難做良好的最佳化工作)
3、具體設計(比如採用快速排序還是冒泡排序)
4、同作業系統的互動(影響你的效率,有時候你自己也沒有意思到有系統調用,比如編譯器會產生系統調用,或者某個庫使用了)
5、代碼編譯(好的編譯器都會替你最佳化)
6、硬體
7、代碼調整(本章主題,細節代碼)
“不足4%的部分常常佔用了超過50%的已耗用時間”
1、“程式碼數多,並不代表運行速度慢”
for(int i=0;i<10;i++)
a[i]=i;
與
a[0]=1;a[2]=2;……a[9]=9;
相比,明顯是後面一個更快。
2、“最佳化要考慮可移植性,可能一種方法放到另外一種語言,甚至同一語言的另外一種編譯器,都可能產生反作用。”
3、“很難找到那4%的部分,程式員做的最佳化往往帶來的效果都微不足道。”
4、“對那4%的最佳化,可能顧此失彼,反而導致整體效能的下降。”
5、“速度的最佳化會帶來可讀性、封裝性、正確性的破壞,帶來的好處並不值得。”
6、“運行速度的要求現在越來越低與正確性等方面。”
7、“程式員應該使用高品質的設計,把代碼寫正確,使之模組化並易於修改,將讓後期的維護工作變得很容易。”
8、“編譯器最佳化開啟會提供效能”
c++編譯器開啟最佳化基本會提高大約50%的效能。可以參考這個例子:
http://topic.csdn.net/u/20091113/14/9d793a37-9953-43be-940f-26e4b64d8a6b.html
最佳化的很激進。
9、“系統影響”
(1)訪問記憶體比訪問網路和磁碟快的百倍。
(2)系統調用
(3)記憶體資料分配不當,導致記憶體分頁也會影響效能:
for(int column=0;column<100;column++)
for(int row=0;row<1000000;row++)
table[row][column] = new XXX;
因為一般讀取資料,都是讀一行,然後取每一列,這種分配方案會導致在讀取時都會切換記憶體頁(缺頁中斷),影響效能。
所以應該是:
for(int row=0;row<1000000;row++)
for(int column=0;column<100;column++)
table[row][column] = new XXX;
不過這樣會導致局部運行變慢,具體見下章。
10、c++函數的參數越多,調用速度越慢。【應該都是說基礎資料型別 (Elementary Data Type),不知道結構體怎麼樣】
很神奇java的函數參數數量不影響運行速度,應該都是做了最佳化的。
多態函數調用速度最慢。
11、沒必要把數組的移動變成多維指標來操作,因為編譯器基本都替我們最佳化了,如果我們再做,會導致可讀性下降。
26 代碼調整技術
“減少佔用資源主要是通過調整類和資料結構,而非代碼調整。”
1、case語句、if=else先判斷常見的。
2、用查表法代替複雜邏輯。
3、算出的結構可以放在緩衝中下次用。
4、把迴圈中的判斷外提
for(int i=0;i<count;i++)
{
if(type=A)
xxxx
else
xxx
}
變成
if(type=A)
for(int i=0;i<count;i++)
xxx
else
for(int i=0;i<count;i++)
xxx
但是這樣就出現了重複代碼,對維護代碼是個問題。
5、展開
for(int i=0;i<count-2;i++)
a[i]=i;
變成
for(int i=0;i<count;i++)
{
a[i]=i;
a[i]=++i;
a[i]=++i;
}
xxxx//處理額外資料
基本可以當成是不良編程,不過如果是很簡單的處理,可以提供34%的效率。
【感覺意義不大,如果是迴圈內部處理很耗時的話,這種提高就被沖淡了】
6、減少迴圈內部的處理
for(int i=0;i<count;i++)
a[i]=b[i]*p->p->p;
變成
pr=p->p->p;
for(int i=0;i<count;i++)
a[i]=b[i]*pr;
7、哨兵值【這個感覺比較變態,第一次見這麼詭異的方法】
for(int i=0;i<count;i++)
if(item[i]==type)
{bFind=true;break;}
改成
item[count]=type;
while(item[i]!=type)
i++;
if(i<count)
xxxx
太詭異了,等於少了一個判斷條件
8、把最忙的迴圈放在內層
觀察上章9,如果不考慮分配記憶體,那麼第一個的迴圈總算是1000000*101次,而第二個是1000001*100,少了999900次迴圈。
但是雖然局部最佳化了,整體下降了。
如果不是分配記憶體而是簡單處理,倒是不錯的。
9、用加法替代乘法
10、迴圈下標不用浮點型而用整型【效率提高3.5倍,不過正常人應該都不會用浮點型做迴圈迴圈下標吧】
11、用一維數組代替多維陣列,沒有什麼效率提高,編譯器已經最佳化了。
12、把常用計算結果變成常量。
比如int log2(int x){return log(x)/log(2);}
把log(2)變成常量使用。
13、小心系統函數
對於不需要精度的函數,直接自己寫。
14、使用正確的常量類型
當賦值變數時,給於的值需要轉換,那麼會消耗時間,因為要做強制轉換。【有空編碼試試】
15、預先算出結果,直接查表
16、把子程式變成內嵌函式
17、用低級語言重寫代碼【改成彙編難度挺大的,尤其是比較複雜的程式,用彙編重寫太難了- - 估計寫幾個演算法還可以,不過不清楚怎麼調用彙編的程式,難道當成是一個獨立程式調用返回結果嗎?還是編譯器可以把彙編出來的東西當做程式一部分調用?tc有個small模式可以調用彙編代碼.ASM檔案】
有空試試這樣編碼
http://zhidao.baidu.com/question/31397560.html
試了下linux版的卡死不動了,什麼都沒列印出來。
linux上面的彙編蠻難理解的,跟以前上學學的都不一樣。
g++ -S可以產生彙編代碼。
拼字上多了一些字元。一些指令後面多了個L,源運算元與目的運算元位置對調了,常數前面加了$,寄存器前面加了%
http://tieba.baidu.com/f?kz=310375556
http://wenku.baidu.com/view/e8752e7f5acfa1c7aa00cc39.html
有空可以看看。
也了一個簡單的例子:
int a = 1;
int b = 2;
int main()
{
__asm__
(
"movl a,%ecx \n"
"xchg b,%ecx \n"
"movl %ecx,a \n"
);
return 0;
}
變數必須用全部定義,用static都不行。
不過一般現在在代碼裡面內嵌彙編意義不大,看這篇
http://topic.csdn.net/u/20100413/20/87aa7378-cd9f-44ad-90ae-4201a3680b71.html
本文出自 “飛翔正義的部落格” 部落格,請務必保留此出處http://xzq2000.blog.51cto.com/2487359/1767393
《代碼大全2》學習筆記4