從CPU緩存看緩存的套路

1、前言

不一樣存儲技術的訪問時間差別很大,從 計算機層次結構 可知,一般狀況下,從高層往底層走,存儲設備變得更慢、更便宜同時體積也會更大,CPU 和內存之間的速度存在着巨大的差別,此時就會想到計算機科學界中一句著名的話:計算機科學的任何一個問題,均可以經過增長一箇中間層來解決。數據庫

2、引入緩存層

爲了解決速度不匹配問題,能夠經過引入一個緩存中間層來解決問題,可是也會引入一些新的問題。現代計算機系統中,從硬件到操做系統、再到一些應用程序,絕大部分的設計都用到了著名的局部性原理,局部性一般有以下兩種不一樣的形式:緩存

時間局部性:在一個具備良好的時間局部性的程序當中,被引用過一次的內存位置,在未來一個不久的時間內極可能會被再次引用到。操作系統

空間局部性:在一個具備良好的空間局部性的程序當中,一個內存位置被引用了一次,那麼在不久的時間內極可能會引用附近的位置。線程

有上面這個局部性原理爲理論指導,爲了解決兩者速度不匹配問題就能夠在 CPU 和內存之間加一個緩存層,因而就有了以下的結構:設計

Xnip2020-08-16_22-51-12.jpg

3、什麼時候更新緩存

CPU 中引入緩存中間層後,雖然能夠解決和內存速度不一致的問題,可是同時也面臨着一個問題:當 CPU 更新了其緩存中的數據以後,要何時去寫入到內存中呢?,比較容易想到的一個解決方案就是,CPU 更新了緩存的數據以後就當即更新到內存中,也就是說當 CPU 更新了緩存的數據以後就會從上到下更新,直到內存爲止,英文稱之爲write through,這種方式的優勢是比較簡單,可是缺點也很明顯,因爲每次都須要訪問內存,因此速度會比較慢。還有一種方法就是,當 CPU 更新了緩存以後並不立刻更新到內存中去,在適當的時候再執行寫入內存的操做,由於有不少的緩存只是存儲一些中間結果,不必每次都更新到內存中去,英文稱之爲write back,這種方式的優勢是 CPU 執行更新的效率比較高,缺點就是實現起來會比較複雜。code

上面說的在適當的時候寫入內存,若是是單核 CPU 的話,能夠在緩存要被新進入的數據取代時,才更新內存,可是在多核 CPU 的狀況下就比較複雜了,因爲 CPU 的運算速度超越了 1 級緩存的數據 I\O 能力,CPU 廠商又引入了多級的緩存結構,好比常見的 L一、L二、L3 三級緩存結構,L1 和 L2 爲 CPU 核心獨有,L3 爲 CPU 共享緩存。blog

Xnip2020-08-16_23-39-28.jpg

若是如今分別有兩個線程運行在兩個不一樣的核 Core 1Core 2 上,內存中 i 的值爲 1,這兩個分別運行在兩個不一樣核上的線程要對 i 進行加 1 操做,若是不加一些限制,兩個核心同時從內存中讀取 i 的值,而後進行加 1 操做後再分別寫入內存中,可能會出現相互覆蓋的狀況,解決的方法相信你們都能想獲得,第一種是隻要有一個核心修改了緩存的數據以後,就當即把內存和其它核心更新。第二種是當一個核心修改了緩存的數據以後,就把其它一樣複製了該數據的 CPU 核心失效掉這些數據,等到合適的時機再更新,一般是下一次讀取該緩存的時候發現已經無效,才從內存中加載最新的值。ip

4、緩存一致性協議

不難看出第一種須要頻繁訪問內存更新數據,執行效率比較低,而第二種會把更新數據推遲到最後一刻纔會更新,讀取內存,效率高(相似於懶加載)。 緩存一致性協議(MESI) 就是使用第二種方案,該協議主要是保證緩存內部數據的一致,不讓系統數據混亂。MESI 是指 4 種狀態的首字母。每一個緩存存儲數據單元(Cache line)有 4 種不一樣的狀態,用 2 個 bit 表示,狀態和對應的描述以下:內存

