為什麼需要 CPU 快取#

CPU 的快取(Cache)被設計來解決記憶體存取過慢的問題。在記憶體階層(Memory Hierarchy)中:

  • 越快的記憶體容量越小
  • 離 CPU 越遠,速度越慢

可能會有人問:那為什麼不直接加大 Cache 或 Register?答案很簡單:價格太貴。以 Intel i7-8700K 為例:

  • L1 Cache: 384 KiB
  • L2 Cache: 1.5 MiB
  • L3 Cache: 12 MiB

速度上 L1 > L2 > L3。Cache 大小都是 MB 級,若擴充到 GB 級甚至 TB 級,整台電腦就會貴到不像話了。

CPU 中的 Register 數量不多且容量更小,只能供運算使用。若沒有 Cache,CPU 就需要反覆讀取主記憶體(RAM)。有了 Cache,CPU 可以在指令執行前進行 data prefetch,將需要用到的資料事先取出來。但 Cache 成本高,無法把所有資料都塞進去,所以需要一套機制決定哪些資料要被快取。

Cache 組成#

從小到大三層結構:

  • cache line:cache 的最小單位
  • cache set:cache line 的集合
cache_set = ["cache_line", "cache_line", "cache_line"]
  • cache:cache set 的集合
cache = ["cache_set", "cache_set", "cache_set"]

Cache 的存取策略#

因為成本考量,Cache 大小有限,所以要設計快取機制決定哪些資料要被快取。

Direct Mapped Cache#

Direct Mapped 把資料直接映射到一條 cache line 上。可以想像:一個班級有 1 - 50 號座號,Cache 也分配了 50 個位置(第 1 個位置只有 1 號的同學能入座)。問題在於:如果有 20 個班級,就有可能造成多個來自不同班級卻同號碼的學生爭搶一個位置,讓彼此不斷被替換出去

Fully Associative#

Fully Associative 簡單多了:50 個位置,誰都能坐,不需要按座號入座。所以可以讓不同班級座號相同的學生同時待在 Cache 中。

但缺點也很明顯:若要尋找特定的同學,最壞情況需要查找 50 個位置才能得知是否有入座。

N-way Set Associative Cache#

為了結合兩者優點,電腦科學家提出 N-way Set Associative:把 50 個位置拆成 5 組,每組 10 個位置,第一組可讓任一班級的 1 - 10 號入座,依此類推。查找範圍縮小到一個 set 內的 N 條 line。

Cache miss#

Cache miss 有三種情況:

類型別稱說明
Compulsory misses(強制性)cold start misses第一次存取未曾在 cache 內的 block 而發生的 miss,這種 miss 是不可避免的
Capacity misses(容量性)程式執行期間,cache 無法包含所有需要的 block 而產生的 miss。發生在一個 block 被取代後,稍後又需要用到。
Conflict misses(衝突性)collision misses發生在 set-associative 或 direct-mapped caches。當多個 block 競爭相同的 set。

Cache 的替換策略#

當 conflict misses 發生時,常見三種替換方法:

替換策略作法優缺點
FIFO(First In First Out)將集合中最早進入的區塊替換掉實作非常容易,但容易把重要資料替換掉
Random隨機挑選一個區塊替換
LRU(Least Recently Used)替換掉最近最少使用的資料實作上比 FIFO 複雜,但能避免頻繁使用的資料被換掉

Cache 機制的其他應用#

除了 CPU 中的 Cache,Cache 思維也廣泛出現在其他地方。以作業系統的檔案系統為例:磁碟非常慢,如果所有資料都等到要用時才讀,IO 延遲會非常高。為了解決這個問題,作業系統會在檔案系統中實作一層 Buffer cache,把常用資料存放在 RAM 中,配合 LRU 等替換策略。

多核心時代的同步問題#

Cache 大幅提升處理器效率,但在多核心世代,它也為資料同步帶來麻煩。假設一顆處理器中有 4 個實體核心,每個核心都有獨立的 Cache,當複數個核心都 Cache 了同一筆資料並對其做了修改,就會讓資料發生非預期的運算結果。

60 分的解法#

學過系統程式的同學可能想到:那我用 Lock 機制保護這些資料?

但這是循環論證:Lock 的實作也是仰賴記憶體變數做紀錄,如果今天多個實體核心的 Cache 都保存了同一道鎖的狀態(未上鎖),就又讓多個核心同時拿到鎖了

機制完整的解法#

完整的解法需要 Cache Coherence(快取一致性)協定,例如 MESI 等,會在後續作業系統章節詳述。

重點整理#

  • 記憶體階層的目的:用便宜的大容量配上昂貴的小容量,模擬出「又快又大」的存取體驗。
  • Cache 的三種對應方式(Direct / Fully / N-way)反映了「速度 vs. 命中率 vs. 硬體成本」的取捨。
  • Cache miss 分為 compulsory / capacity / conflict 三類,理解後才能對症下藥(增加容量?提高 associativity?換替換策略?)。
  • 多核時代 Cache 帶來新的同步問題,必須靠快取一致性協定來解決。

原文出處#

  • 原書/iThome:https://ithelp.ithome.com.tw/articles/10264830