inline 的誘惑與代價
inline 看似免費午餐:消除呼叫開銷、啟用編譯器上下文相關優化 (常數摺疊、死碼消除、標量替換等)。但代價是**程式碼膨脹** → 指令快取未命中、分頁錯誤增加、編譯時間拉長。
何時 inline 為正收益?
- 函數體極小 (1-3 行):產生機器碼比
call/ret 序列更小,指令快取命中率反而提升。
- 熱點路徑小函數:Profile-guided optimization (PGO) 可精準識別。
- 模板/標頭檔函數:編譯期可見,編譯器自動判斷。
何時應避免 inline?
- 函數體超過 10-15 行 (經驗值)。
- 遞迴函數 (編譯器通常拒絕)。
- 虛函數 (動態派發與內聯互斥,除非 devirtualization 成功)。
- 透過函數指標呼叫 (無法內聯)。
- 僅在單一編譯單元使用的非熱點函數 (用
static 內部連結更佳)。
隱含與顯式 inline
// 隱含:類別內定義
class Foo {
int get() const { return val; } // 隱含 inline
};
// 顯式:關鍵字標記
inline int max(int a, int b) { return a > b ? a : b; }
// 模板通常置於標頭檔,常配合顯式 inline
template
inline const T& max(const T& a, const T& b) { return a < b ? b : a; }
迷思破除:模板**不必須**是 inline。若模板實例化產生大型函數 (如複雜演算法),**勿加 inline**,讓編譯器決定或顯式實例化至 .cpp 減少程式碼膨脹。
進階:連結期與執行期內聯 (LTO / JIT)
- LTO (Link Time Optimization):
-flto (GCC/Clang) / /LTCG (MSVC) 允許跨編譯單元內聯。
- JIT (CLR/.NET, JVM, V8):執行期根據熱度動態內聯,甚至投機內聯虛函數 (配合去優化機制)。
實戰檢核表
- Profile 先行:找出真正熱點 (Callgrind, VTune, perf)。
- 小函數隱含 inline (類別內定義) 或顯式
inline。
- 大型函數移至 .cpp,**不加 inline**,依賴 LTO。
- 模板函數:預設標頭檔實作,**不加額外 inline**,除非極簡單。
- 定期檢視二進位大小與指令快取行為 (perf stat -e icache.misses)。