我把 CPU 三級快取的祕密,藏在這 8 張圖裡

語言: CN / TW / HK

theme: jzman

本文已收錄到 AndroidFamily,技術和職場問題,請關注公眾號 [彭旭銳] 提問。

前言

大家好,我是小彭。

在上一篇文章裡,我們聊到了計算機儲存器系統的金字塔結構,其中在 CPU 和記憶體之間有一層快取記憶體,就是我們今天要聊的 CPU 三級快取。

那麼,CPU Cache 的結構是怎樣的,背後隱含著哪些設計思想,CPU Cache 和記憶體資料是如何關聯起來的,今天我們將圍繞這些問題展開。


思維導圖:


1. 認識 CPU 快取記憶體

1.1 儲存器的金字塔結構

現代計算機系統為了尋求容量、速度和價格最大的價效比會採用分層架構,從 “CPU 暫存器 - CPU 快取記憶體 - 記憶體 - 硬碟”自上而下容量逐漸增大,速度逐漸減慢,單位價格也逐漸降低。

  • 1、CPU 暫存器: 儲存 CPU 正在使用的資料或指令;
  • 2、CPU 快取記憶體: 儲存 CPU 近期要用到的資料和指令;
  • 3、記憶體: 儲存正在執行或者將要執行的程式和資料;
  • 4、硬碟: 儲存暫時不使用或者不能直接使用的程式和資料。

儲存器金字塔

1.2 為什麼在 CPU 和記憶體之間增加快取記憶體?

我認為有 2 個原因:

  • 原因 1 - 彌補 CPU 和記憶體的速度差(主要): 由於 CPU 和記憶體的速度差距太大,為了拉平兩者的速度差,現代計算機會在兩者之間插入一塊速度比記憶體更快的快取記憶體。只要將近期 CPU 要用的資訊調入快取,CPU 便可以直接從快取中獲取資訊,從而提高訪問速度;

  • 原因 2 - 減少 CPU 與 I/O 裝置爭搶訪存: 由於 CPU 和 I/O 裝置會競爭同一條記憶體匯流排,有可能出現 CPU 等待 I/O 裝置訪存的情況。而如果 CPU 能直接從快取中獲取資料,就可以減少競爭,提高 CPU 的效率。

1.3 CPU 的三級快取結構

在 CPU Cache 的概念剛出現時,CPU 和記憶體之間只有一個快取,隨著晶片整合密度的提高,現代的 CPU Cache 已經普遍採用 L1/L2/L3 多級快取的結構來改善效能。自頂向下容量逐漸增大,訪問速度也逐漸降低。當快取未命中時,快取系統會向更底層的層次搜尋。

  • L1 Cache: 在 CPU 核心內部,分為指令快取和資料快取,分開存放 CPU 使用的指令和資料;
  • L2 Cache: 在 CPU 核心內部,尺寸比 L1 更大;
  • L3 Cache: 在 CPU 核心外部,所有 CPU 核心共享同一個 L3 快取。

CPU 三級快取


2. 理解 CPU 三級快取的設計思想

2.1 為什麼 L1 要將指令快取和資料快取分開?

這個策略叫分離快取,與之相對應的叫統一快取:

  • 分離快取: 指令和資料分別存放在不同快取中:
    • 指令快取(Instruction Cache,I-Cache)
    • 資料快取(Data Cache,D-Cache)
  • 統一快取: 指令和資料統一存放在一個快取中。

那麼,為什麼 L1 快取要把指令和資料分開呢?我認為有 2 個原因:

  • 原因 1 - 避免取指令單元和取資料單元爭奪訪快取(主要): 在 CPU 核心中,取指令和取資料指令是由兩個不同的單元完成的。如果使用統一快取,當 CPU 使用超前控制或流水線控制(並行執行)的控制方式時,會存在取指令操作和取資料操作同時爭用同一個快取的情況,降低 CPU 執行效率;

  • 原因 2 - 記憶體中資料和指令是相對聚簇的,分離快取能提高命中率: 在現代計算機系統中,記憶體中的指令和資料並不是隨機分佈的,而是相對聚集地分開儲存的。因此,CPU Cache 中也採用分離快取的策略更符合記憶體資料的現狀;

2.2 為什麼 L1 採用分離快取而 L2 採用統一快取?

我認為原因有 2 個:

  • 原因 1: L1 採用分離快取後已經解決了取指令單元和取資料單元的爭奪訪快取問題,所以 L2 是否使用分離快取沒有影響;

  • 原因 2: 當快取容量較大時,分離快取無法動態調節分離比例,不能最大化發揮快取容量的利用率。例如資料快取滿了,但是指令快取還有空閒,而 L2 使用統一快取則能夠保證最大化利用快取空間。