狀態 描述 監放任務
M 修改 (Modified) 該 Cache line 有效,數據被修改了,和內存中的數據不一致,數據只存在於本 Cache 中 Cache line 必須時刻監聽全部試圖讀該緩存行相對就主存的操做,這種操做必須在緩存將該緩存行寫回主存並將狀態變成 S(共享)狀態以前被延遲執行
E 獨享、互斥 (Exclusive) 該 Cache line 有效,數據和內存中的數據一致,數據只存在於本 Cache 中 Cache line 必須監聽其它緩存讀主存中該緩存行的操做,一旦有這種操做,該緩存行須要變成 S(共享)狀態
S 共享 (Shared) 該 Cache line 有效,數據和內存中的數據一致,數據存在於不少 Cache 中 Cache line 必須監聽其它緩存使該緩存行無效或者獨享該緩存行的請求,並將該 Cache line 變成無效
I 無效 (Invalid) 該 Cache line 無效 無監放任務

下面看看基於緩存一致性協議是如何進行讀取和寫入操做的, 假設如今有一個雙核的 CPU,爲了描述方便,簡化一下只看其邏輯結構:get

Xnip2020-08-17_08-44-37.jpg

單核讀取步驟Core 0 發出一條從內存中讀取 a 的指令,從內存經過 BUS 讀取 a 到 Core 0 的緩存中,由於此時數據只在 Core 0 的緩存中,因此將 Cache line 修改成 E 狀態(獨享),該過程用示意圖表示以下:

dnmKYQ.jpg

雙核讀取步驟:首先 Core 0 發出一條從內存中讀取 a 的指令,從內存經過 BUS 讀取 a 到 Core 0 的緩存中,而後將 Cache line 置爲 E 狀態,此時 Core 1 發出一條指令,也是要從內存中讀取 a,當 Core 1 試圖從內存讀取 a 的時候, Core 0 檢測到了發生地址衝突(其它緩存讀主存中該緩存行的操做),而後 Core 0 對相關數據作出響應,a 存儲於這兩個核心 Core 0Core 1 的緩存行中,而後設置其狀態爲 S 狀態(共享),該過程示意圖以下:

dnQsoV.jpg

假設此時 Core 0 核心須要對 a 進行修改了,首先 Core 0 會將其緩存的 a 設置爲 M(修改)狀態,而後通知其它緩存了 a 的其它核 CPU(好比這裏的 Core 1)將內部緩存的 a 的狀態置爲 I(無效)狀態,最後纔對 a 進行賦值操做。該過程以下所示:

dnQxeI.jpg

細心的朋友們可能已經注意到了,上圖中內存中 a 的值(值爲 1)並不等於 Core 0 核心中緩存的最新值(值爲 2),那麼要何時纔會把該值更新到內存中去呢?就是當 Core 1 須要讀取 a 的值的時候,此時會通知 Core 0a 的修改後的最新值同步到內存(Memory)中去,在這個同步的過程當中 Core 0 中緩存的 a 的狀態會置爲 E(獨享)狀態,同步完成後將 Core 0Core 1 中緩存的 a 置爲 S(共享)狀態,示意圖描述該過程以下所示:

dn8HHA.jpg

至此,變量 aCPU 的兩個核 Core 0Core 1 中回到了 S(共享)狀態了,以上只是簡單的描述了一下大概的過程,實際上這些都是在 CPU 的硬件層面上去保證的,並且操做比較複雜。

5、總結

如今不少一些實現緩存功能的應用程序都是基於這些思想設計的,緩存把數據庫中的數據進行緩存到速度更快的內存中,能夠加快咱們應用程序的響應速度,好比咱們使用常見的 Redis 數據庫多是採用下面這些策略:① 首先應用程序從緩存中查詢數據,若是有就直接使用該數據進行相應操做後返回,若是沒有則查詢數據庫,更新緩存而且返回。② 當咱們須要更新數據時,先更新數據庫,而後再讓緩存失效,這樣下次就會先查詢數據庫再回填到緩存中去,能夠發現,實際上底層的一些思想都是相通的,不一樣的只是對於特定的場景可能須要增長一些額外的約束。基礎知識才是技術這顆大樹的根,咱們先把根栽好了,剩下的那些枝和葉都是比較容易獲得的東西了。

相關文章
相關標籤/搜索