來自:codingwu-部落格園
作者:archimedes
出處:http://www.cnblogs.com/archimedes/
本篇文章翻譯自:http://www.codeproject.com/Articles/6154/Writing-Efficient-C-and-C-Code-Optimization
其中參考了文章:http://www.cppblog.com/xlander/archive/2006/07/21/10289.html
但文章中的代碼格式沒有排版,不方便查看,而且有部分翻譯錯誤以及其他錯誤,這篇文章除了參考原文和譯文,也加入了自己的一些理解和代碼,雖然是一篇2006年的文章,但是其中的一些技巧還是挺值得學習的,特重新整理出來與大家分享。
雖然對於最佳化C代碼有很多有效指導方針,但是對於徹底地瞭解編譯器和你工作的機器依然無法取代,通常,加快程式的速度也會加大代碼量。這些增加的代碼也會影響一個程式的複雜度和可讀性,這是不可接受的,比如你在一些小型的裝置上編程,例如:行動裝置、PDA……,這些有著嚴格的記憶體限制,於是,在最佳化的座右銘是:寫代碼在記憶體和速度都應該最佳化。
整型數 / Integers
在我們知道使用的數不可能是負數的時候,應該使用unsigned int取代int,一些處理器處理整數算數運算的時候unsigned int比int快,於是,在一個緊緻的迴圈裡面定義一個整型變數,最好這樣寫代碼:
register unsigned int variable_name;
然而,我們不能保證編譯器會注意到那個register關鍵字,也有可能,對某種處理器來說,有沒有unsigned是一樣的。這兩個關鍵字並不是可以在所有的編譯器中應用。記住,整形數運算要比浮點數運算快得多,因為處理器可以直接進行整型數運算,浮點數運算需要依賴於外部的浮點數處理器或者浮點數數學庫。我們處理小數的時候要精確點些(比如我們在做一個簡單的統計程式時),要限制結果不能超過100,要儘可能晚的把它轉化成浮點數。
除法和餘數 / Division and Remainder
在標準的處理器中,根據分子和分母的不同,一個32位的除法需要20-140個刻度來執行完成,等於一個固定的時間加上每個位被除的時間。
Time (分子/ 分母) = C0 + C1* log2 (分子/分母)
= C0 + C1 * (log2 (分子) - log2 (分母)).
現在的ARM處理器需要消耗20+4.3N個刻度,這是一個非常費時的操作,要儘可能的避免。在有些情況下,除法運算式可以用乘法表達是來重寫。比方說,(a/b)>c可以寫成a>(c*b),條件是我們已經知道b為非負數而且b*c不會超過整型數的取值範圍。如果我們能夠確定其中的一個運算元為unsigned,那麼使用無符號除法將會更好,因為它要比有符號除法快得多。
合并除法運算和取餘運算 / Combining division and remainder
在一些情況下,除法運算和取餘運算都需要用到,在這種情況下,編譯器會將除法運算和取餘運算合并,因為除法運算總是同時返回商和餘數。如果兩個運算都要用到,我們可以將他們寫到一起
int func_div_and_mod (int a, int b) { return (a / b) + (a % b);}
除數是2的冪的除法和取餘 / Division and remainder by powers of two
如果除法運算中的除數是2的冪,我們對這個除法運算還可以進一步最佳化,編譯器會使用移位元運算來進行這種除法運算。所以,我們要儘可能調整比例為2的冪(比方說要用64而不用66)。如果是無符號數,它要比有符號的除法快得多。
typedef unsigned int uint;
uint div32u (uint a) {
return a / 32;
}
int div32s (int a) {
return a / 32;
}
這兩種除法都會避免調用除法函數,另外,無符號的除法要比有符號的除法使用更少的指令。有符號的除法要耗費更多的時間,因為這種除法是使最終結果趨向於零的,而移位則是趨向於負無窮。
模數運算的替換 / An alternative for modulo arithmetic
我們一般使用取餘運算進行模數,不過,有時候使用 if 語句來重寫也是可行的。考慮下面的兩個例子:
uint modulo_func1 (uint count)
{
return (++count % 60);
}
uint modulo_func2 (uint count)
{
if (++count >= 60)
count = 0;
return (count);
}
第二個例子要比第一個更可取,因為由它產生的代碼會更快,注意:這隻是在count取值範圍在0 – 59之間的時候才行。
但是我們可以使用如下的代碼(筆者補充)實現等價的功能:
uint modulo_func3 (uint count)
{
if (++count >= 60)
count %= 60;
return (count);
}
使用數組索引 / Using array indices
假設你要依據某個變數的值,設定另一個變數的取值為特定的字元,你可能會這樣做:
switch(queue) {
case 0 : letter = 'W';
break;
case 1 : letter = 'S';
break;
case 2 : letter = 'U';
break;
}
或者這樣:
if(queue == 0)
letter = 'W';
else if ( queue == 1 )
letter = 'S';
else
letter = 'U';
有一個簡潔且快速的方式是簡單的將變數的取值做成一個字串索引,例如:
static char *classes = "WSU";
letter = classes[queue];
全域變數 / Global variables
全域變數不會被分配在寄存器上,修改全域變數需要通過指標或者調用函數的方式間接進行。所以編譯器不會將全域變數儲存在寄存器中,那樣會帶來額外的、不必要的負擔和儲存空間。所以在比較關鍵的迴圈中,我們要不使用全域變數。
如果一個函數要頻繁的使用全域變數,我們可以使用局部變數,作為全域變數的拷貝,這樣就可以使用寄存器了。條件是本函數調用的任何子函數不使用這些全域變數。