聚集-散播存取(gather-scatter access)
向量載入最開心的時候,是它要的資料坐在一段整齊連續的範圍裡——像一掃就從一個書架抓走八本書。但真實程式常常要的是散落在記憶體各處的元素:表格的每第十列,或那些索引來自另一份清單的項目(A[index[0]]、A[index[1]],依此類推)。聚集(gather)就是伸進許多分散位址、把那些散落元素收進一個向量暫存器的運算;散播(scatter)是反向,拿向量的各車道、把每一個寫回它自己分散的位址。
有兩種型態要緊。跨步存取(strided)讀取彼此固定距離的元素——譬如每第 4 個浮點數,對應 RGBA 影像的紅色通道;跨步(stride)就是那個固定的間隔。聚集/散播是完全一般的情形:你給硬體一個索引向量,它就為向量中的每個索引 i 取 A[i],不管那些位址跳得多不規則。一條索引向量為 [0, 7, 3, 12] 的聚集指令,把 A[0]、A[7]、A[3]、A[12] 載入四條車道;散播在儲存時做鏡像。這正是讓向量程式能處理稀疏矩陣、間接查表與直方圖式更新的東西。
聚集與散播強大,但它們是資料層級平行裡昂貴的角落,初學者該對此有敬意。連續的向量載入取一條快取列就餵飽每條車道;聚集卻可能為每條車道碰一條不同的快取列,所以可能慢上許多倍,並對記憶體系統造成壓力。散播還有額外的危險:若兩條車道散播到同一個位址(碰撞),硬體必須定義會發生什麼,而平行的直方圖更新是經典陷阱。經驗法則是:向量化喜愛連續、單位跨步的資料;你越往任意聚集/散播漂移,瓶頸就越是記憶體系統而非算術。
稀疏內積:對 A[col[k]] 乘 B[k] 求和。col 陣列指名 A 的哪些行非零,所以你無法連續讀 A。一條以 col 為索引向量的聚集把所需的 A 項目載入向量暫存器;接著進行正常的向量乘加——但聚集本身每條車道可能碰一條不同的快取列。
聚集把散落元素收進向量;散播把向量車道寫到分散的位址。兩者都比連續存取昂貴得多。
聚集/散播不是「向量的免費隨機存取」。每條車道可能各自未命中快取,所以任意聚集的代價可能是連續載入的許多倍;而兩條散播車道寫同一位址是真實的正確性危險。