CPU 緩存一致性協議 MESI

CPU 高速緩存(Cache Memory)

CPU 爲什麼要有高速緩存

CPU 在摩爾定律的指導下以每 18 個月翻一番的速度在發展,然而內存和硬盤的發展速度遠遠不及 CPU。這就形成了高性能能的內存和硬盤價格及其昂貴。然而 CPU 的高度運算須要高速的數據。爲了解決這個問題,CPU 廠商在 CPU 中內置了少許的高速緩存以解決 I\O 速度和 CPU 運算速度之間的不匹配問題。html

在 CPU 訪問存儲設備時,不管是存取數據抑或存取指令,都趨於彙集在一片連續的區域中,這就被稱爲局部性原理。數組

時間局部性(Temporal Locality):若是一個信息項正在被訪問,那麼在近期它極可能還會被再次訪問。

好比循環、遞歸、方法的反覆調用等。緩存

空間局部性(Spatial Locality):若是一個存儲器的位置被引用,那麼未來他附近的位置也會被引用。

好比順序執行的代碼、連續建立的兩個對象、數組等。性能

帶有高速緩存的 CPU 執行計算的流程

  1. 程序以及數據被加載到主內存
  2. 指令和數據被加載到 CPU 的高速緩存
  3. CPU 執行指令,把結果寫到高速緩存
  4. 高速緩存中的數據寫回主內存

目前流行的多級緩存結構

因爲 CPU 的運算速度超越了 1 級緩存的數據 I\O 能力,CPU 廠商又引入了多級的緩存結構。優化

多級緩存結構spa

多核 CPU 多級緩存一致性協議 MESI

多核 CPU 的狀況下有多個一級緩存,如何保證緩存內部數據的一致, 不讓系統數據混亂。這裏就引出了一個一致性的協議 MESI。設計

MESI 協議緩存狀態

MESI 是指 4 中狀態的首字母。每一個 Cache line 有 4 個狀態,可用 2 個 bit 表示,它們分別是:code

