寫命中
寫直達(Write Through)
信息會被同時寫到cache的塊和主存中。這樣做雖然比較慢,但缺少代價小,不需要把整個塊都寫回主存。也不會發生一致性問題。
對於寫直達,多出來%10向主存寫入的存儲指令使得其比其單純向Cache寫入的速度慢上將近10倍。這種速度不一致的問題,不管是在硬件結構還是軟件,有着一條“不管怎么樣,先試試這樣行不行”的辦法:並行加緩沖。
我們使用寫緩沖(Write Buffer)來解決這個問題,CPU寫入Cache的同時會寫入Write Buffer。緩沖中的內容什么時候寫入主存交給存控(Memory Controller)來控制,CPU將省下的時間去處理其他事情。
Write Buffer是一個FIFO隊列,一般只有4字節。
然而當寫操作頻繁的時候,這點容量就不夠用了,容易飽和發生阻塞,就相當於沒加一樣...
解決的辦法可以是再加一級Cache,變成二級緩存。什么,你問為什么不把兩級Cache和一起,搞那么多干什么?這個二級當然是慢一點的便宜貨,咱們弄這些東西,不就是因為越快東西越貴,買不起嘛土豪!
對於緩沖的問題,還有個關於合並寫對程序效率的影響, 具體可以參考這篇博文。
第二種辦法就是改變策略使用寫回,也就是下面介紹的方法。
寫回(Write Back)
信息僅僅寫到Cache中的塊。當其被替換時,信息才會被寫回到主存中。虛擬存儲器系統通常采用寫回策略,因為寫到磁盤的延遲代價太大。
寫回的速度要更快一些,因為不必每次寫操作都訪問主存。但這樣我們如何保證一致性問題呢?我們可以給每行添加一個臟位(dirty bit),這樣我們替換這塊Cache時就可以根據臟位來判斷是否需要寫回主存。如果沒有被“弄臟過”,那么就不需要寫回主存。
不過對於同一塊Cache中的變量X,他不是太喜歡這個機制。因為它的鄰居Y老是被修改,導致X這個只被讀取的變量老得往內存跑,它不想跟Y待在一起了,太累人了。
聆聽了X的心聲,我們有什么辦法可以幫助它嗎?辦法當然是有的,讓Y這個煩人的家伙單獨待着就行。下面分別運行兩個程序,排除首次裝入的影響(其實寫一塊也行,對齊的技巧源自Disruptor)
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);
}
}
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更新相應單元。后者則是直接寫主存單元,不將主存塊裝入Cache。