Java併發基礎二:CPU內存結構與MESI緩存一致性詳解

前言

在單核電腦中,處理問題要簡單的多。對內存和硬件的要求,各種方面的考慮沒有在多核的情況下複雜。電腦中,CPU的運行計算速度是非常快的,而其他硬件比如IO,網絡、內存讀取等等,跟cpu的速度比起來是差幾個數量級的。而不管任何操作,幾乎是不可能都在cpu中完成而不借助於任何其他硬件操作。所以協調cpu和各個硬件之間的速度差異是非常重要的,要不然cpu就一直在等待,浪費資源。而在多核中,不僅面臨如上問題,還有如果多個核用到了同一個數據,如何保證數據的一致性、正確性等問題,也是必須要解決的。
目前基於高速緩存的存儲交互很好的解決了cpu和內存等其他硬件之間的速度矛盾,多核情況下各個處理器(核)都要遵循一定的諸如MSI、MESI等協議來保證內存的各個處理器高速緩存和主內存的數據的一致性。

一、CPU與內存

我們知道,CPU計算的數據需要從主存獲取,計算結果需要回寫到主存,而CPU的處理效率是遠遠高於內存的(存在幾個數量級的差距) , 如何避免cpu長時間等待內存的讀寫結果造成的效率浪費呢?聰明的人類想了妙招:在CPU和內存之間加入了高速緩存處理,下面我們來聊下CPU的三級緩存。

CPU的三級緩存

現代計算機系統都加入了一層讀寫速度儘可能接近處理器運算速度的高速緩存(Cache)來作爲內存與處理器之間的緩衝:將運算需要使用的數據複製到緩存中,讓運算能快速進行,當運算結束後再從緩存同步回內存之中,這樣處理器就無須等待緩慢的內存讀寫了。在CPU中加入緩存是一種高效的解決方案,這樣整個內存儲器(緩存+內存)就變成了既有緩存的高速度,又有內存的大容量的存儲系統了。緩存對CPU的性能影響很大,主要是因爲CPU的數據交換順序和CPU與緩存間的帶寬引起的。

下圖是一個典型的存儲器層次結構,我們可以看到一共使用了三級緩存
在這裏插入圖片描述

爲追求速度上的極致,現代計算機CPU緩存通常設計爲三級緩存:L3 cache, L2cache, L1cache,其中L1,L2爲每個內核獨享,L3爲多個內核共享,高速緩存越接近CPU速度越快,同時容量也越小。緩存又可以分爲指令緩存和數據緩存,指令緩存用來緩存程序的代碼,詳見下圖:
在這裏插入圖片描述
說明:
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;

什麼是cache line(緩存行)

cache line是cache與內存數據交換的最小單位,可以簡單的理解爲CPU Cache中的最小緩存單位。根據操作系統一般是32byte或64byte。假設我們有一個512字節的一級緩存,那麼按照64B的緩存單位大小來算,這個一級緩存所能存放的緩存個數就是512/64 = 8個。在MESI協議中,狀態可以是M、E、S、I,地址則是cache line中映射的內存地址,數據則是從內存中讀取的數據。
在這裏插入圖片描述
工作方式: 當CPU從cache中讀取數據的時候,會比較地址是否相同,如果相同則檢查cache line的狀態,再決定該數據是否有效,無效則從主存中獲取數據,或者根據一致性協議發生一次cache-to–chache的數據推送(參見MESI協議,文章最後的鏈接);

工作效率: 當CPU能夠從cache中拿到有效數據的時候,消耗幾個CPU cycle,如果發生cache miss,則會消耗幾十上百個CPU cycle

二、緩存一致性問題

現代計算機一般支持多CPU,每個CPU支持多核,超線程技術使得每核可同時運行多個線程,如果同一個變量i=0,有兩個線程執行i++方法,線程1把i從內存中讀取進緩存,而現在線程2也把i讀取進緩存,兩個線程執行完i++後,線程1寫回內存,i = 1,線程2也寫回內存i = 1,兩次++結果最終值爲1,這就是著名的緩存一致性問題。

