存儲器層次結構與高速緩存(程序性能優化探討)

一、存儲技術

      計算機技術的成功很大程度上源自於存儲技術的巨大進步。早起的計算機只有幾千字的隨機訪問存儲器。最早的IBM PC甚至於沒有硬盤。1982年引入的IBM PC-XT 有10M字節的磁盤。到2015年,典型的計算機已經有3000000倍於PC-XT的磁盤存儲,而且磁盤的容量以每年加倍的速度增長

     隨機訪問存儲器分靜態存儲SRAM、動態存儲DRAM,兩者都屬於易失性存儲器,非易失性存儲器致使斷電後,任然保存着信息。如磁盤、固態硬盤等

二、計算機程序的局部性原理

        局部性通常有兩種不同的形式:時間局部性和空間局部性。1)、具有良好時間局部性的程序中,被引用過一次的內存位置很有可能在不遠的將來再被多次引用。2)、在一個具有良好空間局部性的程序中,如果一個內存位置被引用了一次,那麼程序很可能在不遠的將來引用附近的一個內存位置。

       從硬件層面,局部性原理允許計算機設計者通過引用稱爲高速緩存存儲器的小而快速的存儲器來保存最近被引用的指令和數據項,從而提高對主存的訪問速度。

     從操作系統層面,局部性原理允許系統使用主存作爲虛擬地址空間最近被引用塊的高速緩存。

    從程序的設計層面,Web瀏覽器將最近被引用的文檔放在本地磁盤上,利用的是時間局部性

三、存儲器層次結構

存儲技術:不同存儲技術的訪問時間差異很大。速度較快的技術每字節的成本要比速度慢的技術高,而且容量較小。CPU和主存之間的速度差距在增大

計算機軟件:一個編寫良好的程序傾向於展示出良好的局部性

存儲器層次結構中,從高層往底層走,存儲設備變得更慢,更便宜和更大

四、存儲器層次結構中的緩存

      一般而言,高速緩存是一個小而快速的存儲設備,它作爲存儲在更大、也更慢的設備中的數據對象的緩衝區域。使用高速緩存的過程稱爲緩存caching

      存儲器層次結構的中心思想是,對於每個k,位於K層的更快更小的存儲設備作爲位於K+1層的更大更慢的存儲設備的緩存。存儲層次結構中緩存的一般性概念,存儲器被劃分成連續的數據對象組塊(chunk),稱爲塊(block)。每個塊都有一個唯一的地址和名字,使之區別於其他的塊。數據總是以塊大小爲傳送單元在K層和K+1層之間來回複製

     接下來我們一起探討緩存的命中、不命中和緩存如何管理

     1)、緩存命中

             當程序需要K+1層的某個數據對象d時,它首先在當前存儲的K層的一個塊中查找d,如果d剛好緩存在K層中,那麼久是我們所說的緩存命中(cache hit)。該程序直接從K層讀取d,根據存儲器層次結構的性質,這要比從K+1層讀取d更快。

    2)、緩存不命中

           如果K層中沒有緩存數據對象d,那麼就是我們所說的緩存不命中(cache miss)。當發生緩存不命中是,K層的緩存從K+1層緩存中取出包含d的那個塊,如果K層的緩存已經滿了,就需要覆蓋現存的一個塊。緩存的替換策略有隨機替換策略和最近最少被使用替換策略(LRU)。緩存不命中的種類:冷不命中、衝突不命中、容量不命中

冷不命中只是瞬間存在,原因爲緩存中沒有數據。衝突不命中,則是放置策略的嚴格限制導致,K+1層的多個塊會映射到K的同一個塊。容量不命中則是工作集的數據大小大於緩存大小導致

 3)、緩存管理

     編譯器管理寄存器文件,緩存層次結構的最高層

    L1、L2、L3層的緩存完全是由內置的緩存中的硬件邏輯來管理

    在一個有虛擬內存的系統中,DRAM主存作爲存儲在磁盤上的數據塊的緩存,是由操作系統軟件和CPU上的地址翻譯硬件共同管理

   對於一個具有像AFS這樣的分佈式文件系統的機器來說,本地磁盤作爲緩存,他是由運行在本地機器上的AFS客戶端進程管理