2.3 L3 快取是多核心共享的,放在晶片外有區別嗎?

整合在晶片內部的快取稱為片內快取,整合在晶片外部(主機板)的快取稱為片外快取。最初,由於受到晶片整合工藝的限制,片內快取不可能很大,因此 L2 / L3 快取都是設計在主機板上,而不是在晶片內的。

後來,L2 / L3 才逐漸整合到 CPU 晶片內部後的。片內緩衝和片外快取是有區別的,主要體現在 2 個方面:

  • 區別 1 - 片內快取物理距離更短: 片內快取與取指令單元和取資料單元的物理距離更短,速度更快;

  • 區別 2 - 片內快取不佔用系統匯流排: 片內快取使用獨立的 CPU 片內匯流排,可以減輕系統匯流排的負擔。


3. CPU Cache 的基本單位 —— Cache Line

CPU Cache 在讀取記憶體資料時,每次不會只讀一個字或一個位元組,而是一塊塊地讀取,這每一小塊資料也叫 CPU 快取行(CPU Cache Line)。這也是對區域性性原理的應用,當一個指令或資料被訪問過之後,與它相鄰地址的資料有很大概率也會被訪問,將更多可能被訪問的資料存入快取,可以提高快取命中率。

當然,塊長也不是越大越好(一般是取 4 到 8 個字長,即 64 位):

  • 前期:當塊長由小到大增長時,隨著區域性性原理的影響使得命中率逐漸提高;
  • 後期:但隨著塊長繼續增大,導致快取中承載的塊個數減少,很可能記憶體塊剛剛裝入快取就被新的記憶體塊覆蓋,命中率反而下降。而且,塊長越長,追加的部分距離被訪問的字越遠,近期被訪問的可能性也更低,無濟於事。

區分幾種容量單位:

  • 位元組(Byte): 位元組是計算機資料儲存的基本單位,即使儲存 1 個位也需要按 1 個位元組儲存;
  • 字(Word): 字長是 CPU 在單位時間內能夠同時處理的二進位制資料位數。多少位 CPU 就是指 CPU 的字長是多少位(比如 64 位 CPU 的字長就是 64 位);
  • 塊(Block): 塊是 CPU Cache 管理資料的基本單位,也叫 CPU 快取行;
  • 段(Segmentation)/ 頁(Page): 段 / 頁是作業系統管理虛擬記憶體的基本單位。

事實上,CPU 在訪問記憶體資料的時候,與計算機中對於 “快取設計” 的一般性規律是相同的: 對於基於 Cache 的系統,對資料的讀取和寫入總會先訪問 Cache,檢查要訪問的資料是否在 Cache 中。如果命中則直接使用 Cache 上的資料,否則先將底層的資料來源載入到 Cache 中,再從 Cache 讀取資料。

那麼,CPU 怎麼知道要訪問的記憶體資料是否在 CPU Cache 中,在 CPU 中的哪個位置,以及是不是有效的呢?這就是下一節要講的記憶體地址與 Cache 地址的對映問題。


4. 記憶體地址與 Cache 地址的對映

無論對 Cache 資料檢查、讀取還是寫入,CPU 都需要知道訪問的記憶體資料對應於 Cache 上的哪個位置,這就是記憶體地址與 Cache 地址的對映問題。

事實上,因為記憶體塊和快取塊的大小是相同的,所以在對映的過程中,我們只需要考慮 “記憶體塊索引 - 快取塊索引” 之間的對映關係,而具體訪問的是塊內的哪一個字,則使用相同的偏移在塊中尋找。舉個例子:假設記憶體有 32 個記憶體塊,CPU Cache 有 8 個快取塊,我們只需要考慮 紫色 部分標識的索引如何匹配即可。

目前,主要有 3 種對映方案:

  • 1、直接對映(Direct Mapped Cache): 固定的對映關係;
  • 2、全相聯對映(Fully Associative Cache): 靈活的對映關係;
  • 3、組相聯對映(N-way Set Associative Cache): 前兩種方案的折中方法。

記憶體塊索引 - 快取塊索

4.1 直接對映

直接對映是三種方式中最簡單的對映方式,直接對映的策略是: 在記憶體塊和快取塊之間建立起固定的對映關係,一個記憶體塊總是對映到同一個快取塊上。

