向量化(SIMD)
假設你得把兩欄各八個的數相加。你可以寫八次各自獨立的加法、一個接一個——或者你可以想像一台特別的計算機,按一下鍵就同時把八對數全加好。現代處理器裡正內建了這台計算機。SIMD(單指令多資料的縮寫)是一種硬體,它把一個運算同時施加在一整條小向量的值上,而向量化就是改寫(或讓編譯器改寫)你的迴圈來使用它的動作。
具體而言,CPU 有寬暫存器——比方 256 或 512 位元——它裝的不是一個數,而是一條打包了好幾個數的車道:一個 512 位元暫存器裝八個雙精度數、或十六個單精度數。一條 SIMD 指令如「把這兩個 512 位元暫存器相加」一口氣執行八(或十六)次加法,大約只需一次純量加法的時間。要利用它,迴圈必須可向量化:各次迭代必須獨立(沒有哪一次依賴前一次的結果)、資料應連續且對齊以乾淨地載入車道、且不可有把不同車道送往不同路徑的分支。編譯器能自動向量化簡單迴圈,但它很容易放棄——一個資料相依、一個別名指標、或一個彆扭的跨距,它就退回慢的純量碼。
向量化是單一核心取得尖峰吞吐量的兩條途徑之一(另一條是指令層級管線化),也是為何相同算術不改演算法就能快 4 倍、8 倍或 16 倍。它也是為何資料佈局如此要緊:SIMD 要它的運算元緊挨在一起,這偏好「結構陣列」勝過「陣列結構」佈局,並且是 BLAS level-3 核心能達近尖峰速度的一半原因。但 SIMD 只放大帳本的計算那一側——對一個已在等資料的記憶體受限核心毫無幫助,這正是為何你要先看屋頂線,才知道向量化到底有沒有用。
對一百萬個 double 跑迴圈 c[i] = a[i] + b[i]:純量版本發出一百萬條加法指令;用 512 位元 SIMD,編譯器發出八分之一那麼多,每條一次加八對。算術完全相同,但指令流縮小 8 倍。反觀 c[i] = c[i-1] + a[i](累加和)無法天真地向量化——每次迭代都需要前一個結果,故各車道無法平行跑。
一條 SIMD 指令一次處理 8 個 double——但前提是各次迭代彼此獨立。
向量化只加速算術;對一個已因等資料而停頓的記憶體受限核心毫無作用。而且把浮點求和重排進 SIMD 車道,與純量順序並非逐位元相同,因為浮點加法不具結合律——所以向量化可能悄悄改變結果的末幾位數。