CPU 在摩爾定律的指導下以每 18 個月翻一番的速度在發展,然而內存和硬盤的發展速度遠遠不及 CPU。這就形成了高性能能的內存和硬盤價格及其昂貴。然而 CPU 的高度運算須要高速的數據。爲了解決這個問題,CPU 廠商在 CPU 中內置了少許的高速緩存以解決 I\O 速度和 CPU 運算速度之間的不匹配問題。html
在 CPU 訪問存儲設備時,不管是存取數據抑或存取指令,都趨於彙集在一片連續的區域中,這就被稱爲局部性原理。數組
時間局部性(Temporal Locality):若是一個信息項正在被訪問,那麼在近期它極可能還會被再次訪問。
好比循環、遞歸、方法的反覆調用等。緩存
空間局部性(Spatial Locality):若是一個存儲器的位置被引用,那麼未來他附近的位置也會被引用。
好比順序執行的代碼、連續建立的兩個對象、數組等。性能
因爲 CPU 的運算速度超越了 1 級緩存的數據 I\O 能力,CPU 廠商又引入了多級的緩存結構。優化
多級緩存結構spa
多核 CPU 的狀況下有多個一級緩存,如何保證緩存內部數據的一致, 不讓系統數據混亂。這裏就引出了一個一致性的協議 MESI。設計
MESI 是指 4 中狀態的首字母。每一個 Cache line 有 4 個狀態,可用 2 個 bit 表示,它們分別是:code
緩存行(Cache line): 緩存存儲數據的單元。
狀態 | 描述 | 監放任務 |
---|---|---|
M 修改 (Modified) | 該 Cache line 有效,數據被修改了,和內存中的數據不一致,數據只存在於本 Cache 中。 | 緩存行必須時刻監聽全部試圖讀該緩存行相對就主存的操做,這種操做必須在緩存將該緩存行寫回主存並將狀態變成 S(共享)狀態以前被延遲執行。 |
E 獨享、互斥 (Exclusive) | 該 Cache line 有效,數據和內存中的數據一致,數據只存在於本 Cache 中。 | 緩存行也必須監聽其它緩存讀主存中該緩存行的操做,一旦有這種操做,該緩存行須要變成 S(共享)狀態。 |
S 共享 (Shared) | 該 Cache line 有效,數據和內存中的數據一致,數據存在於不少 Cache 中。 | 緩存行也必須監聽其它緩存使該緩存行無效或者獨享該緩存行的請求,並將該緩存行變成無效(Invalid)。 |
I 無效 (Invalid) | 該 Cache line 無效。 | 無 |
觸發事件 | 描述 |
---|---|
本地讀取(Local read) | 本地 cache 讀取本地 cache 數據 |
本地寫入(Local write) | 本地 cache 寫入本地 cache 數據 |
遠端讀取(Remote read) | 其餘 cache 讀取本地 cache 數據 |
遠端寫入(Remote write) | 其餘 cache 寫入本地 cache 數據 |
狀態 | 觸發本地讀取 | 觸發本地寫入 | 觸發遠端讀取 | 觸發遠端寫入 |
---|---|---|---|---|
M 狀態(修改) | 本地 cache:M 觸發 cache:M 其餘 cache:I |
本地 cache:M 觸發 cache:M 其餘 cache:I |
本地 cache:M→E→S 觸發 cache:I→S 其餘 cache:I→S 同步主內存後修改成 E 獨享, 同步觸發、其餘 cache 後本地、觸發、其餘 cache 修改成 S 共享 |
本地 cache:M→E→S→I 觸發 cache:I→S→E→M 其餘 cache:I→S→I 同步和讀取同樣, 同步完成後觸發 cache 改成 M,本地、其餘 cache 改成 I |
E 狀態(獨享) | 本地 cache:E 觸發 cache:E 其餘 cache:I |
本地 cache:E→M 觸發 cache:E→M 其餘 cache:I 本地 cache 變動爲 M, 其餘 cache 狀態應當是 I(無效) |
本地 cache:E→S 觸發 cache:I→S 其餘 cache:I→S 當其餘 cache 要讀取該數據時,其餘、觸發、本地 cache 都被設置爲 S(共享) |
本地 cache:E→S→I 觸發 cache:I→S→E→M 其餘 cache:I→S→I 當觸發 cache 修改本地 cache 獨享數據時時,將本地、觸發、其餘 cache 修改成 S 共享. 而後觸發 cache 修改成獨享,其餘、本地 cache 修改成 I(無效),觸發 cache 再修改成 M |
S 狀態 (共享) | 本地 cache:S 觸發 cache:S 其餘 cache:S |
本地 cache:S→E→M 觸發 cache:S→E→M 其餘 cache:S→I 當本地 cache 修改時,將本地 cache 修改成 E, 其餘 cache 修改成 I, 而後再將本地 cache 爲 M 狀態 |
本地 cache:S 觸發 cache:S 其餘 cache:S |
本地 cache:S→I 觸發 cache:S→E→M 其餘 cache:S→I 當觸發 cache 要修改本地共享數據時,觸發 cache 修改成 E(獨享), 本地、其餘 cache 修改成 I(無效), 觸發 cache 再次修改成 M(修改) |
I 狀態(無效) | 本地 cache:I→S 或者 I→E 觸發 cache:I→S 或者 I →E 其餘 cache:E、M、I→S、I 本地、觸發 cache 將從 I 無效修改成 S 共享或者 E 獨享,其餘 cache 將從 E、M、I 變爲 S 或者 I |
本地 cache:I→S→E→M 觸發 cache:I→S→E→M 其餘 cache:M、E、S→S→I |
既然是本 cache 是 I,其餘 cache 操做與它無關 | 既然是本 cache 是 I,其餘 cache 操做與它無關 |
M | E | S | I | |
---|---|---|---|---|
M | × | × | × | √ |
E | × | × | × | √ |
S | × | × | √ | √ |
I | √ | √ | √ | √ |
`
value = 3; void exeToCPUA(){ value = 10; isFinsh = true; } void exeToCPUB(){ if(isFinsh){ //value必定等於10?! assert value == 10; } }
`
試想一下開始執行時,CPU A 保存着 finished 在 E(獨享) 狀態,而 value 並無保存在它的緩存中。(例如,Invalid)。在這種狀況下,value 會比 finished 更遲地拋棄存儲緩存。徹底有可能 CPU B 讀取 finished 的值爲 true,而 value 的值不等於 10。
即 isFinsh 的賦值在 value 賦值以前。 這種在可識別的行爲中發生的變化稱爲重排序(reordings)。注意,這不意味着你的指令的位置被惡意(或者好意)地更改。 它只是意味着其餘的 CPU 會讀到跟程序中寫入的順序不同的結果。 ~順便提一下 NIO 的設計和 Store Bufferes 的設計是很是相像的。~ ### 硬件內存模型 執行失效也不是一個簡單的操做,它須要處理器去處理。另外,存儲緩存(Store Buffers)並非無窮大的,因此處理器有時須要等待失效確認的返回。這兩個操做都會使得性能大幅下降。爲了應付這種狀況,引入了失效隊列。它們的約定以下: * 對於全部的收到的 Invalidate 請求,Invalidate Acknowlege 消息必須馬上發送 * Invalidate 並不真正執行,而是被放在一個特殊的隊列中,在方便的時候纔會去執行。 * 處理器不會發送任何消息給所處理的緩存條目,直到它處理 Invalidate。 即使是這樣處理器已然不知道何時優化是容許的,而何時並不容許。 乾脆處理器將這個任務丟給了寫代碼的人。這就是內存屏障(Memory Barriers)。 >
寫屏障 Store Memory Barrier(a.k.a. ST, SMB, smp_wmb) 是一條告訴處理器在執行這以後的指令以前,應用全部已經在存儲緩存(store buffer)中的保存的指令。 >
讀屏障 Load Memory Barrier (a.k.a. LD, RMB, smp_rmb) 是一條告訴處理器在執行任何的加載前,先應用全部已經在失效隊列中的失效操做的指令。
`
void executedOnCpu0() { value = 10; //在更新數據以前必須將全部存儲緩存(store buffer)中的指令執行完畢。 storeMemoryBarrier(); finished = true; } void executedOnCpu1() { while(!finished); //在讀取以前將全部失效隊列中關於該數據的指令執行完畢。 loadMemoryBarrier(); assert value == 10; }
`
##### 引用文章
http://www.importnew.com/1058...
https://www.cnblogs.com/yanlo...