嵌入式實時系統面臨嚴格的時間(Deadline)與空間(ROM/RAM)雙重約束。現代編譯器(GCC/Clang/IAR/Keil)提供 -O2/-Os 等優化等級,但「又快又小」往往不可兼得。本文整理十大手動優化技巧,並分析其對代碼大小、執行速度、可讀性、可維護性的權衡,助工程師在專案中精準取捨。
代碼膨脹 → 指令快取命中率下降、ROM 擴大。慎用於大型或低頻函式。
// C99
static inline uint32_t crc32_step(uint32_t crc, uint8_t data) { ... }
// C++ / GNU C
__attribute__((always_inline)) void critical_isr_handler(void) { ... }
// C++ 關鍵字
inline void fast_io_write(uint8_t val) { ... }
建議:配合 __builtin_constant_p 僅在引數為常數時內聯,否則保留函式呼叫。
陣列索引 a[i] 編譯為 base + i*sizeof(T) 乘法/加法;指標 *p++ 僅需遞增暫存器。多維陣列差異更明顯(需計算行跨距)。
/* 陣列索引 */
for (int i = 0; i < N; ++i) sum += a[i];
/* 指標版 */
uint8_t *p = a, *end = a + N;
do { sum += *p++; } while (p != end);
end 一過去末端)為 UB,迴圈條件務必精確。函式若從不被使用返回值,宣告為 void,編譯器可省略返回值暫存器操作、呼叫端清理代碼。若為庫函式需保持 ABI 相容,可用 __attribute__((warn_unused_result)) 強制呼叫端處理。
CLZ(前導零計數)、DSB/ISB(記憶體屏障)、WFI/WFE(低功耗等待)。NOP 填充、位元反轉、飽和運算。static inline uint32_t rev_bits(uint32_t x) {
uint32_t r;
__asm__("rbit %0, %1" : "=r"(r) : "r"(x));
return r;
}
MSVC 使用 __asm 區塊或內建函式 _byteswap_ulong 等。
register uint32_t counter asm("r12"); // GCC 指定暫存器
register int fast_var; // 建議編譯器配置暫存器
現代編譯器暫存器分配器極其優秀,手動 register 多為干擾。僅在極少數關鍵變數(如 ISR 共享旗標、軟體管腳模擬)經測試確認有效時使用。
/* 較慢:讀-改-寫三指令 */
x = x + 1;
/* 較快:單指令 INC/DEC */
++x; // 或 x++(若不需舊值)
對於無最佳化編譯器(-O0)差異明顯;-O2 通常自動轉換。嵌入式常用 -Os,保留此習慣無害。
| 指標 | 參數傳遞 | 全域/靜態變數 |
|---|---|---|
| 速度 | 壓棧/彈棧開銷 | 零參數開銷,直接存取 |
| ROM | 呼叫端準備參數代碼 | 較小 |
| 可重入性 | 天然支援 | 需額外鎖/副本機制 |
| 模組化 | 介面清晰 | 隱性耦合,難測試 |
| 並行安全 | 無共享狀態 | 需原子操作/鎖 |
建議:僅用於極高頻、單執行緒、無遞迴的葉子函式(如 PID 控制核心)。
switch 案例頻率排序/* 高頻在前,平均比較次數最小化 */
switch (msg_id) {
case HEARTBEAT: handle_heartbeat(); break; // 90%
case SENSOR_DATA: handle_sensor(); break; // 8%
case CONFIG_UPDATE: handle_config(); break; // 1.5%
case FIRMWARE_UPGRADE: handle_fw(); break; // 0.5%
default: handle_unknown(); break;
}
編譯器通常生成跳躍表或二分搜尋樹,但對於稀疏 case 值仍退化為 if-else 鏈,排序可減少平均分支誤測。
switch 拆分為巢狀/函式指標表switch (msg_type) {
case FREQ_GROUP:
switch (msg_subtype) { ... } break;
case RARE_GROUP:
switch (msg_subtype) { ... } break;
}
typedef void (*Handler)(void);
Handler table[MAX_MSG_ID] = {
[MSG_HEARTBEAT] = handle_heartbeat,
[MSG_SENSOR] = handle_sensor,
// ...
};
if (id < MAX_MSG_ID && table[id]) table[id]();
優點:常數時間分派、易動態註冊、程式碼局部性佳。缺點:RAM 佔用指標表、間接呼叫可能阻礙內聯。
| 特性 | ROM 影響 | 速度影響 | 建議 |
|---|---|---|---|
| 類/封裝/繼承 | 無 | 無 | 放心使用 |
| 虛擬函式 | +vtable | 間接呼叫 | 關鍵路徑避免,或用 CRTP 靜態多型 |
| 多重繼承/虛擬基類 | +vbase offset | 多次間接 | 嚴禁 |
| 模板 | 程式碼膨脹 | 可內聯加速 | 僅用於極小通用演算法 |
| 異常處理 | +EH table, 解構器 | 零開銷(非拋出路徑)但增加 ROM | 嵌入式通常 -fno-exceptions 禁用 |
| RTTI | +type_info | dynamic_cast 慢 | 禁用 -fno-rtti |
-Os -ffunction-sections -fdata-sections -Wl,--gc-sections。perf、gprof、硬體 PMU、IAR C-SPY、Keil Event Recorder。嵌入式優化無銀彈,核心在於 「測量 → 定位 → 微調 → 驗證」 閉環。十大技巧各有適用邊界,盲目套用反而增加技術債。建議團隊建立《優化決策記錄》,每次手動優化必須附帶:前後二進位大小、週期數、測試向量、風險評估,讓優化可追溯、可審計、可回滾。