記憶體階層與快取

快取友善程式碼(區塊化/分塊,blocking and tiling)

兩位廚師照著一模一樣的食譜、做出一模一樣的菜,但一位一小時完成、另一位五小時。差別在組織:快的廚師成批地把食材聚在手邊使用,慢的廚師則每一步都去儲藏室拿一樣東西、整天來回奔走。快取友善程式碼就是快廚師的紀律:撰寫存取模式尊重區域性的程式,好讓 CPU 需要的資料幾乎總是已在快取裡。結果是正確性一模一樣,速度卻可能快上好幾倍。

兩項核心技巧是迴圈排序與區塊化(也叫分塊,tiling)。迴圈排序是指照資料在記憶體中擺放的方式走訪,好讓你掃過相連的位址、收割空間區域性——在列優先語言裡,對二維陣列逐列走、而非逐行走,好讓你拉進的每條快取列在你前進之前都被完全用掉。區塊化是指把對大型資料集的計算拆成一個個小子塊(tile),每塊大小剛好塞進快取,並在一塊還常駐時把它的所有工作做完、才換下一塊。這把容量未命中轉成命中:不再讓巨大矩陣反覆從快取旁串流而過(每一遍都重新載入同一資料),而是把一塊載入一次、重用多次,善用了原始迴圈順序所揮霍掉的時間區域性。

經典例子是矩陣乘法。對大矩陣的天真三重迴圈,以對快取不友善的順序觸碰某個運算元、一再從記憶體重讀它;而把快取大小的子塊相乘的分塊版本,在完全相同的硬體上、以完全相同的結果,可以快上好幾倍。這是整章的實際回報:快取是自動的,但它的好處必須「賺來」。誠實的說法是,快取只改變速度、從不改變答案——所以快取友善程式設計對正確性隱形、容易被忽略,卻是程式設計者能施展的最高槓桿優化之一,常勝過演算法上的細微調整。提醒是最佳的分塊大小與迴圈順序取決於快取大小與機器,所以調校是真實、有時瑣碎的工作——但其原理,尊重區域性,是普世的。

對大型矩陣乘法 C = A x B,天真的 i-j-k 迴圈為 A 的每一列都從記憶體重新串流 B。分成例如 64x64 的子塊後,B 的每塊只載入一次、橫跨許多列重用——相同的算術、相同的 C,卻常因更好的快取重用而快上 3 到 10 倍。

相同結果、速度大不同:尊重區域性(迴圈順序、分塊),賺到快取的好處。

快取友善程式碼「只」改變速度、從不改變答案,這正是它如此容易被忽略的原因——也是忽略它會白白浪費巨大效能的原因。最佳的分塊大小與迴圈順序取決於機器的快取大小,所以原理普世、確切的調校卻因硬體而異。

又称
cache-aware codeloop blockingtiling區塊化