對cache的小結,參考:Computer System A programmer's Perspective 原版_第二版
x86查看cache信息: sudo dmidecode -t cache
locality:操作近期被其它指令所操作、或本身所操作的數或指令。
good temporal locality:被引用過的存儲器在近期被多次引用
good spatial locality:被引用過的存儲器的相鄰存儲器在近期被多次引用
數據的局部性適用於指令的局部性,但是我們很少修改指令,對於數據卻是頻繁的改動。
利用temporal locality,當發生第一次時出現miss,以后可以hit;利用spatial locality,當發生第一次時出現miss,將data所在的block中所有數據均放到cache中,再次取其臨近數據時可以hit。
將K+1級的存儲分成數個block,K級存儲也划分成數個block,且K級block與K+1級block大小相同。K+1級與K級之間以block為數據量單位進行傳輸(相鄰級的block大小相同,非相鄰級其block不一定相同,低級的block較大,而高級的block較小)
cache的組成:
存取一個內存中的數據時需給出地址,假設為m位,那么:
- 將cache划分成2^s個sets;
- 每個set包含E個cache lines (block是傳輸的定長信息,cache line是block的容器,其中不僅存放block,而且還有其它存儲單元,例如有效位——valid指示該line 中block是否有效,標簽位——tag 來匹配標簽,臟標志——dirty指示block數據是否發生變化……另外set是幾個line的集合);
- 每個cache line中的block的數據偏移量2^b;
- t=m-(s+b)每個cache有t位tag bits,來識別每個set中的cache line。
注意:1.m位地址被划分為 Tag:Set index:Block offset
解釋一下為什么會這樣划分:
如果Set Index在高位,那么大量的block極易映射到同一Set中,這樣造成的conflict misses概率較大,cache性能會下降。Set Index更不可以放在低位,應為這樣會造成緩存在block中的數據不能得到有效應用,例如:從0地址讀一整型,假設block為16 bytes,那么4,8,12處數據將被同時放入cache,在讀4處數據,此時由於Set Index為低位造成不從cache讀取,而仍舊從內存讀取,造成cache沒有得到有效應用。所以Set Index在中間。
2.實際上set所需的s位在硬件中並不存在。
cache組成可簡單的以組元描述(S、E、B、m)
cache容量C=S*E*B
cache hit:若欲從K+1級模塊中讀d,則先在k的某塊中尋找d,若找到,則cache hit
cache miss:與上相反。
對於cache miss,會將K+1級中的d所在的塊替換K級模塊(已滿),或者調入K級模塊。若是替換,則將此過程稱為replacing或者evicting,被替換的塊稱為victiming block。決定哪一塊被替換,則稱為replacement policy
cache misses 分類:
cold misses:不可避免。若K級cache空,則必發生cache miss,空的cache稱為cold cache,這種cache misses稱為compulsory misses或者cold misses。當cache已被warmed up則一般不會再發生cold misses。
conflict misses:多個K+1級的blocks被映射到K級中同一個block。這一點關系到對於程序員而言能否寫出cache友好代碼。
程序常會分階段執行(例如循環:內層、外層),每個階段會取cache blocks的固定幾個塊,
這幾個塊所構成的集合稱為working set。
capacity misses:當working set超過cache大小時所發生的miss稱為capacity misses。
發生cache misses時,K級Cache需實現placement policy來決定將K+1級的block放入K級的哪個block中。策略:
- 隨機(代價太大,不能實現cache的用途);
- K+1級的某block只能被映射到K級的blocks中的某一個set中。
cache映射方式(E值):
E值較大的cache,其conflict miss概率小,但是代價太高(功耗、工藝、電路……),一般情況下K+1級的E值>K級E值。
1.E=1:Direct_Mapped Caches (每個set中只有一個cache line)
K+1級的block只能映射到K級相應set中唯一一個block。對於Directed_Mapped Caches發生misses時的替換策略非常簡單:Set所在行必被替換。Directed_Mapped Caches中發生confilct misses的概率較大,cache性能沒有得到充分利用,會造成thrashing:在一個cache中不停的loading或evicting。
2.E=C/B,S=1:Fully Associative Caches
對於這種cache,其電路結構復雜(因為tag需進行大量匹配測試,所以代價較高,因此一般只做比較小的cache,例如TLB)
3.1<E<C/B,稱為E-way set associative cache
可以使得K+1級的cache映射到K級set中的某一個block。對於Set Associative Caches發生misses的line replacement該采取何種策略:若Set中有空或無效則直接替換;若Set中已滿,則:隨機(代價高)、LFU、LRU
上面討論了讀,以下總結針對寫。
若要寫的字w已在K級cache中,則稱write hit,此時該采用何種策略更新K+1級中的w?
policy:
1.write-through:立即將w所在的K級cache block寫回K+1級cache block 中,這回產生cache bus traffic,因此我們可以加入write buffer 來保存要更新的地址及其中的數據,write buffer由內存控制器自己控制,其是先進先出結構,以減少更新次數。據統計沒10條指令將會執行一次寫操作,因此在低速的CPU,這可以很好的工作,但是當CPU頻率上升時,存儲系統已不能以CPU的平均寫速率來消化寫操作。
2.write-back:盡量推遲將w所在的K級cache block寫回K+1級cache block中,只有當K級block被evicted時才將更新的塊寫入K+1相應塊中,這需要為每個block添加dirty bit位來指示block是否被更改。write-back策略可以顯著的減少傳輸次數,由於越往下級的cache其傳輸時間更長,因此越往下級的cache更傾向於采用write-back而不是write-through(當然我們也可以采用write-through配以write-buffer來更加有效的使用cache)。
若要寫的字w不在K級cache中,則稱write misses,此時該采用何種策略更新K+1級中的w?
1.write_allocate:把K+1級相應先寫入K級相應塊,再更新已在K級的塊。這樣可以利用空間局部性。但是對於這種方式,每個misses都將數據由K+1先寫入K級,要求cache性能高。
2.no_write_allocate:不經K級cache,而直接寫入K+1級cache。
2012_11_18,讀了老板的論文,記錄下:
當處理器內核提出寫操作的地址空間暫時沒有被Cache緩沖時,即Cache發生寫不命中時,其行為又可以分為寫分配、讀分配和讀寫分配三種策略。所謂讀分配是指
數據僅在讀操作不命中時由Cache進行行填充,從外存裝載到Cache中;寫分配是指數據僅在寫不命中時由Cache從外存裝載數據到Cache中;讀寫分配策略則不管時讀缺失還是寫缺失都將導致Cache的獲取邏輯將數據從主存轉載到Cache行中。
cache友好代碼:
在一個對連續矢量操作的過程中,每隔K個元素進行一次操作稱為stride-k pattern。當K變大時則空間布局性變差。從此處我們也可以看出數組元數在內存中的存放規則將影響我們的所采用算法的性能。
若cache的block size為B bytes,一個stride-k(以words為單位)pattern導致平均錯失率為min(1,(wordsize*k)/B)。