1. 程式人生 > >Cache寫策略(Cache一致性問題與騷操作)

Cache寫策略(Cache一致性問題與騷操作)

## 寫命中 ### 寫直達(Write Through) 資訊會被同時寫到cache的塊和主存中。這樣做雖然比較慢,但缺少代價小,不需要把整個塊都寫回主存。也不會發生一致性問題。 對於寫直達,多出來%10向主存寫入的儲存指令使得其比其單純向Cache寫入的速度慢上將近10倍。這種速度不一致的問題,不管是在硬體結構還是軟體,有著一條“不管怎麼樣,先試試這樣行不行”的辦法:並行加緩衝。 ![Write Buffer](https://img2020.cnblogs.com/blog/1932912/202007/1932912-20200704163129743-2038047854.png) 我們使用寫緩衝(Write Buffer)來解決這個問題,CPU寫入Cache的同時會寫入Write Buffer。緩衝中的內容什麼時候寫入主存交給存控(Memory Controller)來控制,CPU將省下的時間去處理其他事情。 > Write Buffer是一個FIFO佇列,一般只有4位元組。 然而當寫操作頻繁的時候,這點容量就不夠用了,容易飽和發生阻塞,就相當於沒加一樣... 解決的辦法可以是再加一級Cache,變成二級快取。什麼,你問為什麼不把兩級Cache和一起,搞那麼多幹什麼?這個二級當然是慢一點的便宜貨,咱們弄這些東西,不就是因為越快東西越貴,買不起嘛土豪! 對於緩衝的問題,還有個關於合併寫對程式效率的影響, [具體可以參考這篇博文](https://www.cnblogs.com/liushaodong/p/4777308.html)。 第二種辦法就是改變策略使用寫回,也就是下面介紹的方法。 ### 寫回(Write Back) 資訊僅僅寫到Cache中的塊。當其被替換時,資訊才會被寫回到主存中。虛擬儲存器系統通常採用寫回策略,因為寫到磁碟的延遲代價太大。 寫回的速度要更快一些,因為不必每次寫操作都訪問主存。但這樣我們如何保證一致性問題呢?我們可以給每行新增一個髒位(dirty bit),這樣我們替換這塊Cache時就可以根據髒位來判斷是否需要寫回主存。如果沒有被“弄髒過”,那麼就不需要寫回主存。 不過對於同一塊Cache中的變數X,他不是太喜歡這個機制。因為它的鄰居Y老是被修改,導致X這個只被讀取的變數老得往記憶體跑,它不想跟Y待在一起了,太累人了。 聆聽了X的心聲,我們有什麼辦法可以幫助它嗎?辦法當然是有的,讓Y這個煩人的傢伙單獨待著就行。下面分別執行兩個程式,排除首次裝入的影響(其實寫一塊也行,對齊的技巧源自Disruptor) ```java public class Padding { private static class X { public long p1,p2,p3,p4,p5,p6,p7;//cache padding public volatile long x=0L; public long p9,p10,p11,p12,p13,p14,p15; //8*8剛好佔滿Cache一行,p9...p15只是為了確保x單獨在一行中,不與其他頻繁修改的變數在一起 } public static X[] arrX=new X[2]; static { arrX[0]=new X(); arrX[1]=new X(); } public static void main(String[] args) throws InterruptedException { Thread thread1=new Thread(()->{ for(long i=0;i<100_000_000L;i++) arrX[0].x=i; }); Thread thread2=new Thread(()->{ for(long i=0;i<100_000_000L;i++) arrX[0].x=i; }); final long start=System.nanoTime(); thread1.start(); thread2.start(); thread1.join(); thread2.join(); System.out.println((System.nanoTime()-start)/1_000_000); } } ``` ```java package mytask; public class NoPadding { private static class X { public volatile long x=0L; } public static X[] arrX=new X[2]; static { arrX[0]=new X(); arrX[1]=new X(); } public static void main(String[] args) throws InterruptedException { Thread thread1=new Thread(()->
{ for(long i=0;i<100_000_000L;i++) arrX[0].x=i; }); Thread thread2=new Thread(()->{ for(long i=0;i<100_000_000L;i++) arrX[0].x=i; }); final long start=System.nanoTime(); thread1.start(); thread2.start(); thread1.join(); thread2.join(); System.out.println((System.nanoTime()-start)/1_000_000); } } ``` ## 寫不命中 對於寫不命中,有兩者方法:寫分配與非寫分配。前者利用空間區域性性,每次都從主存中讀取一個塊裝入Cache更新相應單元。後者則是直接寫主存單元,不將主存塊裝入C