為什么需要存儲器層次結構?
現在cpu的執行速度和內存的速度相差過大,為了避免cpu因為等待重內存中的數據而導致指令流水線阻塞,浪費cpu資源,就出現了存儲器層次結構,cpu不從內存拿去數據,而是從一個靠近cpu內部的高速緩存中拿去數據。由於程序的執行符合局部性原理,因此我們只要使用較小的內存就可足夠了,一旦cpu訪問cache發生塊缺失,cache再從內存中找數據,如果內存中發生也缺失,那么就去外存中找數據,一旦找到了數據,內存和cache都存儲一份這個數據的副本,因為時間局部性原理,程序可能接下來還會訪問該數據。
即使是現在,cpu依然比內存快上3個的數量級,這意味着,只要訪問一次內存或者發生一次塊缺失,cpu就要阻塞上1000個時鍾周期准備數據,所以我們通過設計不同的存儲器層次結構,設計不同的cache映射方式來降低塊的缺失率;內存則設計不同的頁替換算法來降低頁的缺失,還要使用內存來作為外存的高速緩存也是一種方法。例如我的電腦:


Cache的基本原理
Cache就像一個家樓下郵箱,當我們想要獲取雜志報紙時,我們只需要下樓取就好了,而不用取郵局或者商店。降低我們獲取報紙的時間成本。
Cache的基本單位時塊,這個單位比內存單元要大,一個塊可能容納N個內存單元。這里我們假設塊的大小是一個字(64位的,也可能是32位的根據平台來定),內存是按字節編址,那么我一個塊的大小可以容納下8個內存單元。
在這一小節,我們只采用直接映射的方式,將每組內存單元中存儲的值復制到cache中,當我們要通過虛擬地址查找時,我們可以通過虛擬地址號找到放在cache中的數據。
Cache訪問
- 將虛擬地址轉換為塊地址,就是將虛擬地址 / 塊的大小。
- 塊地址 % 塊的個數,找到這個虛擬地址對應的數據存儲在第幾個塊中。
但是由於Cache也有換入換出的操作,並且當進程切換后,cache中的內容全都失效了,我們不知道這個塊對應的數據是不是我們想要的,因此我們需要增加1個valid位,還要增加一個tag標簽,這個tag標簽是塊地址中,除了第n位以下的位外的其他地址位表示的。
- 查看塊表項的valid位是否為1,如果唯一這是塊存放的是這個進程的數據,在用虛擬地址與tag相同位置的位來比較,若都相同那么這個塊中的數據就是這個虛擬地址對應的數據了。
具體的就是下面圖所標識的,不過注意一點,找到了虛擬地址所對應的塊還有一點需要補充,因為一個塊包含了多個內存單元,例如這張圖中所說明了,假定這個cache有1024個塊,cache容量位4KiB,那么data字段就是32bit,此時一個塊包含4個內存單元,要指明這個虛擬地址的數據是哪個內存單元的就需要一個偏移量,這就是虛擬地址的底兩位來標識的,2位二進程可以表示4個數0,1,2,3;data的大小是32位也就是4個Byte剛剛好能夠表示。至於為什么cache有1024個塊,cache容量4Kib,data字段就是32bit,這是因為4KiB = 1024個 * (8 * 4)Byte。

