cache一致性裏的MESI協議

前言

在有多個核的處理器的處理器中,每一個核都有本身的cache,而如何確保多個核的cache內容的一致則是一個很容易遇到的問題,MESI協議就是一個專門用來解決cache一致性的協議。不少處理器使用的都是MESI協議或者MESI協議的變體,而MESI協議其實也是MSI協議的變種。MESI協議採用了回寫(write-back)的策略來更新cache,使得其性能進一步提升,但也帶來了額外的風險,回寫帶來的問題能夠在編寫程序時使用內存屏障來規避。java

MESI協議簡介

MESI協議名字的由來是由其描述的四個cache狀態組成的,分別是M(modified)、E(exclusive)、S(shared)和I(invalid)。各個狀態的描述具體以下:緩存

狀態 描述
Modified 當前cache的內容有效,數據已被修改並且與內存中的數據不一致,數據只在當前cache裏存在
Exclusive 當前cache的內容有效,數據與內存中的數據一致,數據只在當前cache裏存在
Shared 當前cache的內容有效,數據與內存中的數據一致,數據在多個cache裏存在
Invalid 當前cache無效

狀態轉移

MESI協議實際上是一個狀態機,cache的狀態會跟根據外部事件的刺激而發生轉移,具體的事件分爲兩類:處理器對cache的請求和總線對cache的請求:併發

  1. PrRd: 處理器請求讀一個緩存塊
  2. PrWr: 處理器請求寫一個緩存塊
  3. BusRd: 窺探器請求指出其餘處理器請求讀一個緩存塊
  4. BusRdX: 窺探器請求指出其餘處理器請求寫一個該處理器不擁有的緩存塊
  5. BusUpgr: 窺探器請求指出其餘處理器請求寫一個該處理器擁有的緩存塊
  6. Flush: 窺探器請求指出請求回寫整個緩存到主存
  7. FlushOpt: 窺探器請求指出整個緩存塊被髮到總線以發送給另一個處理器(緩存到緩存的複製)

而狀態之間的轉換以下圖:性能

處理器操做帶來的狀態轉化
初始狀態 操做 響應
Invalid(I) PrRd
  • 給總線發BusRd信號
  • 其餘處理器看到BusRd,檢查本身是否有有效的數據副本,通知發出請求的緩存
  • 若是其餘緩存有有效的副本,狀態轉換爲(S)Shared
  • 若是其餘緩存都沒有有效的副本,狀態轉換爲(E)Exclusive
  • 若是其餘緩存有有效的副本, 其中一個緩存發出數據;不然從主存得到數據
PrWr
  • 給總線發BusRdX信號
  • 狀態轉換爲(M)Modified
  • 若是其餘緩存有有效的副本, 其中一個緩存發出數據;不然從主存得到數據
  • 若是其餘緩存有有效的副本, 見到BusRdX信號後無效其副本
  • 向緩存塊中寫入修改後的值
Exclusive(E) PrRd
  • 無總線事務生成
  • 狀態保持不變
  • 讀操做爲緩存命中
PrWr
  • 無總線事務生成
  • 狀態轉換爲(M)Modified
  • 向緩存塊中寫入修改後的值
Shared(S) PrRd
  • 無總線事務生成
  • 狀態保持不變
  • 讀操做爲緩存命中
PrWr
  • 發出總線事務BusUpgr信號
  • 狀態轉換爲(M)Modified
  • 其餘緩存看到BusUpgr總線信號,標記其副本爲(I)Invalid.
Modified(M) PrRd
  • 無總線事務生成
  • 狀態保持不變
  • 讀操做爲緩存命中
PrWr
  • 無總線事務生成
  • 狀態保持不變
  • 寫操做爲緩存命中
不一樣總線操做帶來的狀態轉化
初始狀態 操做 響應
Invalid(I) BusRd
  • 狀態保持不變,信號忽略
BusRdX/BusUpgr
  • 狀態保持不變,信號忽略
Exclusive(E) BusRd
  • 狀態變爲共享
  • 發出總線FlushOpt信號併發出塊的內容
BusRdX
  • 狀態變爲無效
  • 發出總線FlushOpt信號併發出塊的內容
Shared(S) BusRd
  • 狀態變爲共享
  • 可能發出總線FlushOpt信號併發出塊的內容(設計時決定那個共享的緩存發出數據)
BusRdX
  • 狀態變爲無效
  • 可能發出總線FlushOpt信號併發出塊的內容(設計時決定那個共享的緩存發出數據)
Modified(M) BusRd
  • 狀態變爲共享
  • 發出總線FlushOpt信號併發出塊的內容,接收者爲最初發出BusRd的緩存與主存控制器(回寫主存)
BusRdX
  • 狀態變爲無效
  • 發出總線FlushOpt信號併發出塊的內容,接收者爲最初發出BusRd的緩存與主存控制器(回寫主存)

內存屏障的引入

MESI的設計比較簡單直接,可是其中有兩個地方會致使性能降低:一是更新invalidate狀態的cache時,須要嘗試從其餘cpu甚至是內存獲取最新的數據;二是使一個cache變爲invalidate時須要等待其餘cpu的確認;這兩個操做都是比較耗時的,若是cpu在這兩個過程當中一直等待的話,就會造成浪費。優化

store buffer

爲了下降寫入invalidate狀態的cache的延時,能夠引入store buffer。既然寫入操做不管如何必定會發生,那麼cpu就先發出信號通知其餘cpu這個cache已經失效,而後再將本次的寫操做更新到store buffer中,等到其餘cpu都確認收到信號後再將結果寫到內存中。spa

這樣就避免了更新cache時阻塞等待其餘cpu確認的耗時,可是也會致使cpu的更新並無及時寫入cache,因此當cpu須要讀取cache時,它須要先確認store buffer中是否有所需的數據,這個機制成爲store forwarding。值得注意的是,當cpu在讀寫本身的store buffer時,對應的數據變動其餘cpu是感知不到的。設計

invalidate queue

當cpu收到使某個cache失效的消息時,預期的行爲是cpu立刻執行這個失效操做。但實際上cpu並不會立刻執行失效操做,而是先發送確認收到的消息,而後將失效操做加入到invalidate queue中,queue中的操做隨後會在適當的時刻執行(並不必定是立刻)。之因此須要invalidate queue一樣是由於invalidate操做開銷比較大,cpu爲了執行invalidate操做必須丟棄cache,致使cache命中率降低。這樣的好處是可以提升cpu的性能,但同時也致使cache中可能存在過時的數據。3d

內存屏障

針對store buffer和invalidate queue這兩個優化帶來的問題,咱們又提供了內存屏障做爲解決方案。內存屏障交給了編寫程序的人的手裏,利用它就能夠規避上面提到的問題。cdn

內存屏障分爲寫屏障和讀屏障,編寫程序時能夠在指望的地方加入內存屏障。寫屏障會強制cpu清空store buffer的內容,也就是將全部的變動都寫入cache,隨後變動也就寫入了內存,使其對其餘cpu可見;讀屏障會強制cpu執行invalidate queue中的全部invalidate操做,使自身的cache內容失效,從而使cpu從內存或者其餘cpu中獲取最新的cache數據。blog

其餘

MESI協議乍一看和java裏的內存模型以及volatile關鍵字有些類似,後續再詳細展開了。

相關文章
相關標籤/搜索