1. 程式人生 > 程式設計 >深入學習快取一致性問題和快取一致性協議MESI(一)

深入學習快取一致性問題和快取一致性協議MESI(一)

先來梳理下關於快取記憶體的一些知識

快取記憶體是一種存取速率遠比主記憶體大而容量遠比主記憶體小的儲存部件,每個處理器都有其快取記憶體。引入快取記憶體之後, 處理器在執行記憶體讀、 寫操作的時候並不直接與主記憶體打交道, 而是通過快取記憶體進行的。變數名相當於記憶體地址, 而變數值則相當於相應記憶體空間所儲存的資料。

從內部結構來看快取記憶體相當於一個拉鍊散列表(ChainedHash Table). 它包含若干桶(Bucket,硬體上稱之為Set),每個桶又可以包含若干快取條目(CacheEntry) .

快取記憶體記憶體結構示意圖

快取條目可被進一步劃分為Tag、Data Block以及Flag這三個部分其中, Data Block也被稱為快取行(CacheLine),它是快取記憶體與主記憶體之間的資料交換最小單元 用於儲存從記憶體中讀取的或者準備寫往記憶體的資料。 Tag則包含了與快取行中資料相應的記憶體地址的部分資訊(記憶體地址的高位部分位元)。 Flag用於表示相應快取行的狀態資訊。 快取行的容量(也被稱為快取行寬度)通常是2的倍數, 其大小在16-256 位元組(Byte)之間不等。一個快取行可以儲存若干變數的值, 而多個變數的值則可能被儲存在同一個快取行之中。

快取條目
處理器在執行記憶體訪問操作時會將相應的記憶體地址解碼。記憶體地址的解碼結果包括 tag、index以及offset這三部分資料。其中,index相當於桶編號,它可以用來定位記憶體地址對應的桶;

一個桶可能包含多個快取條目. tag相當於快取條目的相對編號, 其作用在於用來與同一個桶中的各個快取條目中的Tag部分進行比較, 以定位一個具體的快取條目; 一個快取條目中的快取行可以用來儲存多個變數,offset是快取行內的位置偏移, 其作用在於確定一個變數在一個快取行中的儲存起始位置。

快取命中

根據這個記憶體地址的解碼結果,如果快取記憶體子系統能夠找到相應的快取行並且快取行所在的快取條目的Flag表示相應快取條目是有效的, 那麼我們就稱相應的記憶體操作產生了快取命中(CacheHit) 3; 否則,我們就稱相應的記憶體操作產生了快取未命中(CacheMiss)。

從效能角度來看,減少快取未命中

快取未命中包括讀未命中(Read Miss)和寫未命中(Write Miss),分別對應記憶體讀和寫操作。當讀未命中產生時, 處理器所需讀取的資料會從主記憶體中載入並被存入相應的快取行之中。 這個過程會導致處理器停頓(Stall)而不能執行其他指令,這不利於發揮處理器的處理能力。

快取未命中不可避免

由於快取記憶體的總容量遠小於主記憶體的總容量,同一個快取行在不同時刻儲存的可能是不同的一段資料, 因此快取未命中是不可避免的。

在Linux系統中,我們可以使用Linux核心工具perf來檢視程式執行過程中的快取未命中情況。

快取記憶體通常被稱為一級快取(LI Cache)、 二級快取(L2 Cache)、 三級快取(L3 Cache)

image.png

image.png
快取記憶體從下到上越接近CPU速度越快,同時容量也越小。現在大部分的處理器都有二級或者三級快取,從下到上依次為 L3 cache,L2 cache,L1 cache. 快取又可以分為指令快取和資料快取,指令快取用來快取程式的程式碼,資料快取用來快取程式的資料L1 Cache,一級快取,本地core的快取,分成32K的資料快取L1d和32k指令快取L1i,訪問L1需要3cycles,耗時大約1ns; L2 Cache,二級快取,本地core的快取,被設計為L1快取與共享的L3快取之間的緩衝,大小為256K,訪問L2需要12cycles,耗時大約3ns; L3 Cache,三級快取,在同插槽的所有core共享L3快取,分為多個2M的段,訪問L3需要38cycles,耗時大約12ns;