緩存一致性問題解決方案

1 總線鎖
cpu和內存在主板上面,他們是分佈在兩個不同的插槽上面,cpu訪問內存,他要通過主板上面的總線,才能去訪問內存條。 當一個CPU對其緩存中的數據進行操作的時候,往總線中發送一個Lock信號(即在消息總線上加一把鎖,就能保證一致性)。其他處理器的請求將會被阻塞,那麼該處理器可以獨佔共享內存。總線鎖相當於把CPU和內存之間的通信鎖住了,所以這種方式會導致CPU的性能下降,所以出現了另外一種方式,就是緩存鎖。
2 緩存一致性協議:MESI
緩存一致性:在多核CPU中,內存中的數據會在多個核心中存在數據副本,某一個核心發生修改操作,就產生了數據不一致的問題。而一致性協議正是用於保證多個CPU cache之間緩存共享數據的一致。硬件的緩存一致性協議比較多,其中比較經典的應該就是MESI嗅探協議 了,它的方法是在CPU緩存中保存一個標記位,這個標記爲有四種狀態:

  1. M(Modified) 修改緩存:當前CPU緩存已經被修改,表示已經和內存中的數據不一致了
  2. I(Invalid) 失效緩存:說明CPU的緩存已經不能使用了
  3. E(Exclusive) 獨佔緩存:當前cpu的緩存和內存中數據保持一直,而且其他處理器沒有緩存該數據
  4. S(Shared) 共享緩存:數據和內存中數據一致,並且該數據存在多個cpu緩存中
    在這裏插入圖片描述

cache操作

MESI協議中,每個cache的控制器不僅知道自己的操作(local read和local write),每個核心的緩存控制器通過總線監聽即“嗅探機制”(snooping) 也知道其他CPU中cache的操作(remote read和remote write),再確定自己cache中共享數據的狀態是否需要調整。

  • local read(LR):讀本地cache中的數據;
  • local write(LW):將數據寫到本地cache;
  • remote read(RR):其他核心發生read;
  • remote write(RW):其他核心發生write;

說明: CPU的讀取會遵循幾個原則:

(1)如果緩存的狀態是I,那麼就從內存中讀取,否則直接從緩存讀取
(2)如果緩存處於M或者E的CPU 嗅探到其他CPU有讀的操作,就把自己的緩存寫入到內存,並把自己的狀態設置爲S
(3)只有緩存狀態是M或E的時候,CPU纔可以修改緩存中的數據,修改後,緩存狀態變爲S
(4)每次CPU修改了cache中的數據,不會立即更新到內存,而是等到cache line在某一個必須或合適的時機纔會更新到內存中;

重點:狀態轉換和cache操作。

如上文內容所述,MESI協議中cache line數據狀態有4種,引起數據狀態轉換的CPU cache操作也有4種,因此要理解MESI協議,就要將這16種狀態轉換的情況討論清楚。

初始場景: 在最初的時候,所有CPU中都沒有數據,某一個CPU發生讀操作,此時必然發生cache miss,數據從主存中讀取到當前CPU的cache,狀態爲E(獨佔,只有當前CPU有數據,且和主存一致),此時如果有其他CPU也讀取數據,則狀態修改爲S(共享,多個CPU之間擁有相同數據,並且和主存保持一致),如果其中某一個CPU發生數據修改,那麼該CPU中數據狀態修改爲M(擁有最新數據,和主存不一致,但是以當前CPU中的爲準),其他擁有該數據的核心通過緩存控制器監聽到remote write行文,然後將自己擁有的數據的cache line狀態修改爲I(失效,和主存中的數據被認爲不一致,數據不可用應該重新獲取)。

狀態轉換動態演示:https://www.scss.tcd.ie/Jeremy.Jones/VivioJS/caches/MESIHelp.htm

