程式設計師的 CPU 微架構

SIMD 與向量化(SSE/AVX、NEON)

/ SIMD -> SIM-dee; SSE -> ess-ess-EE; AVX -> ay-vee-EKS; NEON -> NEE-on /

普通指令是純量的:一次加法吃兩個數、產生一個。但大量的真實工作是對「一整個陣列」套用同樣的運算——把這 1000 個數加到那 1000 個數、把每個像素調亮、把每個音訊取樣縮放。SIMD,全名 Single Instruction, Multiple Data(單指令多資料),正是做這件事的硬體:一條指令一次對一整個向量的值運算,對每個「車道(lane)」平行地做同樣的事。

機制是寬向量暫存器,以及對其所有車道同時運算的指令。在 x86 上這些家族隨時間越變越寬:SSE 暫存器是 128 位元(四個 32 位元浮點數)、AVX 是 256 位元(八個浮點數)、AVX-512 是 512 位元(十六個浮點數);在 ARM 上對應的是 NEON(128 位元)與較新的可伸縮 SVE。單一條 AVX 加法指令,在大約純量加法做一對的時間內加了八對浮點數——若你的資料與運算契合向量形狀,那是對算術最多八倍的吞吐量增益。抵達 SIMD 有兩條路:自動向量化,由最佳化器辨識一個簡單迴圈並替你發出向量指令(簡單、可移植的路),或內建函式(intrinsics),你透過像 _mm256_add_ps 這樣的編譯器內建明確地寫出向量運算(更多控制、更多功夫、較不可移植)。

要誠實面對它何時真的有幫助。SIMD 在規律、連續、獨立、每元素一個簡單運算且沒有彆扭流程控制的資料上發光。當每元素有資料相依的分支、當存取模式分散(gather 遠慢於連續載入)、當資料未對齊、或當迴圈有跨迭代相依時,它就吃力。自動向量化很脆弱:單單一次函式呼叫、一個別名的可能、或一個提早離開,都可能悄悄地阻止它,所以一個你「以為」被向量化的迴圈往往沒有——查看編譯器的最佳化報告或產生的組合語言。而那個頭條加速只套用於被向量化的算術;若迴圈是記憶體受限的,你可能受限於 DRAM 頻寬、而非算術,SIMD 買不到多少。

for (i = 0; i < n; i++) c[i] = a[i] + b[i]; 是教科書級的可自動向量化迴圈——獨立、連續、沒有分支。在 -O2(或 -O3)並指定像 -mavx2 的目標下,gcc/clang 每 8 個浮點數發出一條向量加法。加上一次函式呼叫、或 a、b、c 之間一個可能的別名,它就可能悄悄退回純量。

理想的 SIMD 迴圈——也提醒你一個別名風險就能悄悄關掉向量化器。

別只因為你加了 -O3 就假設迴圈被向量化了——到編譯器的最佳化報告或組合語言裡驗證。而那個「最多 N 倍」是算術吞吐量;記憶體受限的迴圈受頻寬封頂,更寬的向量在那裡加不了多少。

又稱
single instruction multiple datavectorizationvector registersSVE向量化單指令多資料