一級快取可能直接被整合在處理器的核心裡,因此訪問效率非常高。通常包括兩部分,一部分用於儲存指令,一部分用於儲存資料,距離處理器越近的快取記憶體,儲存速率越快,製造成本越高,容量越小。

快取一致性協議

快取一致性問題

多個執行緒併發訪問同一個共享變植的時候,這些執行緒的執行處理器上的快取記憶體各自都會保留一份該共享變撒的副本,這就帶來一個新問題一個處理器對其副本資料進行更新之後, 其他處理器如何 “察覺” 到該更新並做出適當反應, 以確保這些處理器後續讀取該共享變扯時能夠讀取到這個更新。這就是快取一致性問題。 例如: CPU-0讀取主存的資料,快取到CPU-0的快取記憶體中,CPU-1也做了同樣的事情,而CPU-1把count的值修改成了2,並且同步到CPU-1的快取記憶體,但是這個修改以後的值並沒有寫入到主存中,CPU-0訪問該位元組,由於快取沒有更新,所以仍然是之前的值,就會導致資料不一致的問題.

實質

實質就是如何防止讀髒資料和丟失更新的問題。 各個廠家提供了不少解決方案,最後選擇了快取一致性協議。

匯流排鎖 當一個CPU對其快取中的資料進行操作的時候,往匯流排中傳送一個Lock訊號。其他處理器的請求將會被阻塞,那麼該處理器可以獨佔共享記憶體。匯流排鎖相當於把CPU和記憶體之間的通訊鎖住了,所以這種方式會導致CPU的效能下降,所以P6系列以後的處理器,出現了另外一種方式,就是快取鎖。

快取鎖 如果快取在處理器快取行中的記憶體區域在LOCK操作期間被鎖定,當它執行鎖操作回寫記憶體時,處理不在匯流排上宣告LOCK訊號,而是修改內部的快取地址,然後通過快取一致性機制來保證操作的原子性,因為快取一致性機制會阻止同時修改被兩個以上處理器快取的記憶體區域的資料,當其他處理器回寫已經被鎖定的快取行的資料時會導致該快取行無效。所以如果宣告瞭CPU的鎖機制,會生成一個LOCK指令,會產生兩個作用。

  1. Lock字首指令會引起引起處理器快取回寫到記憶體,在P6以後的處理器中,LOCK訊號一般不鎖匯流排,而是鎖快取。
  2. 一個處理器的快取回寫到記憶體會導致其他處理器的快取無效

X86基於MESI協議的問題。

MESI (Modified-Exclusive-Shared-Invalid)協議是一種廣為使用的快取一致性協議, x86處理器所使用的快取一致性協議就是基於MESI協議的。

MESI協議對記憶體資料訪問的控制類似讀寫鎖,它使得針對同一地址的讀記憶體操作是併發的,而針對同一地址的寫記憶體操作是獨佔的,即針對同一記憶體地址進行的寫操作在任意一個時刻只能夠由一個處理器執行。 在MESI協議中, 一個處理器往記憶體中寫資料時必須持有該資料的所有權。

為了保障資料的一致性. MESI將快取條目的狀態劃分為Modified(更新的)、Exclusive(排外的)、Shared(共享的)和Invalid(無效的)這4種, 並在此基礎上定義了一組訊息(Message)用於協調各個處理器的讀、 寫記憶體操作。