緩存行(Cache line): 緩存存儲數據的單元。
狀態 描述 監放任務
M 修改 (Modified) 該 Cache line 有效,數據被修改了,和內存中的數據不一致,數據只存在於本 Cache 中。 緩存行必須時刻監聽全部試圖讀該緩存行相對就主存的操做,這種操做必須在緩存將該緩存行寫回主存並將狀態變成 S(共享)狀態以前被延遲執行。
E 獨享、互斥 (Exclusive) 該 Cache line 有效,數據和內存中的數據一致,數據只存在於本 Cache 中。 緩存行也必須監聽其它緩存讀主存中該緩存行的操做,一旦有這種操做,該緩存行須要變成 S(共享)狀態。
S 共享 (Shared) 該 Cache line 有效,數據和內存中的數據一致,數據存在於不少 Cache 中。 緩存行也必須監聽其它緩存使該緩存行無效或者獨享該緩存行的請求,並將該緩存行變成無效(Invalid)。
I 無效 (Invalid) 該 Cache line 無效。
注意: 對於 M 和 E 狀態而言老是精確的,他們在和該緩存行的真正狀態是一致的,而 S 狀態多是非一致的。若是一個緩存將處於 S 狀態的緩存行做廢了,而另外一個緩存實際上可能已經獨享了該緩存行,可是該緩存卻不會將該緩存行升遷爲 E 狀態,這是由於其它緩存不會廣播他們做廢掉該緩存行的通知,一樣因爲緩存並無保存該緩存行的 copy 的數量,所以(即便有這種通知)也沒有辦法肯定本身是否已經獨享了該緩存行。 從上面的意義看來 E 狀態是一種投機性的優化:若是一個 CPU 想修改一個處於 S 狀態的緩存行,總線事務須要將全部該緩存行的 copy 變成 invalid 狀態,而修改 E 狀態的緩存不須要使用總線事務。 ### MESI 狀態轉換 理解該圖的前置說明: 1. 觸發事件
觸發事件 描述
本地讀取(Local read) 本地 cache 讀取本地 cache 數據
本地寫入(Local write) 本地 cache 寫入本地 cache 數據
遠端讀取(Remote read) 其餘 cache 讀取本地 cache 數據
遠端寫入(Remote write) 其餘 cache 寫入本地 cache 數據
2.cache 分類: 前提:全部的 cache 共同緩存了主內存中的某一條數據。 本地 cache: 指當前 cpu 的 cache。 觸發 cache: 觸發讀寫事件的 cache。 其餘 cache: 指既除了以上兩種以外的 cache。 注意:本地的事件觸發 本地 cache 和觸發 cache 爲相同。 上圖的切換解釋:
狀態 觸發本地讀取 觸發本地寫入 觸發遠端讀取 觸發遠端寫入
M 狀態(修改) 本地 cache:M
觸發 cache:M
其餘 cache:I
本地 cache:M
觸發 cache:M
其餘 cache:I
本地 cache:M→E→S
觸發 cache:I→S
其餘 cache:I→S
同步主內存後修改成 E 獨享, 同步觸發、其餘 cache 後本地、觸發、其餘 cache 修改成 S 共享
本地 cache:M→E→S→I
觸發 cache:I→S→E→M
其餘 cache:I→S→I
同步和讀取同樣, 同步完成後觸發 cache 改成 M,本地、其餘 cache 改成 I
E 狀態(獨享) 本地 cache:E
觸發 cache:E
其餘 cache:I
本地 cache:E→M
觸發 cache:E→M
其餘 cache:I
本地 cache 變動爲 M, 其餘 cache 狀態應當是 I(無效)
本地 cache:E→S
觸發 cache:I→S
其餘 cache:I→S
當其餘 cache 要讀取該數據時,其餘、觸發、本地 cache 都被設置爲 S(共享)
本地 cache:E→S→I
觸發 cache:I→S→E→M
其餘 cache:I→S→I
當觸發 cache 修改本地 cache 獨享數據時時,將本地、觸發、其餘 cache 修改成 S 共享. 而後觸發 cache 修改成獨享,其餘、本地 cache 修改成 I(無效),觸發 cache 再修改成 M
S 狀態 (共享) 本地 cache:S
觸發 cache:S
其餘 cache:S
本地 cache:S→E→M
觸發 cache:S→E→M
其餘 cache:S→I
當本地 cache 修改時,將本地 cache 修改成 E, 其餘 cache 修改成 I, 而後再將本地 cache 爲 M 狀態
本地 cache:S
觸發 cache:S
其餘 cache:S
本地 cache:S→I
觸發 cache:S→E→M
其餘 cache:S→I
當觸發 cache 要修改本地共享數據時,觸發 cache 修改成 E(獨享), 本地、其餘 cache 修改成 I(無效), 觸發 cache 再次修改成 M(修改)
I 狀態(無效) 本地 cache:I→S 或者 I→E
觸發 cache:I→S 或者 I →E
其餘 cache:E、M、I→S、I
本地、觸發 cache 將從 I 無效修改成 S 共享或者 E 獨享,其餘 cache 將從 E、M、I 變爲 S 或者 I
本地 cache:I→S→E→M
觸發 cache:I→S→E→M
其餘 cache:M、E、S→S→I
既然是本 cache 是 I,其餘 cache 操做與它無關 既然是本 cache 是 I,其餘 cache 操做與它無關
下圖示意了,當一個 cache line 的調整的狀態的時候,另一個 cache line 須要調整的狀態。
M E S I
M × × ×
E × × ×
S × ×
I
舉個栗子來講: 假設 cache 1 中有一個變量 x = 0 的 cache line 處於 S 狀態 (共享)。 那麼其餘擁有 x 變量的 cache 二、cache 3 等 x 的 cache line 調整爲 S 狀態(共享)或者調整爲 I 狀態(無效)。 ### 多核緩存協同操做 假設有三個 CPU A、B、C,對應三個緩存分別是 cache a、b、 c。在主內存中定義了 x 的引用值爲 0。 #### 單核讀取 那麼執行流程是: CPU A 發出了一條指令,從主內存中讀取 x。 從主內存經過 bus 讀取到緩存中(遠端讀取 Remote read), 這是該 Cache line 修改成 E 狀態(獨享). #### 雙核讀取 那麼執行流程是: CPU A 發出了一條指令,從主內存中讀取 x。 CPU A 從主內存經過 bus 讀取到 cache a 中並將該 cache line 設置爲 E 狀態。 CPU B 發出了一條指令,從主內存中讀取 x。 CPU B 試圖從主內存中讀取 x 時,CPU A 檢測到了地址衝突。這時 CPU A 對相關數據作出響應。此時 x 存儲於 cache a 和 cache b 中,x 在 chche a 和 cache b 中都被設置爲 S 狀態 (共享)。 #### 修改數據 那麼執行流程是: CPU A 計算完成後發指令須要修改 x. CPU A 將 x 設置爲 M 狀態(修改)並通知緩存了 x 的 CPU B, CPU B 將本地 cache b 中的 x 設置爲 I 狀態 (無效) CPU A 對 x 進行賦值。 #### 同步數據 那麼執行流程是: CPU B 發出了要讀取 x 的指令。 CPU B 通知 CPU A,CPU A 將修改後的數據同步到主內存時 cache a 修改成 E(獨享) CPU A 同步 CPU B 的 x, 將 cache a 和同步後 cache b 中的 x 設置爲 S 狀態(共享)。 MESI 優化和他們引入的問題 --------------- 緩存的一致性消息傳遞是要時間的,這就使其切換時會產生延遲。當一個緩存被切換狀態時其餘緩存收到消息完成各自的切換而且發出迴應消息這麼一長串的時間中 CPU 都會等待全部緩存響應完成。可能出現的阻塞都會致使各類各樣的性能問題和穩定性問題。 ### CPU 切換狀態阻塞解決 - 存儲緩存(Store Bufferes) 好比你須要修改本地緩存中的一條信息,那麼你必須將 I(無效)狀態通知到其餘擁有該緩存數據的 CPU 緩存中,而且等待確認。等待確認的過程會阻塞處理器,這會下降處理器的性能。應爲這個等待遠遠比一個指令的執行時間長的多。 #### Store Bufferes 爲了不這種 CPU 運算能力的浪費,Store Bufferes 被引入使用。處理器把它想要寫入到主存的值寫到緩存,而後繼續去處理其餘事情。當全部失效確認(Invalidate Acknowledge)都接收到時,數據纔會最終被提交。 這麼作有兩個風險 #### Store Bufferes 的風險 第1、就是處理器會嘗試從存儲緩存(Store buffer)中讀取值,但它尚未進行提交。這個的解決方案稱爲 Store Forwarding,它使得加載的時候,若是存儲緩存中存在,則進行返回。 第2、保存何時會完成,這個並無任何保證。 ` value = 3; void exeToCPUA(){ value = 10; isFinsh = true; } void exeToCPUB(){ if(isFinsh){ //value必定等於10?! assert value == 10; } } ` 試想一下開始執行時,CPU A 保存着 finished 在 E(獨享) 狀態,而 value 並無保存在它的緩存中。(例如,Invalid)。在這種狀況下,value 會比 finished 更遲地拋棄存儲緩存。徹底有可能 CPU B 讀取 finished 的值爲 true,而 value 的值不等於 10。 即 isFinsh 的賦值在 value 賦值以前。 這種在可識別的行爲中發生的變化稱爲重排序(reordings)。注意,這不意味着你的指令的位置被惡意(或者好意)地更改。 它只是意味着其餘的 CPU 會讀到跟程序中寫入的順序不同的結果。 ~順便提一下 NIO 的設計和 Store Bufferes 的設計是很是相像的。~ ### 硬件內存模型 執行失效也不是一個簡單的操做,它須要處理器去處理。另外,存儲緩存(Store Buffers)並非無窮大的,因此處理器有時須要等待失效確認的返回。這兩個操做都會使得性能大幅下降。爲了應付這種狀況,引入了失效隊列。它們的約定以下: * 對於全部的收到的 Invalidate 請求,Invalidate Acknowlege 消息必須馬上發送 * Invalidate 並不真正執行,而是被放在一個特殊的隊列中,在方便的時候纔會去執行。 * 處理器不會發送任何消息給所處理的緩存條目,直到它處理 Invalidate。 即使是這樣處理器已然不知道何時優化是容許的,而何時並不容許。 乾脆處理器將這個任務丟給了寫代碼的人。這就是內存屏障(Memory Barriers)。 > 寫屏障 Store Memory Barrier(a.k.a. ST, SMB, smp_wmb) 是一條告訴處理器在執行這以後的指令以前,應用全部已經在存儲緩存(store buffer)中的保存的指令。 > 讀屏障 Load Memory Barrier (a.k.a. LD, RMB, smp_rmb) 是一條告訴處理器在執行任何的加載前,先應用全部已經在失效隊列中的失效操做的指令。 ` void executedOnCpu0() { value = 10; //在更新數據以前必須將全部存儲緩存(store buffer)中的指令執行完畢。 storeMemoryBarrier(); finished = true; } void executedOnCpu1() { while(!finished); //在讀取以前將全部失效隊列中關於該數據的指令執行完畢。 loadMemoryBarrier(); assert value == 10; } ` ##### 引用文章 http://www.importnew.com/1058... https://www.cnblogs.com/yanlo...
相關文章
相關標籤/搜索