CPU指令重排與快取一致性
程式中CPU計算+與記憶體互動(讀取,賦值),CPU計算速度遠遠高於與記憶體互動的速度(好比記憶體與查資料庫)引入快取提高速度,快取一致性是保證資料準確性的硬體協議。指令重排無法逾越記憶體屏障。
快取一致性的實現,先看看x86的cpu快取結構。
1、各種暫存器,用來儲存本地變數和函式引數,訪問一次需要1cycle,耗時小於1ns;
2、L1 Cache,一級快取,本地core的快取,分成32K的資料快取L1d和32k指令快取L1i,訪問L1需要3cycles,耗時大約1ns;
3、L2 Cache,二級快取,本地core的快取,被設計為L1快取與共享的L3快取之間的緩衝,大小為256K,訪問L2需要12cycles,耗時大約3ns;
4、L3 Cache,三級快取,在同插槽的所有core共享L3快取,分為多個2M的段,訪問L3需要38cycles,耗時大約12ns;
當然了,還有平時熟知的DRAM,訪問記憶體一般需要65ns,所以CPU訪問一次記憶體和快取比較起來顯得很慢。
對於不同插槽的CPU,L1和L2的資料並不共享,一般通過MESI協議保證Cache的一致性,但需要付出代價。
在MESI協議中,每個Cache line有4種狀態,分別是:
1、M(Modified)
這行資料有效,但是被修改了,和記憶體中的資料不一致,資料只存在於本Cache中
2、E(Exclusive)
這行資料有效,和記憶體中的資料一致,資料只存在於本Cache中
3、S(Shared)
這行資料有效,和記憶體中的資料一致,資料分佈在很多Cache中
4、I(Invalid)
這行資料無效
每個Core的Cache控制器不僅知道自己的讀寫操作,也監聽其它Cache的讀寫操作,假如有4個Core:
1、Core1從記憶體中載入了變數X,值為10,這時Core1中快取變數X的cache line的狀態是E;
2、Core2也從記憶體中載入了變數X,這時Core1和Core2快取變數X的cache line狀態轉化成S;
3、Core3也從記憶體中載入了變數X,然後把X設定成了20,這時Core3中快取變數X的cache line狀態轉化成M,其它Core對應的cache line變成I(無效)
當然了,不同的處理器內部細節也是不一樣的,比如Intel的core i7處理器使用從MESI中演化出的MESIF協議,F(Forward)從Share中演化而來,一個cache line如果是F狀態,可以把資料直接傳給其它核心,這裡就不糾結了。
CPU在cache line狀態的轉化期間是阻塞的,經過長時間的優化,在暫存器和L1快取之間添加了LoadBuffer、StoreBuffer來降低阻塞時間,LoadBuffer、StoreBuffer,合稱排序緩衝(Memoryordering Buffers (MOB)),Load緩衝64長度,store緩衝36長度,Buffer與L1進行資料傳輸時,CPU無須等待。
1、CPU執行load讀資料時,把讀請求放到LoadBuffer,這樣就不用等待其它CPU響應,先進行下面操作,稍後再處理這個讀請求的結果。
2、CPU執行store寫資料時,把資料寫到StoreBuffer中,待到某個適合的時間點,把StoreBuffer的資料刷到主存中。
因為StoreBuffer的存在,CPU在寫資料時,真實資料並不會立即表現到記憶體中,所以對於其它CPU是不可見的;同樣的道理,LoadBuffer中的請求也無法拿到其它CPU設定的最新資料;
由於StoreBuffer和LoadBuffer是非同步執行的,所以在外面看來,先寫後讀,還是先讀後寫,沒有嚴格的固定順序。