核心原則:權衡速度、空間與可讀性
優化目標:在嚴格內存限制(嵌入式/移動設備)下最大化速度。優化常增加代碼量,需謹慎選擇熱點路徑。
1. 數據類型優化
- 優先用無符號整型:非負場景用
unsigned int,部分CPU無符號運算更快。
- 寄存器關鍵字:
register unsigned int var;提示編譯器優先分配寄存器(現代編譯器多自動優化)。
- 避免浮點運算:整型運算直接由CPU完成,浮點需协處理器/庫,慢數十倍。延後轉換,定點運算替代。
2. 除法與取模優化
- 除法極慢(20-140周期)。能用乘法/移位替代除法:
a/b > c → a > c*b(b>0且無溢出)。
- 除數為2的冪次:編譯器自動優化為移位。無符號移位更快(邏輯右移),有符號需算術右移修正負數截斷差異。
- 合併除法與取模:
a/b + a%b編譯器可合併為單次除法指令。
- 取模替換為條件判斷:
x = (x+1)%60 → if (++x >= 60) x=0;(已知範圍內更快)。
3. 變量與內存訪問優化
- 局部變量優於全局:全局變量不分配寄存器,須間接訪問。熱點循環中用局部變量緩存全局值,循環結束寫回。
- 避免指針別名:編譯器不知指針是否指向同一地址,不敢優化。用
restrict(C99)或局部變量緩存*ptr消除別名。
- 指針鏈緩存:
p->pos->x多次訪問 → Point3 *pos = p->pos; pos->x=...。
- 結構體傳指針:避免大結構體壓棧拷貝,傳
const Struct*。唯讀加const助編譯器優化。
4. 控制流優化
- 簡化條件表達式:將關系運算分組,便於條件執行指令(如ARM的條件執行)。
- 範圍檢查優化:
(x>=min && x → (unsigned)(x-min) < (max-min)(單次比較)。
- 利用零標誌位:
if (x+y < 0)後緊跟ADD指令可省CMP,編譯器自動優化。
- 惰性求值:
if (a>10 && b=4)將最可能為false的條件前置。
- switch優於if-else鏈:跳轉表O(1) vs 線性判斷。案例值稠密時用查找表更快(如字符串映射)。
- 二分決策樹:有序條件構建平衡判斷樹,最壞比較次數log₂N。
5. 循環優化
- 倒序循環:
for(i=n; i--;) 或 for(i=n; i!=0; --i),終止條件測試i!=0比i快(隱含零標誌位)。
- 循環融合:合併相同範圍的多個循環,減少循環開銷,提高指令緩存命中。
- 函數內聯/循環內聯:將小函數調用移入循環體,或將循環移入函數,消除調用開銷。
- 循環展開:手動展開小循環(固定次數)或分塊展開(動態次數,如每8次一塊+switch處理餘數),減少分支預測失敗與計數器更新。
- 提前退出:搜索類循環找到目標即
break,避免無效遍歷。
6. 函數設計優化
- 函數短小精悍:利於寄存器分配與編譯器優化。
- 參數≤4個:ARM等架構前4參數用寄存器傳遞,超過部分壓棧(雙倍內存訪問)。
- 葉子函數:不調用其他函數,無需保存鏈接寄存器LR,極高效。熱點函數設計為葉子或
__inline。
- 內聯函數:
__inline消除調用開銷,參數為常量時可進一步常量傳播優化。慎用於大函數/高頻調用(代碼膨脹)。
7. 查找表替代計算
- sin/cos、偽隨機數、位計數等可預計算為表。精度換速度,嵌入式常用。
- 合併相近運算為單表,減少存儲與訪問開銷。
8. 浮點運算優化
- 除法慢於乘法2倍:
x/3.0 → x*(1.0/3.0)(倒數編譯期計算)。
- 優先用
float而非double(精度足夠時),佔用更少寄存器/內存,運算更快。
- 避免超越函數(sin/log等),由多項式逼近實現,極慢。查找表或CORDIC算法替代。
- 手動簡化浮點表達式:編譯器不敢重排浮點運算(精度/結合律差異),如
3*(x/3) ≠ x。
- 定點運算替代:數值範圍小時,整數模擬小數,精度更高速度更快。
9. 綜合技巧清單
- 以空間換時間:緩存熱點數據(sin表、隨機數池)。
- 少用全局變量,文件內部變量加
static。
- 避免遞歸(調用開銷大),改迭代/顯式棧。
- 一維數組快於多維(連續內存、索引計算簡單)。
#define宏替代極小函數(無調用開銷),注意副作用與類型安全。
參考資源
- ARM: Writing Efficient C for ARM (DAI 0034A)
- GNU C優化指南、Richard's C Optimization Page
- Agner Fog's Optimization Manuals