高效能與平行計算

快取失誤(cache miss)

當處理器需要一個數時,它先看快取——核心旁那塊小而快的便箋本。如果那個數已經在那裡,就是快取命中,值幾乎立刻到手。如果不在,就是快取失誤:處理器必須伸向更慢的記憶體去取它,這期間它大致卡住,乾等數十到數百個週期。快取失誤,說白了,就是你快速的電腦變成慢電腦的那一刻。

有個微妙之處讓局部性得以回本:記憶體不是一次取一個數的。失誤時硬體會搬上一整塊連續的區段,稱為快取線(cache line)——通常 64 位元組,也就是八個雙精度數。所以如果你碰一個陣列元素、接著前進到緊鄰的下一個元素,那鄰居已經在快取裡(命中)——你為這條線付了一次錢,卻從中拿到八個數。但如果你以大跨距在記憶體中跳得很遠,每次存取都落在全新的線上、每次都失誤,於是你浪費掉每條取來的線的八分之七。失誤有幾種:強制性(你第一次碰到某資料)、容量性(工作集比快取大,舊資料在被重用前就被逐出)、衝突性(運氣不好的位址在快取槽位中相撞)。

快取失誤是真實數值程式上隱藏的稅。課本會說以天真方式相乘兩個 n×n 矩陣耗 2*n^3 次浮點運算;在真機上,天真三重迴圈之所以慢,不是因為浮點運算,而是因為其中一個迴圈橫跨列做大跨距走訪,幾乎每個內層步都產生一次快取失誤。高效能數值計算的整套手藝——分塊、選擇列優先或行優先走訪、打包資料——大體上就是把失誤轉成命中的手藝。預測一個記憶體受限核心的執行時間,要數的是失誤次數,不是浮點運算數。

對一個列優先儲存的二維 double 陣列求和:先列後行的迴圈碰連續位址,故每條 64 位元組的線服務 8 個數——約每 8 次存取一次失誤。改成先行後列,每次存取就跳一整列,每次都落在全新的線上——約每次存取一次失誤,記憶體流量約多 8 倍,往往慢上數倍,而算術完全相同。

相同浮點運算、相同總和——但錯誤的迴圈順序幾乎每次存取都失誤。

一次失誤取的是一整條線(常為 8 個 double),不是一個數——所以唯有你用上一起拖進來的鄰居,代價才被攤平。常見錯誤是以為「一次存取等於一次取數」;在真機上,一次跨距糟糕的存取可能浪費它觸發頻寬的八分之七。

又稱
cache faultmiss快取未命中未命中