五、高速緩存存儲器

5.1、通用的高速緩存存儲器組織結構

  在討論之前,我們再來確認一個老生常談的問題,我常說:“32位機最大可訪問4GB的地址空間”,啥意思?首先,32位是CPU的一個參數,指CPU總線的數據寬度。32位CPU顧名思義就是擁有32bit的總線數據寬度,一次操作最大可執行32位的計算。回憶下之前我們講過的32位乘法,由於乘法的結果有可能大於32位,因此會用兩個32位變量進行保存。

        那麼,當CPU要讀某個地址時,首先要先計算出該地址的值。假設地址是從00000000開始的,FFFFFFFF結束,在此範圍內的地址空間有多大呢?很明顯,00000000~FFFFFFFF一共有2^32= 4294967296種取值,也就是我們常說的4G。可惜小編我曾經腦子進水,問出這樣的問題:地址不是按字節標識的麼?byte和bit不是按8位換算的麼?你這4G換算成字節,不是隻有500MB大小麼?那00000000~FFFFFFFF何來4GB地址空間???餓,別笑話我,拿這個問題去問我的學霸朋友,居然把他也蒙了好一會兒!當然,要解釋這個也很容易,正因爲地址是按字節標識的,因此當我們訪問存儲器時,根本不需要對存儲器的逐個位進行讀取,找到字節級,再用移位就能存儲空間的每個位了。比如,假設有一款特殊的CPU,它是2位的CPU,毫無疑問,他每次只可能處理00、01、10、11這四種值,於是這四個值可以代表四個地址,而每個地址代表8位存儲空間,也就是32位地址空間……想明白沒?CPU裏處理地址值時,每個數值都代表一個字節(byte)大小的存儲空間,而不是一位(bit)大小的存儲空間。因此,4Gbit大小的數值空間,就能標識4GB大小的地址空間,如果說計算地址相當於數數,那麼我們是按字節來數存儲器空間,而不是按位來數……你別說,腦子卡殼時,要是沒事先想清楚,還真有可能被人問到(⊙o⊙)。ps,這裏的“卡”應該讀qiǎ,哈哈!

        所謂地址空間,其實是由地址來解釋空間,地址是數值,而每一個數值標識一個8位的存儲空間,這應該算是地址空間不太嚴格的定義了!

哈勒,好了,假設地址有m位,那麼就有M=2^m個字節地址空間,M個不同的地址。結論先放在這,我們來淺析下什麼是地址多。來看兩則定義:

    1.內存中每個用於數據存取的基本單位,都被賦予一個唯一的序號,稱爲地址。

    2.標識寄存器、存儲單元和存儲設備的編號或名稱。

定義1太過狹隘,把地址的概念限制於內存之中,顯然不是我們想要的,還是定義2比較靠譜,用來標識寄存器和存儲的編號或名稱,它提供了在寄存器、緩存、內存、硬盤等器件中的數據檢索依據。也就是說,M=2^m地址空間對這些器件的作用是類似的,只是具體策略有所不同。

事實上,現代操作系統所管轄的內存時,是不會讓程序員訪問到內存每個數據塊的實際硬件地址的,而是採用一種稱爲“虛擬存儲器”的方法,將內存中連續或不連續的存儲塊,定義成連續的虛擬地址供程序員訪問,類似C語言中取地址&符號的出來的地址值,都是這種虛擬地址值,這樣有利於簡化操作,更有便於操作系統和內存硬件採用更合理的方案分配存儲空間。內存是緩存硬盤數據的重要工具,這些屬於層次結構中L4和L5的規則原理,將在後面虛擬存儲器章節進行詳細討論。

CPU如何通過地址管理寄存器,我們也不關心,我們要關心的是嵌入cpu內部的高速緩存L1以及下層的L2、L3的通用緩存的地址結構。試想,高速緩存總是比內存(教材上經常成爲主存或者存儲器)小得多得多,L1一般就幾十上百KB而已,離4GB遠着呢,那麼CPU的這32位地址怎麼用呢?很明顯,高速緩存既然小,那一定是被地址空間所共享,你4GB中任何一個字節都可能被緩存在L1~L3中,很顯然,我們得有辦法進行區分。要對高速緩存進行有效的管理,就得給高緩存分組,組裏面還可以有行,行裏面可以有不同的字節串,針對這個想法,就能推出教材裏的通用結構圖:

品味這個結構圖時,憑直覺容易犯的錯誤,就是把下面的地址和上面的行混爲一談。因此先明確下概念:

            1.上面整個部分就是高速緩存

            2.這個高速緩存被劃分成S個組

            3.每個組有E行數據

            4.每行中都有一個高速緩存塊,

            5.每個緩存塊的大小是B個字節

            6.綜上所述,整個高速緩存的存儲大小C = S*E*B,你滴明白?

        我始終覺得,從大到小的計算方式更符合中國人的思維習慣,對C的一個簡單乘法計算,寫成這樣的順序更便於理解。

        好了,既然清楚了高速緩存的通用結構,那麼CPU是依據什麼來訪問緩存中各字節的呢?首先你得找到組吧,然後找到行吧,最後找到塊吧,最後再通過偏移找到具體的字節吧?如果你能看懂到這,那(S,E,B,m)就灰常好理解了。

        既然CPU在訪問高速緩存時,需要找這麼多信息,那麼在定義高速緩存的地址概念時,就必須得有響應的字段來標識。剛纔我們有了M=2^m,說明地址有m位,標識了M字節的地址空間。現在要標識組,要在S個組裏面找出唯一的一個組信息,需要佔用地址中的多少位呢?如果S=2^s,那我們就需要在m位地址中劃分出s位來標識組信息;好,接下來要找行,既然每組有E行,若E=2^t,那有要在m位中劃分t位來標識行信息;最後是行中的緩存塊字節偏移,既然有B字節,而B=2^b字節,也就說還要從m中分配b位來標識塊字節偏移
 

5.2、直接映射高速緩存

        於是我們先對t做做文章,讓他不表述行信息,假如每個組只有一行如何?這就是所謂的直接映射高速緩存!這種緩存的特點是,每組只有一行,那每組也就只有一個緩存塊。這樣一來,t就不在用於區分組中的行信息了。它用來幹什麼呢?答案是,用來共享。在直接映射高速緩存中,標記位t就是不同地址共享同一行緩存空間的依據。

        上圖解釋了地址位(下)於直接映射高速緩存(上)的對應關係。

                1.根據地址中的i已經找到了緩存裏的i組

                2.讀取了緩存中第一位的有效位是1,說明該數據有效,

                3. 判斷地址中標記位t的值是否與i組裏這唯一的一行紀錄中的標記位相等,若相等說明數據有效,緩存命中,

                            若不相等呢?說明該組該行正緩存其他標記位標識的信息,也就是對當前地址不命中。

                4.若緩存命中,則讀取地址中塊偏移信息,找到具體的字節。既然塊偏移的取值範圍是000~111,也就是0~7,說明可以訪問8字節塊中任意一個字節。

        我們發現,正是因爲有標記位的存在,同樣大小的直接映射緩存,在服務於m位地址時,可以減少塊偏移的位數,簡化偏移操作,增加組的數量。同時,因爲標記位的存在,使得m地址所標識的數據被輪流載入緩存,由標記位判斷緩存是否命中。某個確定地址的數據可以暫時不在緩存中保存。

        這裏詳細闡述教材中的例子:

        假設有一個直接映射高速緩存:(S,E,B,m)=(4,1,2,4)。根據上面的基礎可以得知,這個緩存有4個組,每組一行(直接映射的特徵),每個塊有兩個字節,地址有4位。可以得出如下信息:

        組索引位:s=2;塊偏移位數b=1;地址位數m=4,物理地址最大數量M=2^4=16字節,標記位t = m - s - b = 1。

1.先從索引位和偏移位看起,組索引00~11標誌四個組,每個組有一個緩存塊(每個組只有一行的嘛),每個緩存塊有兩個字節,因此偏移位是0~1,從上圖看出,每個索引位對應兩個偏移位,其實就是遍歷每組裏緩存塊的各字節,他們共有8種組合。