Cache缺失處理
Cache缺失時,需要Cpu的控制器和Cache的控制器共同完成操作,這是因為,cache缺失將引起流水線阻塞,這個與進程線程阻塞不同,流水線阻塞時指令因為缺乏數據無法繼續執行下去,需要等待,直到從內存中取出數據,所以在這里CPU的控制做的應該是流水線阻塞的任務,而Cache控制執行的是通知內存進行讀操作,並且將從內存中讀取的數據放入Cache中。
指令cache缺失的步驟(數據cache缺失也基本相同):
- 將程序計數器PC的原始值送到存儲器中。
- 通知主存執行一次讀操作,並等待主存完成訪問。
- 將主存取回的數據(塊),放入cache中,重新設置tag,tag的值由ALU得出,valid = 1。
- 重啟引發指令cache缺失的指令。
寫操作處理
寫操作的三種處理方法:
-
寫直達:同時寫入虛擬內存所對應的內存和cache。
這要處理下寫缺失,就是虛擬內存所對應的塊不在cache中,那么我們就要先將這個塊放入cache中,再執行寫直達。
-
寫緩沖:將數據寫入cache和寫緩沖。
由寫緩沖慢慢寫入內存,不過當寫緩沖滿時,執行store語句時cpu就要阻塞等待寫緩沖空閑位置。
-
寫回:先寫入cache中,等到該塊被替換出cache時,再將寫數據寫入內存中。
Cache的性能改進
我們可以通過提高Cache塊的大小來提高cache的命中率,只要程序符合空間局部性的要求,塊容量變大意味着我們可以把程序接下來要用的數據提前准備好了,但是提高塊容量大小意味着塊的數量減小了,塊的復用率也降低了,我們可能因為cache中缺少一個數據,導致將整個塊換出,塊中其他數據可能接下來會接着使用,到時候又要換入。還有不可忽視的一點就是塊的容量變大,那么將內存中讀的數據再存入cache需要的時間也變高了。
通過塊的放置策略改進cache缺失
-
直接映射
可以將直接映射理解為數據結構中的數組, 塊號就是整個數組的下標。 優點: 就是檢索快簡單,實現簡單,不用為了降低查找時間而增加其他硬件成本. 缺點: 就是容量小,命中率低 -
全相聯
可以將全相聯理解為數據結構中的鏈表, 全相連沒有塊號,若要檢索就要從頭開始直到找到塊或者塊缺失。 優點:容量大,cache命中率就高。 缺點:檢索費時,為了降低檢索時間,需要增加額外的硬件成本。 -
組相聯
組相聯就像使用了拉鏈法解決散列沖突的散列表, 組號相當於索引。 優點:提高cache容量,通過將一組中的多個cache塊看成一個大的cache塊的方式,提高命中率。提高塊的復用率,因為 發現塊缺失時,我們不需要將整組的cache塊全部替換,只需要根據替換算法,替換組中一個塊就好了。 缺點:比直接映射法多了檢索的時間,和額外的硬件成本開銷,比如n路選擇器,但是開銷比全相聯小。
注意一點:若通過並行檢索的方式進行全相聯和組相聯的塊檢索,那么對比直接映射的方法只提高了額外的硬件成本。

在Cache中查找塊
和之前介紹的直接映射法查找Cache塊一樣,我們通過虛擬地址提供的地址信息來查找數據,我們將虛擬地址划分為一下的組成部分:
若相聯度每翻一倍,那么Index就要減小以為,因為一個組擁有更多的塊,那么組的數量就要變少,同時Tag就要增加一位。Tag用來比對組中哪個塊是我們想要的。

具體的實現如下圖

替換塊的選擇
這個其實和分頁的替換算法差不多,頁的替換算法能用的也同樣能用在cache中,具體的見操作系統部分
使用多級cache
通過增加多級cache的組合來減少缺失代價,具體的做法就是,第一級用小容量高速的cache減小cpu的訪問時間,第二cache使用比第一級cache容量大的多的但是速度稍慢的cache解決塊缺失照成的訪存的代價,除非二級cache也缺失了,不得不訪存或者存在第三級cache,若是這樣,那訪存需要等到第三級cache也缺失。所以多級cache其實使用減少因一級cache塊缺失導致訪問內存的概率,或者說時減少訪存大代價。
1、為什么更多的相聯可以提高命中率。
首先,如果是直接映射的方法(特殊的組相聯--單組組相聯)一個內存中一個塊對應cache中一個塊,那么當相應塊只被復用很少的次數,就被內存中的另外一塊給取代了,這樣相比於采取多組相聯的模式確實命中率更低,因為多組相聯的模式種,內存中的相同一組的塊,在cache可以同時存放,這樣避免了cache塊種復用率低的概率性。
2、不管是那種減少頁表占用內存的方法, 其實主要核心我感覺都是只要添加有用的頁表項到頁表中,就是說,虛擬頁號要是沒有對應的物理頁號,那么我們就不用把這個頁表項加入頁表。頁表的作用就是提到虛擬頁號到物理頁號的映射。