具體方式:

  • 1、將記憶體塊索引對 Cache 塊個數取模,得到固定的對映位置。例如 13 號記憶體塊對映的位置就是 13 % 8 = 5,對應 5 號 Cache 塊;

  • 2、由於取模後多個記憶體塊會對映到同一個快取塊上,產生塊衝突,所以需要在 Cache 塊上增加一個 組標記(TAG),標記當前快取塊儲存的是哪一個記憶體塊的資料。其實,組標記就是記憶體塊索引的高位,而 Cache 塊索引就是記憶體塊索引的低 4 位(8 個字塊需要 4 位);

  • 3、由於初始狀態 Cache 塊中的資料是空的,也是無效的。為了標識 Cache 塊中的資料是否已經從記憶體中讀取,需要在 Cache 塊上增加一個 有效位(Valid bit) 。如果有效位為 0,則 CPU 可以直接讀取 Cache 塊上的內容,否則需要先從記憶體讀取記憶體塊填入 Cache 塊,再將有效位改為 1。

直接對映

4.2 全相聯對映

理解了直接對映的方式後,我們發現直接對映存在 2 個問題:

  • 問題 1 - 快取利用不充分: 每個記憶體塊只能對映到固定的位置上,即使 Cache 上有空閒位置也不會使用;
  • 問題 2 - 塊衝突率高: 直接對映會頻繁出現塊衝突,影響快取命中率。

基於直接對映的缺點,全相聯對映的策略是: 允許記憶體塊對映到任何一個 Cache 塊上。 這種方式能夠充分利用 Cache 的空間,塊衝突率也更低,但是所需要的電路結構物更復雜,成本更高。

具體方式:

  • 1、當 Cache 塊上有空閒位置時,使用空閒位置;
  • 2、當 Cache 被佔滿時則替換出一箇舊的塊騰出空閒位置;
  • 3、由於一個 Cache 塊會對映所有記憶體塊,因此組標記 TAG 需要擴大到與主記憶體塊索引相同的位數,而且對映的過程需要沿著 Cache 從頭到尾匹配 Cache 塊的 TAG 標記。

全相聯對映

4.3 組相聯對映

組相聯對映是直接對映和全相聯對映的折中方案,組相聯對映的策略是: 將 Cache 分為多組,每個記憶體塊固定對映到一個分組中,又允許對映到組內的任意 Cache 塊。顯然,組相聯的分組為 1 時就等於全相聯對映,而分組等於 Cache 塊個數時就等於直接對映。

組相聯對映


5. Cache 塊的替換策略

在使用直接對映的 Cache 中,由於每個主記憶體塊都與某個 Cache 塊有直接對映關係,因此不存在替換策略。而使用全相聯對映或組相聯對映的 Cache 中,主記憶體塊與 Cache 塊沒有固定的對映關係,當新的記憶體塊需要載入到 Cache 中時(且 Cache 塊沒有空閒位置),則需要替換到 Cache 塊上的資料。此時就存在替換策略的問題。

常見替換策略:

  • 1、隨機法: 使用一個隨機數生成器隨機地選擇要被替換的 Cache 塊,實現簡單,缺點是沒有利用 “區域性性原理”,無法提高快取命中率;

  • 2、FIFO 先進先出法: 記錄各個 Cache 塊的載入事件,最早調入的塊最先被替換,缺點同樣是沒有利用 “區域性性原理”,無法提高快取命中率;

  • 3、LRU 最近最少使用法: 記錄各個 Cache 塊的使用情況,最近最少使用的塊最先被替換。這種方法相對比較複雜,也有類似的簡化方法,即記錄各個塊最近一次使用時間,最久未訪問的最先被替換。與前 2 種策略相比,LRU 策略利用了 “區域性性原理”,平均快取命中率更高。


6. 總結

  • 1、為了彌補 CPU 和記憶體的速度差和減少 CPU 與 I/O 裝置爭搶訪存,計算機在 CPU 和記憶體之間增加快取記憶體,一般存在 L1/L2/L3 多級快取的結構;

  • 2、對於基於 Cache 的儲存系統,對資料的讀取和寫入總會先訪問 Cache,檢查要訪問的資料是否在 Cache 中。如果命中則直接使用 Cache 上的資料,否則先將底層的資料來源載入到 Cache 中,再從 Cache 讀取資料;

  • 3、CPU Cache 是一塊塊地從記憶體讀取資料,一塊資料就是快取行;

  • 4、記憶體地址與 Cache 地址的對映有直接對映、全相聯對映和組相聯對映;

  • 5、使用全相聯對映或組相聯對映的 Cache 中,當新的記憶體塊需要載入到 Cache 中時且 Cache 塊沒有空閒位置,則需要替換到 Cache 塊上的資料。

今天,我們主要討論了 CPU Cache 的基本設計思想以及 Cache 與記憶體的對映關係。具體 CPU Cache 是如何讀取和寫入的還沒講,另外有一個 CPU 快取一致性問題 說的又是什麼呢?下一篇文章我們詳細展開討論,請關注。


版權宣告

本文為稀土掘金技術社群首發簽約文章,14天內禁止轉載,14天后未獲授權禁止轉載,侵權必究!

參考資料