CPU Cache結構
CPU包含多個核心,每個核心又有獨自的一級緩存(細分成代碼緩存和數據緩存)和二級緩存,各個核心之間共享三級緩存,并統一通過總線與內存進行交互
運行程序時,每個核心都有數據的一個副本(在各自的緩存中),這將會導致數據的一致性問題
Cache Line
整個Cache被分成多個Line,每個Line通常是32byte或64byte
Cache Line是Cache和內存交換數據的最小單位
每個Cache Line包含三個部分
Valid:當前緩存是否有效
Tag:對應的內存地址
Block:緩存數據
映射
主存與Cache的對應關系
將主存和Cache劃分成若干大小相等的塊
全關聯映射
主存任意一塊可以映射到Cache的任意一塊
優點:空間利用率高、命中率高
缺點:訪問存儲器時,每次都要全部查找,速度低,基本不使用
直接映射
主存中的一塊只能映射到Cache的一個特定的塊中
優點:映射簡單,訪問速度快
缺點:替換頻繁,命中率低
組相連映射
主存根據Cache大小劃分多個區,每個區劃分成多個組,每個組內劃分多個塊
Cache劃分成多個組,每個組內劃分多個塊
主存的每個區與Cache直接映射,組內采用全映射方式
替換策略
隨機
隨機確定要替換的塊
先進先出
選擇最先調入的塊進行替換
LRU(最近最少使用)
根據塊的使用情況,選擇最近最少使用的塊進行替換,反映了程序的局部性規律
寫模式
直寫(Write Through)
透過本級緩存,直接把數據寫到下一級緩存(或直接到內存)中,如果對應的段被緩存了,同時更新緩存中的內容(甚至直接丟棄)
緩存中的段永遠和它對應的內存內容匹配
寫回(Write Back)
緩存不會立即把寫操作傳遞到下一級,而是僅修改本級緩存中的數據,并且把對應的緩存段標記為“臟”段。臟段會觸發回寫,也就是把里面的內容寫到對應的內存或下一級緩存中。回寫后,臟段又變“干凈”了。當一個臟段被丟棄的時候,總是先要進行一次回寫
回寫定律:當所有的臟段被回寫后,任意級別緩存中的緩存段的內容,等同于它對應的內存中的內容
弱一致性:要么緩存段的內容和內存一致(如果緩存段是干凈的話),要么緩存段中的內容最終要回寫到內存中(對于臟緩存段來說)
優點:能過濾掉對同一地址的反復寫操作,并且,如果大多數緩存段都在回寫模式下工作,那么系統經常可以一下子寫一大片內存,而不是分成小塊來寫,前者的效率更高
一致性
單核是沒有問題的,但多核情況下,每個核都有自己的緩存,該如何確保各核緩存數據的一致性?
窺探協議
所有內存傳輸都發生在一條共享的總線上,而所有的處理器都能看到這條總線:緩存本身是獨立的,但是內存是共享資源,所有的內存訪問都要經過仲裁(arbitrate):同一個指令周期中,只有一個緩存可以讀寫內存。
緩存不僅僅在做內存傳輸的時候才和總線打交道,而是不停地在窺探總線上發生的數據交換,跟蹤其他緩存在做什么。所以當一個緩存代表它所屬的處理器去讀寫內存時,其他處理器都會得到通知,它們以此來使自己的緩存保持同步。只要某個處理器一寫內存,其他處理器馬上就知道這塊內存在它們自己的緩存中對應的段已經失效。
在直寫模式下,是很直接的,因為寫操作一旦發生,它的效果馬上會被“公布”出去,確保所有核都得到通知
在回寫模式下,就有問題了,因為有可能在寫指令執行過后很久,數據才會被真正回寫到物理內存中。在這段時間內,其他處理器的緩存也可能會去寫同一塊內存地址,導致沖突
MESI緩存一直性協議
每個Cache line有2個標志:dirty(數據是否被修改)和valid(數據是否有效)標志,描述了Cache和主存之間的數據關系
在MESI協議中,每個Cache line有4個狀態
狀態 | 描述 |
---|---|
M(Modified) | 數據有效,數據被修改了,和內存中的數據不一致,數據只存在于本Cache中 |
E(Exclusive) | 數據有效,數據和內存中的數據一致,數據只存在于本Cache中 |
S(Shared) | 數據有效,數據和內存中的數據一致,數據存在于很多Cache中 |
I(Invalid) | 這行數據無效 |
M(Modified)和E(Exclusive)狀態的Cache line,數據是獨有的,不同點在于M狀態的數據是dirty的(和內存的不一致),E狀態的數據是clean的(和內存的一致)
S(Shared)狀態的Cache line,數據和其他Core的Cache共享。只有clean的數據才能被多個Cache共享
E狀態
只有Core 0訪問變量x,它的Cache line狀態為E(Exclusive)
S狀態
3個Core都訪問變量x,它們對應的Cache line為S(Shared)狀態
M狀態和I狀態
Core 0修改了x的值之后,這個Cache line變成了M(Modified)狀態,其他Core對應的Cache line變成了I(Invalid)狀態
MESI協議狀態遷移
Local Read表示本內核讀本Cache中的值,Local Write表示本內核寫本Cache中的值,Remote Read表示其它內核讀其它Cache中的值,Remote Write表示其它內核寫其它Cache中的值,箭頭表示本Cache line狀態的遷移,環形箭頭表示狀態不變
當前狀態 | 事件 | 行為 | 下一個狀態 |
---|---|---|---|
I(Invalid) | Local Read | 如果其它Cache沒有這份數據,本Cache從內存中取數據,Cache line狀態變成E;如果其它Cache有這份數據,且狀態為M,則將數據更新到內存,本Cache再從內存中取數據,2個Cache 的Cache line狀態都變成S;如果其它Cache有這份數據,且狀態為S或者E,本Cache從內存中取數據,這些Cache 的Cache line狀態都變成S | E/S |
Local Write | 從內存中取數據,在Cache中修改,狀態變成M;如果其它Cache有這份數據,且狀態為M,則要先將數據更新到內存;如果其它Cache有這份數據,則其它Cache的Cache line狀態變成I | M | |
Remote Read | 既然是Invalid,別的核的操作與它無關 | I | |
Remote Write | 既然是Invalid,別的核的操作與它無關 | I | |
E(Exclusive) | Local Read | 從Cache中取數據,狀態不變 | E |
Local Write | 修改Cache中的數據,狀態變成M | M | |
Remote Read | 數據和其它核共用,狀態變成了S | S | |
Remote Write | 數據被修改,本Cache line不能再使用,狀態變成I | I | |
S(Shared) | Local Read | 從Cache中取數據,狀態不變 | S |
Local Write | 修改Cache中的數據,狀態變成M,其它核共享的Cache line狀態變成I | M | |
Remote Read | 狀態不變 | S | |
Remote Write | 數據被修改,本Cache line不能再使用,狀態變成I | I | |
M(Modified) | Local Read | 從Cache中取數據,狀態不變 | M |
Local Write | 修改Cache中的數據,狀態不變 | M | |
Remote Read | 這行數據被寫到內存中,使其它核能使用到最新的數據,狀態變成S | S | |
Remote Write | 這行數據被寫到內存中,使其它核能使用到最新的數據,由于其它核會修改這行數據,狀態變成I | I |
*MESI定律:在所有的臟緩存段(M狀態)被回寫后,任意緩存級別的所有緩存段中的內容,和它們對應的內存中的內容一致。此外,在任意時刻,當某個位置的內存被一個處理器加載入獨占緩存段時(E狀態),那它就不會再出現在其他任何處理器的緩存中。
偽共享(false sharing)
發生在不同處理器上的線程修改位于同一個cache line的變量的情景下(本來每個線程都訪問不同的數據,不會造成同步問題,但因為數據都處于同一cache line中,根據MESI協議,cache line中任意數據的修改都需要同步給其他內核,導致不應同步的操作變成同步了)
這會導致cache line失效并強制刷新,因此導致性能下降
解決辦法:字節對齊,將字節填滿一個cache line
Java中主要有sun.misc.Contended和Disruptor框架
參考
每個程序員都應該了解的CPU高速緩存
關于CPU Cache -- 程序猿需要知道的那些事
緩存一致性(Cache Coherency)入門
《大話處理器》Cache一致性協議之MESI
偽共享(False Sharing)
Java 7與偽共享的新仇舊恨
Java8中用sun.misc.Contended避免偽共享(false sharing)