MESI協議中一個快取條目的Flag值有以下4種可能: ​ •Invalid (無效的.記為I)。 該狀態表示相應快取行中不包含任何記憶體地址對應的有效副本資料。 該狀態是快取條目的初始狀態。 ​ •Shared (共享的,記為s)。 該狀態表示相應快取行包含相應記憶體地址所對應的 副本資料。 並且, 其他處理器上的快取記憶體中也可能包含相同記憶體地址對應的副本資料。因此,一個快取條目的狀態如果為 Shared,並且其他處理器上也存在 Tag 值與該快取條目的 Tag 值相同的快取條目,那麼這些快取條目的狀態也為 Shared。 處於該狀態的快取條目, 其快取行中包含的資料與主記憶體中包含的資料一致。 ​ •Exclusive (獨佔的,記為 E) 。該狀態表示相應快取行包含相應記憶體地址所對應 的副本資料。 並且, 該快取行以獨佔的方式保留了相應記憶體地址的副本資料, 即 其他所有處理器上的快取記憶體當前都不保留該資料的有效副本。 處千該狀態的緩 存條目, 其快取行中包含的資料與主記憶體中包含的資料一致。 ​ •Modified (更改過的,記為 M) 。該狀態表示相應快取行包含對相應記憶體地址所做的更新結果資料。 由於 MESI 協議中的任意一個時刻只能夠有一個處理器對同一記憶體地址對應的資料進行更新, 因此在多個處理器上的快取記憶體中 Tag 值相同 的快取條目中, 任意一個時刻只能夠有一個快取條目處於該狀態。 處於該狀態的 快取條目, 其快取行中包含的資料與主記憶體中包含的資料不一致。

MESI 協議定義了一組訊息 (Message) 用於協調各個處理器的讀、 寫記憶體操作,如表11-1 所示。 比照 HTTP 協議, 我們可以將 MESI 協議中的訊息分為請求訊息和響應訊息。處理器在執行記憶體讀、寫操作時在必要的情況下會往匯流排 (Bus) 中傳送特定的請求訊息,同時每個處理器還嗅探 (Snoop,也稱攔截)匯流排中由其他處理器發出的請求訊息並在一 定條件下往匯流排中回覆相應的響應訊息。

image.png

MESI協議的處理器讀寫操作?

下面討論在Processor 0上讀取資料S的實現。

設記憶體地址A上 的資料S是處理器Processor 0和處理器Processor l可能共享的資料。

下面討論在Processor 0上讀取資料S的實現。Processor 0會根據地址A找到對應的 快取條目, 並讀取該快取條目的Tag和Flag值(快取條目狀態)。 為討論方便, 這裡我們不討論Tag值的匹配問題。Processor0找到的快取條目的狀態如果為M、 E或者s. 那麼 該處理器可以直接從相應的快取行 中讀取地址 A所對應的資料, 而無須往匯流排中傳送任何訊息。Processor 0找到的快取條目的狀態如果為I. 則說明該處理器的快取記憶體中並不 包含S的有效副本資料,此時Processor 0需要往匯流排傳送Read訊息以讀取地址A對應的 資料, 而其他處理器Processor l (或者主記憶體)則需要回復ReadResponse以提供相應的 資料

Processor 0接收到ReadResponse訊息時, 會將其中攜帶的資料(包含資料S的資料塊) 存入相應的快取行 並將相應快取條目的狀態更新為S。 Processor 0 接收到的Read Response訊息可能來自主記憶體也可能來自其他處理器(Processor I)。

Processor I會嗅探匯流排中由其他處理器傳送的訊息。Processor I嗅探到Read訊息的時候, 會從該訊息中取 出待讀取的記憶體地址.並根據該地址在其快取記憶體中查詢對應的快取條目。如果Processor I 找到的快取條目的狀態不為I (表11-2所示的情況). 則說明該處理器的快取記憶體中有待 讀取資料的副本,此時Processor l會構造相應的ReadResponse訊息並將相應快取行所儲存的整塊資料(而不僅僅是Processor0所請求的資料s),. 塞入 “ 該訊息。如果Processor1 找到的相應快取條目的狀態為M,那麼Processor1可能在往匯流排傳送ReadResponse訊息 前將相應快取行中的資料寫入主記憶體。Processor1往匯流排傳送ReadResponse之後,相應快取條目的狀態會被更新為 S。 如果 Processor I 找到的快取記憶體條目的狀態為I,那麼 Processor 0所接收到的ReadResponse訊息就來自主記憶體。

