強制、容量與衝突失誤(the three C's of cache misses)
快取失誤,單純就是中央處理器索取的資料不在它所查的那層快取裡的那一刻,於是它必須去更慢的一層(並停滯、等待)。然而並非所有失誤都一樣,一個著名的分類——三個 C——依根本原因把它們分類,因為每個原因有不同的解藥。知道你正受哪一種折磨,就知道該改什麼。
強制失誤(compulsory miss,也叫冷失誤 cold miss)是對某一列的有史以來第一次存取:資料從未被載入過,當然不在快取裡。任何手段都阻止不了「最初那一次觸碰」,不過預取能提早發出它、藉以隱藏其延遲。容量失誤(capacity miss)的發生,是因為你的工作集——你正積極重用的所有資料——根本就比快取大,於是較舊的列在你回頭用它們之前就被擠掉了,即使在一個理想的全結合快取裡也一樣。解藥是縮小工作集:以快取大小的磚塊(tile)或區塊(block)來處理資料。衝突失誤(conflict miss)的發生,則是那些「本來放得進快取」的列,卻因為在快取有限的結合度下對映到同一組而互相擠掉。解藥是改變佈局或步幅,讓位址分散到更多組,或仰賴更高的結合度。
這三者解釋的是單一快取內的失誤,而實務流程是:用硬體效能計數器量你的失誤率,再問哪一個 C 佔主導。強制失誤偏多的程式要的是預取;容量失誤偏多的要的是分塊與更小的資料;衝突失誤偏多的要的是填充與步幅調整。一旦你進入多執行緒,還會多出第四個 C——一致性失誤(coherence miss,即另一核心使你的列失效);那一個正是偽共享背後的代價。
天真地相乘兩個大矩陣(列乘行)會遭受容量失誤,因為每次走訪一行都重新串流了比 L2 還大的記憶體。把它改寫成相乘「放得進 L1 的 B×B 小磚塊」,就把其中大部分轉成重用命中——這就是經典的快取分塊。
相同的算術、相同的結果——分塊縮小了工作集,使容量失誤變成快取命中。
三個 C 是一個用來推理的模型,而非硬體回報的細目——真實的計數器告訴你的是總失誤率,而非那是哪個 C;你得從存取模式去推斷原因。