自動向量化(auto-vectorization)
你寫了個普通的迴圈——對每個 i,c[i] = a[i] + b[i]——一次一個元素地把兩個陣列相加,就像你跟朋友解釋時那樣。自動向量化是編譯器悄悄注意到這個迴圈對每個元素做相同且獨立的運算,並在幕後改寫它去用 SIMD 指令:不是每次迭代一個加法,而是每八個元素發出一條寬加法。你寫了純量程式,編譯器交給 CPU 的是向量程式。它是個翻譯員,把樸素迴圈變成 SIMD 的寬車道蓋章,而你自己沒碰到任何 SIMD 指令。
編譯器怎麼判斷這樣安全?它必須證明迴圈各迭代真的彼此獨立——在一批八個之中,處理元素 i 不會改變同一批中元素 j 的答案。為此它分析迴圈的資料相依(第 i+1 次迭代是否讀了第 i 次寫的東西?),檢查陣列存取是否規律(最好是連續單位跨步,好讓一條寬載入就填滿一個向量暫存器),並處理陣列長度不是向量寬度整齊倍數時剩下的尾巴。若它能證明安全又划算,就把迴圈條帶開採成寬塊加上一個純量尾巴。
這條的誠實核心是「為什麼自動向量化這麼常失敗」——因為這是初學者最常遇到的失望。編譯器必須保守:若兩個指標可能別名(指進同一個陣列,所以一個寫入可能毀掉之後的讀取),它就得假設最壞情況而拒絕。迴圈攜帶的相依(sum += a[i] 是一種歸約,編譯器必須特別辨認;s[i] = s[i-1] + a[i] 是真正的鏈,它通常無法向量化)、迴圈內的函式呼叫、複雜的分支,以及不規則的聚集/散播存取型態,全都會擋住它。實務結論是:對連續陣列、沒有別名、分支少的乾淨迴圈,向量化得很漂亮;糾結的程式則不會,而編譯器會默默讓它維持純量。restrict 提示、較簡單的迴圈本體,以及讀編譯器的向量化報告,就是真實程式設計師哄它前進的方法。
for (i=0;i<n;i++) c[i]=a[i]+b[i]; 能乾淨地向量化——無相依、連續。但 for (i=1;i<n;i++) a[i]=a[i-1]+b[i]; 不行:每次迭代都需要上一次迭代剛寫的 a[i-1],是真正迴圈攜帶的相依,所以編譯器讓它維持純量。
獨立、連續的迴圈會向量化;迴圈攜帶的相依與可能的指標別名會迫使編譯器退回純量程式。
自動向量化不是永遠會發動的魔法。指標別名、迴圈攜帶的相依、分支與函式呼叫經常擊敗它,而編譯器會默默失敗——你的迴圈照跑,只是純量。去看向量化報告,別假設它發生了。