一、程序的運行
馮諾依曼機的運行大致可分為兩個步驟,存儲程序和程序控制。具體而言,可分為以下步驟:
(1)輸入設備將程序與數據寫入內存;
(2)CPU取指令;
(3)CPU執行指令期間讀數據;
(4)CPU寫回運算結果;
(5)輸出設備輸出結果。
二、存儲系統層次結構
由於CPU和主存的運行速度相差較大,程序運行的速度就會受制於內存的速度。於是,存儲系統引入了高速緩存(Cache)。Cache的引入使得CPU訪問到的存儲系統具有Cache的速度,賦存的容量和價格。
同時,Cache又可以划分為L1 Cache和L2 Cache。其中L1 Cache集成在CPU中,分數據分數據Cache(D-Cache)和指令Cache(I-Cache)。早期L2 Cache在主板上或與CPU集成在同一電路板上。隨着工藝的提高L2 Cache被集成在CPU內核中,不分D-Cache和I-Cache。
三、主存中的數據
數據存放在內存中,分為按邊界存放和未按邊界存放。
(1)按邊界存放
按邊界存放的方式,在32位系統中,存儲單元為4個字節。int占4個字節,short占兩個字節,double占8個字節,char占1個字節。會產生空間空余。
(2)未按邊界存放
未按邊界存放充分利用了空間,但是增加了內存的訪問次數。如double類型的x,占了三個存儲單元,需要訪問三次。因此,這種方式雖然節省了空間,但增加了訪問內存的次數。
四、主存中的數據組織
知道了數據在內存中的存放方式,具體到代碼層面有什么影響呢?
如下兩種結構體的寫法:
struct S1{ int i; char c; int j } struct S2{ int i; int j char c; }
在邊界對齊的情況下,這些數據在內存中的存儲如下圖所示。可以看到S1的結構體寫法占了12個字節,S2的結構體占了9個字節。但就一個結構體的數據而言,代碼的寫法確實可以節省空間。
五、Cache的基本原理
Cache主要是用來解決CPU與慢速的主存之間的速度差異。Cache的工作原理分為讀操作和寫操作。
(1)讀操作
如果Cache中已經有了該數據,就直接讀入,這個過程稱為命中(HIT)。如果Cache中沒有這個數據,就會從內存中讀取一個數據塊緩存到Cache中,接着從Cache中讀取到CPU,這個過程稱為缺失(MISS)。缺失會造成訪問速度急劇下降。
(2)寫操作
寫操作有兩種策略,寫穿策略(WriteThrough)和寫回策略(WriteBack)。寫穿策略是CPU向Cache中寫數據后,將Cache中的數據寫入到主存。寫回策略是CPU向Cache中寫數據后,不向主存寫入數據。寫回策略速度很快,但是數據的更新沒有刷新到主存。
六、Cache的地址映射機制
主存地址通常按照塊地址進行划分,Cache地址通常按照行進行划分,主存塊大小和Cache中的行大小相等。主存地址通常划分為三個部分,Tag用來判斷該數據是否在Cache中,Index用來找Cache中對於的數據位,塊內偏移用來查找對應數據。Cache的結構如下圖所示,Tag與主存Tag一致,Data用來存儲一行數據,Valid表示Cache中的數據是否有效,Dirty表示主存中的數據是否是最新的。
主存與Cache地址映射通常有三種方式,全相聯(fully-associated)、直接相聯(direct mapped)和組相聯(set-associated)。
(1)全映射
主存地址變成二維(塊號 塊內地址),如地址61,二進制是00111101,將它划分為兩部分 001111和01,其中001111是塊號,01是塊內地址。全映射的映射算法是主存的數據庫可以映射到Cache的任意行,同時將該數據塊地址對應行的標記存儲體中保存。
全映射的過程如上圖所示,若CPU的訪問序列順序為1F 20 24 1E。首先,對於1F地址,二進制為0001 1111,划分為二維后tag=000111,offset=11即第四哥單元。首先回判斷tag為000111是否命中,一開始是缺失的,因此添加tag為000111的單元,並把有效位置為1,同時從主存中讀取一個數據塊1C 1D 1E 1F到Cache中。其中偏移地址為11的1F即可訪問。20和24的訪問同理可得。當訪問到1E的時候,tag為000111的單元已經存在,此時是命中的狀態,可以直接去除偏移為10的數據。
可以看到,全相聯的映射Cache率很高,主存可以映射到Cache任意位置。同時,塊沖突率低,淘汰算法復雜。因此,全映射算法適用於小容量Cache。
(2)直接映射
直接映射將主存地址從一維變成三維(區號、區內塊號、塊內地址),如地址61二進制為00111101,划分為000011 11 01。直接映射的映射算法是,假設Cache有n行,主存第j塊號映射到Cache的行號為i=j mod n ,即內存的數據塊映射到Cache的特定行。
直接映射的過程如上圖所示,若訪問順序為1F 20 24 1E 44。首先對於1F,地址划分為tag=0000,index=111,offset=11。因此,1F這個數據塊只能映射到第7行。 如果某一行中,已經有了數據,且tag不一致,那么就會發生碰撞。這種情況下就會覆蓋掉原來的數據。
直接映射的特點是Cache的利用率低,只能映射到特定行中。塊沖突率高,淘汰算法簡單,適用於大容量的Cache。
(3)組相聯映射
組相連映射把地址從一維變成三維(組號、組內塊號、塊內地址)。如地址61划分為0000111 1 01。組內映射算法是Cache共n組,主存第j塊號映射到Cache 的組號為: i=j mod n 即主存的數據塊映射到Cache特定組的任意行。
組相聯映射會把數據映射到對應組的任意行,接着根據tag進行判斷是否命中,如果該組已經滿了,就會發生碰撞。組相聯映射相當於全相聯映射和直接映射的折中。下圖說明了組相聯與其它兩種方式的關系。若Cache有8行,當組相聯數k=8時,組相聯就變成了全相聯。當k=1時,就變成了直接相聯。
七、替換算法
當程序運行一段時間后,Cache存儲空間被占滿,當再有新數據要調入時,就需要通過某種機制決定替換的對象。因此,就需要替換算法。常見的替換算法有:先進先出法(FIFO,First in First out),最不經常使用法(LFU,Least Frequently Used),近期最少使用法(LRU,Least recently Used),隨機替換法。
(1)先進先出法(FIFO)
先進先出法,顧名思義就是當需要替換時,先被訪問的最先被替換。如上圖,22載入添加一個標記0,后面沒載入一次標記+1,依次載入到22 11 19 7,其中22命中一次。當載入16時,Cache已經滿了。這時候需要采用先進先出的替換算法,其中22是最先被載入的,因此替換22。
(2)最不經常使用法(LFU)
最不經常使用法,就是優先替換掉命中次數最少的行,如果命中次數相同,可以搭配其它替換策略,如FIFO。如上圖,當載入4時,22的次數為2,11的次數為1,19和16的次數為0。因此替換掉先進來的19。同理,當載入3時,優先替換掉次數少且先進來的16。
(3)近期最少使用法(LRU)
近期最少使用法,就是優先替換掉很長一段時間沒有命中的單元。每次載入,如果命中則標記清0,否則標記+1。如上圖所示,當載入16時,優先替換標記最大的11,載入4時,優先替換最大的22。
















