書籍提出以模板遞歸在編譯期展開向量點積迴圈:
// 遞歸模板
template
struct DotProduct {
static T execute(const T* v1, const T* v2) {
return v1[0]*v2[0] + DotProduct::execute(v1+1, v2+1);
}
};
// 終止特化
template
struct DotProduct<1, T> {
static T execute(const T* v1, const T* v2) { return v1[0]*v2[0]; }
};
// 輔助函式
template
T dot_product(const T* v1, const T* v2) { return DotProduct::execute(v1, v2); }
// 使用
int v1[]={1,2,3}, v2[]={4,5,6};
int r = dot_product<3>(v1, v2); // 編譯期展開為 v1[0]*v2[0]+v1[1]*v2[1]+v1[2]*v2[2]
測試代碼:
int main() {
int v1[]={1,2,3}, v2[]={4,5,6};
int r1 = dot_product(3, v1, v2); // 迴圈版
int r2 = dot_product<3>(v1, v2); // 解迴圈版
cout << r1 << endl << r2 << endl;
}
反組譯顯示:前四行**完全無機器碼**,編譯器直接將 r1/r2 常數摺疊為 32 (0x20),cout 直接輸出常數。無法觀察實際運算指令。
clock() 填充陣列 + 函數指針防內聯
template
void __stdcall MyGenerate(OutIt first, OutIt last, const char* name) {
cout << "generating " << name << " with clock\n";
generate(first, last, clock); // clock() 不可預測
copy(first, last, ostream_iterator(cout, " "));
cout << endl;
}
void ForceCalcResult(int) {} // 空函式強制運算
void (*const ForceCalcResult_non_inline)(int) = ForceCalcResult; // 函數指針防內聯
int main() {
const int dim = 3;
int v1[dim], v2[dim];
MyGenerate(v1, v1+dim, "v1");
MyGenerate(v2, v2+dim, "v2");
int r1 = dot_product(dim, v1, v2);
ForceCalcResult_non_inline(r1);
MyGenerate(v1, v1+dim, "v1");
MyGenerate(v2, v2+dim, "v2");
int r2 = dot_product(v1, v2);
ForceCalcResult_non_inline(r2);
cout << r1 << endl << r2 << endl;
}
| 版本 | 指令特徵 | 寄存器使用 | 結論 |
|---|---|---|---|
| 模板解迴圈 | 3 次 imul + 2 次 add,無跳轉 | edi, edx, eax | 完全展開 |
| 普通迴圈 | 完全相同指令序列 | esi, ecx, eax | 編譯器**自動完全展開** |
**驚人發現**:MSVC 8.0 在 dim=3 為編譯期常數時,**普通迴圈版本同樣被完全展開**,兩者機器碼幾乎等價。
| dim | 普通迴圈 | 模板解迴圈 |
|---|---|---|
| 10 | 完全展開(10 imul + 9 add) | 完全展開 |
| 11 | **真實迴圈**(含 cmp/jl 跳轉) | 完全展開(11 層內聯) |
| 12 | 真實迴圈,**不再內聯**函式呼叫 | 完全展開 |
| 32 | 真實迴圈 | 分三段:9+11+12,各段內聯,段間 call |
編譯器對普通迴圈有**展開閾值**(約 10-11),超過則生成真實迴圈;模板版本**強制完全展開**,但維度過大(≥32)會導致編譯器將遞歸拆分為多段 call,避免單一函式過大。
dim 由 cin 讀取(運行期值),普通迴圈版本**終於生成真實迴圈**;模板版本**無法使用**(需編譯期常數)。-O2 下對小型固定迴圈會自動完全/部分展開,手寫模板解迴圈**多數情況無額外收益**。dim=100 產生 100 個函式實例)。dot<3>、mat4::mul)。
// 現代 C++ 推薦寫法:constexpr + 迴圈,讓編譯器自行決定
template
constexpr float dot(const float* a, const float* b) {
float sum = 0;
for (size_t i = 0; i < DIM; ++i) sum += a[i]*b[i];
return sum;
}
// 配合 #pragma loop(hint_parallel(0)) 或 __pragma(loop(no_vector)) 微調
模板元編程解迴圈是展示 C++ 編譯期運算能力的經典教案,但在現代優化編譯器面前,它已從「效能利器」轉為「教學樣本」。實際專案中請優先信任編譯器,僅在經剖析確認的極少數熱點、且維度極小固定時,謹慎考慮手動展開或模板技術,**務必以基準測試數據為準**。