程式設計師的 CPU 微架構

快取結合度(cache associativity)

快取遠比它前面所擋的那塊記憶體小,所以許多不同的記憶體位址必須競爭同一片架位。結合度就是「某個位址被允許放在哪些架位」的規則。想像一個有編號掛鉤的衣帽寄存處:若你的號碼牌逼你只能掛在某一個特定掛鉤(而且你得把那上面原本的衣服擠掉),那很受限但查找瞬間完成;若你的外套可以掛在好幾個掛鉤中的任一個,爭執就變少,但查找得多花一點功夫。

三種設計落在一條光譜上。在直接對映(direct-mapped)快取裡,每個記憶體位址恰好對映到唯一一個列槽——查找快,但兩個都很熱、卻對映到同一槽的位址會永遠互相擠掉對方。在全結合(fully-associative)快取裡,任何位址都能住進任何槽——沒有被迫的碰撞,但每次存取都要檢查每一個槽,代價高昂,因此只用於極小的結構。實務上的折衷是組相聯(set-associative):快取被切成若干組(set),一個位址恰好對映到一組,而在那一組之內它可佔據 N 路(way)中的任一路(即 N 路組相聯快取,常見 4 路、8 路或 16 路)。硬體為此把位址切成三個欄位:最低的幾個位元是列內偏移(offset,指 64 位元組列內的哪個位元組),接下來幾個位元是組索引(index,指哪一組),最高的幾個位元是標籤(tag,與列一起存放,用來確認對映到這一組的眾多位址中究竟是哪一個實際在場)。

你該在意的原因:結合度正是把「理論上夠大」的快取變成「依然會抖動」的那個因素。若你的存取模式不斷讓位址全落在同一組——典型情況是以等於組步距的 2 的次方為步幅走訪記憶體——你會在大半個快取空著的同時把活著的資料擠掉,產生衝突失誤(conflict miss)。這正是為何以 1024 為步幅可能比以 1023 為步幅慢上一大截:差一個元素就打破了 2 的次方對齊造成的別名。

對一個 32 KiB、8 路、64 位元組列的 L1:共有 32768 / 64 / 8 = 64 組。一個 64 位元位址切成:偏移 = 低 6 位元,索引 = 接下來 6 位元(64 組),標籤 = 其餘高位元。位址 0x10000 與 0x20000 共用同一個索引,因此它們在同一個 8 路組內競爭。

偏移 | 索引 | 標籤——每一層快取放置與尋找一個列時都用的同一套位址拆解。

更高的結合度能減少衝突失誤,卻永遠消不掉容量失誤——一個全結合快取仍可能單純就是太小了。而 2 的次方步幅,正是把一個快迴圈變成抖動迴圈的經典陷阱。

又稱
set-associative cachedirect-mapped cachefully-associative cache快取關聯性