2.標記位t取值0~1,由於每組只有一行,因此標記位用於區分同組、同偏移位的可能存儲的不同字節。也就說,每組中的緩存塊的每個字節,都可能對應兩個不同的取值,用標記位區分開來。因此我們看到,標記位0~1,將索引位和偏移位的組合數增大了一倍。

3.由於標記位對緩存塊的擴展,本來只有C=S*E*B=4*1*2=8字節大小的緩存,增大一倍後就能處理16字節數據了,你看看,剛好就能對應地址0~15這16個取值,每個取值代表一個字節,就剛好是16字節(聽起來很廢話,主要爲了儘可能讓大家讀懂)

4.由於每個緩存塊是兩個字節大小,因此16個字節就需要8個緩存塊,因此就有了最後的“塊號”,剛好0~7,共8個塊。本來高速緩存只有4個塊,現在通過標記位的引入,邏輯上便擴展成8個塊,只是在具體實現時,0、4塊共享同一個空間;1、5塊共享同一個空間;2、6塊共享同一個空間;3、7塊共享同一個空間。

綜上所述,這款直接映射高速緩存實際具有4個緩存塊,通過標記位在邏輯上擴展成8個邏輯塊,每個邏輯塊都唯一對應兩個計算機裏的字節


5.3、組相聯高速緩存和全相聯高速緩存

        組相聯英文裏爲set associative,比如我的CPU是8-way set associative,8路組相聯緩存,說明每組中有8行。組相聯高速緩存中,每組的行數E的範圍應該是1<E<C/B。每組多行的緩存策略如何實現呢?想想直接映射高速緩存,雖然每組只有一行,但是該行的索引位t並不唯一,如上例,索引位00有可能對應0塊和塊4,說明塊0和塊4共享組0,那如果組0剛好能容納兩行數據,則塊0和塊4就可以同時存在組0中,訪問時也很好區分,就用直接映射緩存裏的標記位t,通過t的0、1不同取值來識別。


5.3.1、組相聯高速緩存中的組選擇

     它的組選擇與直接映射高速緩存的組選擇一樣,組索引爲標識組

5.3.2、組相聯高速緩存中的行匹配和字選擇 

    組相聯高速緩存中的行匹配比直接高速緩存中的更復雜,因爲它必須檢查多個行的標記位和有效位,以確定所請求的字是否在集合中。傳統的內存是一個值的數組,以地址爲輸入,並返回存儲在哪個地址的值。另一方面,相聯存儲器是一個(Key,Value)對的數組,以Key爲輸入,返回與輸入Key相匹配的(Key,Value)對中的Value值。

組相聯高速緩存中行匹配的基本思想就是組中的任何一行都可以包含任何映射到這個組的內存塊。

5.3.3、組相聯高速緩存中不命中時的行替換

     如果CPU請求的字不在組的任何一行中,那麼就是緩存不命中,高速緩存必須從內存中取出包含這個字得塊。不過,一旦高速緩存取出了這個塊,該替換哪個行?當然,如果有一個空行,那它就是個很好的候選。但是如果該組中沒有空行,那麼我們必須從中選擇一個非空行,希望CPU不會很快引用這個被替換的行。

    最簡單的替換策略是隨機選擇要替換的行。其他更復雜的策略利用了局部性原理,以使在比較近的將來引用被替換的行的概率最小。常用的替換策略有:最不常使用策略(Least-Frequently—Used)和最近最少使用(Least-Recently-Used)

 5.4、全相聯高速緩存

      全相聯是一個組包含了所有的行的組
5.4.1、全相聯高速緩存中的組選擇
     全相聯高速緩存中組選擇特別簡單,因爲只有一個組,地址中沒有組索引爲,地址只被劃分成了一個標記位和一個塊偏移
5.4.2、全相聯高速緩存中行匹配和字抽取

    全相聯高速緩存中的行匹配和字選擇與組相聯高速緩存中的一樣,他們之間的區別主要是規模大小的問題

5.4.3、全相聯高速緩存應用場景

       因爲高速緩存電路必須並行的搜索許多匹配的標記,構造一個又大又快的相聯高速緩存很困難,而且很昂貴。因此,全相聯高速緩存只適合做小的高速緩存,列如虛擬內存系統中的翻譯備用緩存器(TLB)

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章