CPU 多級緩存 & 緩存一致性協(xié)議(MESI)
CPU 多級緩存
- 參考:Java Memory Model
緩存一致性協(xié)議(MESI)
- 多級緩存的出現(xiàn)解決了CPU處理速度和內(nèi)存讀取速度不一致的問題,但是同時也帶來緩存不一致的問題,為了解決這個問題,我們引入了緩存一致性協(xié)議,常見的緩存一致性協(xié)議有MSI,MESI,MOSI,Synapse,F(xiàn)irefly及DragonProtocol等等,下文以MESI協(xié)議進行講述。
緩存行(Cache line)
- 緩存行是指在緩存中的最小數(shù)據(jù)單元。
四種緩存狀態(tài)
- 緩存行有4個狀態(tài),用2個bit表示。
狀態(tài) | 描述 | 監(jiān)聽任務(wù) |
---|---|---|
E 獨享 | 該Cache line有效,數(shù)據(jù)被修改,和內(nèi)存數(shù)據(jù)一致,數(shù)據(jù)只存在本Cahe中 | 必須監(jiān)聽所有試圖讀該緩存行的操作,操作必須在該緩存行寫回主存并將狀態(tài)變?yōu)镾后執(zhí)行 |
M 修改 | 該Cache line有效,數(shù)據(jù)被修改,和內(nèi)存數(shù)據(jù)不一致,數(shù)據(jù)只存在本Cahe中 | 必須監(jiān)聽所有試圖讀該緩存行的操作,操作必須在該緩存行寫回主存并將狀態(tài)變?yōu)镾后執(zhí)行 |
S 共享 | 該Cache line有效,數(shù)據(jù)和內(nèi)存數(shù)據(jù)一致,數(shù)據(jù)存在多個Cache中 | 必須監(jiān)聽其它緩存使該緩存無效或獨享該緩存的請求,并將該緩存行變?yōu)闊o效 |
I 失效 | 該Cache line無效 | 無 |
- 注:對于M和E狀態(tài)而言總是精確的,他們在和該緩存行的真正狀態(tài)是一致的,而S狀態(tài)可能是非一致的。如果一個處于S狀態(tài)的緩存失效,另外一個緩存行可能已經(jīng)獨享了該緩存行,但是不會升遷為獨享狀態(tài),因為失效并不會廣播給其它緩存行。
緩存行狀態(tài)轉(zhuǎn)換
多核協(xié)同示例
- 初始狀態(tài):CPUB 存在緩存變量 X 狀態(tài)為 M
- CPUA 發(fā)出指令讀取 X 指令,通過 bus 讀取 X,檢測到地址沖突,將 CPUB 緩存變量狀態(tài)置為 S,讀取 X 到 CPUA 完成
- 此時,CPUB 修改緩存變量并通過 bus 寫回主存,發(fā)現(xiàn)地址沖突,將 CPUA 中的變量從 S 狀態(tài)置為 I,數(shù)據(jù)寫回主存
網(wǎng)站體驗
- 模擬一致性的整個過程:https://www.scss.tcd.ie/Jeremy.Jones/VivioJS/caches/MESIHelp.htm
MESI優(yōu)化和引入的問題
- 在上述多核CPU為保證緩存一致性進行協(xié)同的過程中,消息傳遞的時間遠遠大于CPU執(zhí)行時間,如果每次的操作都需要等待協(xié)同指令響應(yīng)完成,那么就會大大降低處理器的處理性能,因此引入了Store Bufferes和Invalidate Queue進行優(yōu)化。
Store Bufferes & Invalidate Queue
- 從上述的多核協(xié)同案例中我們可以發(fā)現(xiàn),每次修改緩存中的元素,都需要將無效狀態(tài)指令(Invalidate Acknowledge)執(zhí)行完才能將修改的數(shù)據(jù)寫回緩存行中,等待協(xié)同指令會造成CPU運算能力浪費,因此,Store Bufferes被引入,我們不需要等待協(xié)同指令返回就可以將修改的數(shù)據(jù)寫入Store Bufferes,當再次讀取時若在Store Bufferes中已存在直接從Buffer中讀取(稱為“Store Forwarding”),只有當收到所有協(xié)同指令響應(yīng)后才能寫回緩存行中。
- Store Bufferes 是有限的,因此當要寫回緩存行時為了更快的得到所有Invalidate Acknowledge指令的響應(yīng),實際上也不會立即執(zhí)行,而是放入了Invalidate Queue中,并立即返回響應(yīng),在合適的時機去執(zhí)行。
Store Bufferes & Invalidate Queue 帶來的問題
- Store buffer 什么時候?qū)懟夭]有保證
value = 3;
void exeToCPUA(){
value = 10;
isFinsh = true;
}
void exeToCPUB(){
if(isFinsh){
// value 一定等于10?
// 如果 Store Bufferes 沒有寫回那么將導(dǎo)致數(shù)據(jù)不一致
assert value == 10;
}
}
- Invalidate Acknowledge 什么時候執(zhí)行沒有保證
// 當一個CPU嘗試讀取實際已經(jīng)失效但未執(zhí)行 Invalidate Acknowledge 的數(shù)據(jù)時,會導(dǎo)致數(shù)據(jù)不一致
硬件內(nèi)存模型
- 由于 Store Bufferes & Invalidate Queue 的引入,導(dǎo)致 Store Bufferes 寫入緩存行和執(zhí)行 Invalidate Acknowledge 的時機需要十分合適才能盡可能釋放CPU的處理能力,實際上CPU并不知道什么時候會執(zhí)行,因此將這個任務(wù)留給了寫程序的人,這就是我們常說的內(nèi)存屏障。
讀屏障 & 寫屏障
-
寫屏障 Store Memory Barrier(a.k.a. ST, SMB, smp_wmb)是一條告訴處理器在執(zhí)行這之后的指令之前,應(yīng)用所有已經(jīng)在Store buffer中的保存的指令到緩存行中。文章來源:http://www.zghlxwxcb.cn/news/detail-743166.html
-
讀屏障Load Memory Barrier (a.k.a. LD, RMB, smp_rmb)是一條告訴處理器在執(zhí)行任何的加載前,應(yīng)用所有已經(jīng)在失效隊列中的失效操作的指令。文章來源地址http://www.zghlxwxcb.cn/news/detail-743166.html
void executedOnCpu0() {
value = 10;
// 在更新數(shù)據(jù)之前必須將所有存儲緩存(store buffer)中的指令執(zhí)行完畢。
storeMemoryBarrier();
finished = true;
}
void executedOnCpu1() {
while(!finished);
// 在讀取之前將所有失效隊列中關(guān)于該數(shù)據(jù)的指令執(zhí)行完畢。
loadMemoryBarrier();
assert value == 10;
}
思考 & 聯(lián)系
- 不同的系統(tǒng)架構(gòu)有不同的內(nèi)存屏障,以X86架構(gòu)為例:讀屏障:lfence、寫屏障:sfence、讀寫屏障:mfence。
- MESI 緩存一致性協(xié)議中為了盡可能的提高性能,引入了 Store Bufferes & Invalidate Queue ,將數(shù)據(jù)具體的失效時機和寫入時間交給了內(nèi)存屏障控制,而 JMM 則基于內(nèi)存屏障保證數(shù)據(jù)的可見性。
- volatile 關(guān)鍵字底層使用了LOCK關(guān)鍵字,LOCK關(guān)鍵字的本質(zhì)是鎖(總線鎖或緩存行鎖),只是LOCK關(guān)鍵字的一部分能力具備和內(nèi)存屏障相同的作用,但是和內(nèi)存屏障還是有一定區(qū)別。
到了這里,關(guān)于一文讀懂從 CPU 多級緩存 & 緩存一致性協(xié)議(MESI)到 Java 內(nèi)存模型的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!