可見,在Processor0讀取記憶體 的時候,即便Processor I對相應的記憶體資料進行了更新且這種更新還停留在Processor I 的快取記憶體中而造成快取記憶體與主記憶體中的資料不一致,在MESI訊息的協調下這種不一 致也並不會導致Processor0讀取到一個過時的舊值。

討論Processor 0往地址A寫資料的實現

任何一個處理器執行記憶體寫操作時必須擁有相應資料的所有權。在執行記憶體寫操作時,Processor0會先根據記憶體地址A找到相應的快取條目。Processor0所找到的快取條目的狀態若為E或者M,則說明該處理器已經擁有相應資料的所有權,此時該處理器可以直接將資料寫入相應的快取行並將相應快取條目的狀態更新為M 。Processor0所找到的快取條目的狀態如果不為E、M,則該處理器需要往匯流排傳送Invalidate訊息以獲得資料的所有權。其他處理器接收到Invalidate訊息後會 將其快取記憶體中相應的快取條目狀態更新為I (相當於刪除相應的副本資料)並回復 Invalidate Acknowledge訊息。傳送Invalidate訊息的處理器(即記憶體寫操作的執行處理器),必須在接收到其他所有處理器所回覆的所有I nvalidate Acknowledge訊息之後再將資料更 新到相應的快取行之中.

image.png

Processor 0所找到的快取條目的狀態若為s. 則說明Processor l上的快取記憶體可能也保留了地址A對應的資料副本(場景I). 此時Processor 0需要往匯流排傳送Invalidate訊息。Processor 0在接收到其他所有處理器所回覆的InvalidateAcknowledge訊息之後會將相應的快取條目的狀態更新為 E,此時 Processor 0 獲得了地址 A 上資料的所有權。 接著,Processor 0 便可以將資料寫入相應的快取行, 並將相應快取條目的狀態更新為 M 。Processor 0 所找到的快取條目的狀態若為 I,則表示該處理器不包含地址 A 對應的有效副本資料(場景 2),此時 Processor 0 需要往匯流排傳送 Read Invalidate 訊息。Processor 0 在接收到 Read Response 訊息以及其他所有處理器所回覆的 Invalidate Acknowledge 訊息之後, 會將相應快取條目的狀態更新為E,這表示該處理器已經獲得相應資料的所有權。接 著, Processor 0 便可以往相應的快取行中寫入資料了並將相應快取條目的狀態更新為 M 。 其他處理器在接收到 Invalidate 訊息或者 Read Invalidate 訊息之後, 必須根據訊息中包含的記憶體地址在該處理器的快取記憶體中查詢相應的快取記憶體條目。若 Processor I 所找到的快取記憶體條目的狀態不為 I (場景 2),那麼 Processor I 必須將相應快取條目的狀態更新為I,以刪除相應的副本資料並給匯流排回覆 Invalidate Acknowledge 訊息。可見. Invalidate 訊息和 Invalidate Acknowledge 訊息使得針對同一個記憶體地址的寫操作在任意一個時刻只能由一個處理器執行, 從而避免了多個處理器同時更新同一資料可能導致的資料不一致問題。

從上述例子來看. 在多個執行緒共享變抵的情況下, MESI 協議已經能夠保障一個執行緒 對共享變數的更新對其他處理器上執行的執行緒來說是可見的;既然如此,可見性又何以存在呢?這就要從寫緩衝器和無效化佇列的角度來解釋了。

整理不易,喜歡請點個贊。