(1)modify
場景:當前CPU中數據的狀態是modify,表示當前CPU中擁有最新數據,雖然主存中的數據和當前CPU中的數據不一致,但是以當前CPU中的數據爲準;

  • LR:此時如果發生local read,即當前CPU讀數據,直接從cache中獲取數據,擁有最新數據,因此狀態不變;

  • LW:直接修改本地cache數據,修改後也是當前CPU擁有最新數據,因此狀態不變;

  • RR:因爲本地內存中有最新數據,當本地cache控制器監聽到總線上有RR發生的時,必然是其他CPU發生了讀主存的操作,此時爲了保證一致性,當前CPU應該將數據寫回主存,而隨後的RR將會使得其他CPU和當前CPU擁有共同的數據,因此狀態修改爲S;

  • RW:同RR,當cache控制器監聽到總線發生RW,當前CPU會將數據寫回主存,因爲隨後的RW將會導致主存的數據修改,因此狀態修改成I;

(2)exclusive
場景:當前CPU中的數據狀態是exclusive,表示當前CPU獨佔數據(其他CPU沒有數據),並且和主存的數據一致;

  • LR:從本地cache中直接獲取數據,狀態不變;

  • LW:修改本地cache中的數據,狀態修改成M(因爲其他CPU中並沒有該數據,因此不存在共享問題,不需要通知其他CPU修改cache line的狀態爲I);

  • RR:本地cache中有最新數據,當cache控制器監聽到總線上發生RR的時候,必然是其他CPU發生了讀取主存的操作,而RR操作不會導致數據修改,因此兩個CPU中的數據和主存中的數據一致,此時cache line狀態修改爲S;

  • RW:同RR,當cache控制器監聽到總線發生RW,發生其他CPU將最新數據寫回到主存,此時爲了保證緩存一致性,當前CPU的數據狀態修改爲I;

(3)shared
場景:當前CPU中的數據狀態是shared,表示當前CPU和其他CPU共享數據,且數據在多個CPU之間一致、多個CPU之間的數據和主存一致;

  • LR:直接從cache中讀取數據,狀態不變;

  • LW:發生本地寫,並不會將數據立即寫回主存,而是在稍後的一個時間再寫回主存,因此爲了保證緩存一致性,當前CPU的cache line狀態修改爲M,並通知其他擁有該數據的CPU該數據失效,其他CPU將cache line狀態修改爲I;

  • RR:狀態不變,因爲多個CPU中的數據和主存一致;

  • RW:當監聽到總線發生了RW,意味着其他CPU發生了寫主存操作,此時本地cache中的數據既不是最新數據,和主存也不再一致,因此當前CPU的cache line狀態修改爲I;

(4)invalid
場景:當前CPU中的數據狀態是invalid,表示當前CPU中是髒數據,不可用,其他CPU可能有數據、也可能沒有數據;

  • LR:因爲當前CPU的cache line數據不可用,因此會發生讀內存,此時的情形如下。

A. 如果其他CPU中無數據則狀態修改爲E;

B. 如果其他CPU中有數據且狀態爲S或E則狀態修改爲S;

C.如果其他CPU中有數據且狀態爲M,那麼其他CPU首先發生RW將M狀態的數據寫回主存並修改狀態爲S,隨後當前CPU讀取主存數據,也將狀態修改爲S;

  • LW:因爲當前CPU的cache line數據無效,因此發生LW會直接操作本地cache,此時的情形如下。

A. 如果其他CPU中無數據,則將本地cache line的狀態修改爲M;

B. 如果其他CPU中有數據且狀態爲S或E,則修改本地cache,通知其他CPU將數據修改爲I,當前CPU中的cacheline狀態修改爲M;

C. 如果其他CPU中有數據且狀態爲M,則其他CPU首先將數據寫回主存,並將狀態修改爲I,當前CPU中的cacheline轉檯修改爲M;

  • RR:監聽到總線發生RR操作,表示有其他CPU讀取內存,和本地cache無關,狀態不變;

  • RW:監聽到總線發生RW操作,表示有其他CPU寫主存,和本地cache無關,狀態不變;

文章參考:
https://www.cnblogs.com/igoodful/p/9493156.html
https://blog.csdn.net/m18870420619/article/details/82431319
https://mp.weixin.qq.com/s/gPdxtXx17b7M8jjnc_-SGw

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章