《深刻理解計算機系統》(CSAPP)讀書筆記 —— 第六章 存儲器層次結構

  在計算機系統模型中,CPU執行指令,而存儲器系統爲CPU存放指令和數據。實際上,存儲器系統是一個具備不一樣容量、成本和訪問時間的存儲設備的層次結構
  若是你的程序須要的數據是存儲在CPU寄存器中,那麼在指令的執行期間,在0個週期內就能訪問到它們。若是存儲在高速緩存中,須要4~75個週期。若是存儲在主存中,須要上百個週期。而若是存儲在磁盤上,須要大約幾千萬個週期!
  計算機程序的一個基本屬性稱爲局部性。具備良好局部性的程序傾向於一次又一次地訪問相同的數據項集合,或是傾向於訪問鄰近的數據項集合。具備良好局部性的程序比局部性差的程序更多地傾向於從存儲器層次結構中較高層次處訪問數據項,所以運行得更快。算法

存儲技術

隨機訪問存儲器

  隨機訪問存儲器( Random-Access Memory,RAM)分爲兩類:靜態的和動態的。靜態RAM(SRAM)比動態RAM(DRAM)更快,但也貴得多。SRAM用來做爲高速緩存存儲器。DRAM用來做爲主存以及圖形系統的幀緩衝區編程

靜態RAM數組

  SRAM將每一個位存儲在一個雙穩態的( bistable)存儲器單元裏。每一個單元是用一個六晶體管電路來實現的。這個電路有這樣一個屬性,它能夠無限期地保持在兩個不一樣的電壓配置( configuration)或狀態( state)之一。其餘任何狀態都是不穩定的,在不穩定狀態時,電路會迅速轉移到兩個穩定狀態的一個。緩存

  因爲SRAM存儲器單元的雙穩態特性,只要有電,它就會永遠地保持它的值。即便有干擾(例如電子噪音)來擾亂電壓,當干擾消除時,電路就會恢復到穩定值。服務器

動態RAM網絡

  DRAM將每一個位存儲爲對一個電容的充電。DRAM存儲器能夠製造得很是密集。每一個單元由一個電容和一個訪問晶體管組成。可是,與SRAM不一樣,DRAM存儲器單元對干擾很是敏感。當電容的電壓被擾亂以後,它就永遠不會恢復了。暴露在光線下會致使電容電壓改變。dom

  下表總結了SRAM和DRAM存儲器的特性。只要有供電,SRAM就會保持不變。與DRAM不一樣,它不須要刷新。SRAM的存取比DRAM快。SRAM對諸如光和電噪聲這樣的干擾不敏感。代價是SRAM單元比DRAM單元使用更多的晶體管,於是密集度低,並且更貴,功耗更大。異步

每位晶體管數 相對訪問時間 持續的 敏感的 相對花費 應用
SRAM 6 1X 1000X 高速緩存存儲器
DRAM 1 10X 1X 主存,幀緩衝區

傳統的DRAMide

  DRAM芯片中的單元(位)被分紅d個超單元( supercell),每一個超單元都由w個DRAM單元組成。一個\(d \times w\)的DRAM總共存儲了\(dw\)位信息。超單元被組織成一個r行c列的長方形陣列,這裏rc=d。每一個超單元有形如(i,j)的地址,這裏i表示行,而j表示列。函數

  例如,以下圖所示是一個16×8的DRAM芯片的組織,有d=16個超單元,每一個超單元有w=8位,r=4行,c=4列。帶陰影的方框表示地址(2,1)處的超單元。信息經過稱爲引腳(pin)的外部鏈接器流入和流出芯片。每一個引腳攜帶一個1位的信號。下圖給出了兩組引腳:8個data引腳,它們能傳送一個字節到芯片或從芯片傳出一個字節,以及2個addr引腳,它們攜帶2位的行和列超單元地址。其餘攜帶控制信息的引腳沒有顯示出來。

image-20201212092653332

  每一個DRAM芯片被鏈接到某個稱爲內存控制器( memory controller)的電路,這個電路能夠一次傳送w位到每一個DRAM芯片或一次從每一個DRAM芯片傳出w位。爲了讀出超單元(i,j)的內容,內存控制器將行地址i發送到DRAM,而後是列地址j。DRAM把超單元(i,j)的內容發回給控制器做爲響應。行地址i稱爲RAS( Row Access strobe,行訪問選通脈衝)請求。列地址j稱爲CAS( Column Access strobe,列訪問選通脈衝)請求。注意,RAS和CAS請求共享相同的DRAM地址引腳。

  例如,要從圖6-3中16×8的DRAM中讀出超單元(2,1),內存控制器發送行地址2,以下圖a所示。DRAM的響應是將行2的整個內容都複製到一個內部行緩衝區。接下來,內存控制器發送列地址1,以下圖b所示。DRAM的響應是從行緩衝區複製出超單元(2,1)中的8位,並把它們發送到內存控制器。

image-20201212093003882

  電路設計者將DRAM組織成二維陣列而不是線性數組的一個緣由是下降芯片上地址引腳的數量。例如,若是示例的128位DRAM被組織成一個16個超單元的線性數組,地址爲0~15,那麼芯片會須要4個地址引腳而不是2個。二維陣列組織的缺點是必須分兩步發送地址,這增長了訪問時間

加強的DRAM

  能夠經過如下方式提升訪問基本DRAM的速度。

  快頁模式DRAM( Fast Page Mode dram, FPM DRAM)。傳統的DRAM將超單元的一整行復制到它的內部行緩衝區中,使用一個,而後丟棄剩餘的。FPM DRAM容許對同一行連續地訪問能夠直接從行緩衝區獲得服務

假如要讀取第4行的3個超單元,傳統DRAM須要發出3次RAS,CAS。而FPM DRAM只須要發出一次RAS,CAS,後面跟2個CAS便可。

  擴展數據輸出DRAM( Extended Data Out Dram, EDO DRAM)。 FPM DRAM的個加強的形式,它容許各個CAS信號在時間上靠得更緊密一點。

 同步DRAM( Synchronous DRaM, SDRAM)。 SDRAM用與驅動內存控制器相同的外部時鐘信號的上升沿來代替許多這樣的控制信號。最終效果就是 SDRAM可以比那些異步的存儲器更快地輸出它的超單元的內容。

  雙倍數據速率同步DRAM( Double data- Rate SynchronouS DRAm, DDR SDRAM)。DDR SDRAM是對 SDRAM的一種加強,它經過使用兩個時鐘沿做爲控制信號,從而使DRAM的速度翻倍。不一樣類型的 DDR SDRAM是用提升有效帶寬的很小的預取緩衝區的大小來劃分的:DDR(2位)、DDR2(4位)和DDR(8位)。

  視頻RAM( Video ram,VRAM)。它用在圖形系統的幀緩衝區中。VRAM的思想與 FPM DRAM相似。兩個主要區別是:1)VRAM的輸出是經過依次對內部緩衝區的整個內容進行移位獲得的;2)VRAM容許對內存並行地讀和寫。所以,系統能夠在寫下一次更新的新值(寫)的同時,用幀緩衝區中的像素刷屏幕(讀)。

非易失性存儲器

  若是斷電,DRAM和SRAM會丟失它們的信息,從這個意義上說,它們是易失的( volatile)。另外一方面,非易失性存儲器( nonvolatile memory)即便是在關電後,仍然保存着它們的信息。

  對EPROM編程是經過使用一種把1寫人 EPROM的特殊設備來完成的。 EPROM可以被擦除和重編程的次數的數量級能夠達到1000次。EEPROM可以被編程的次數的數量級能夠達到10次。

  閃存( flash memory)是一類非易失性存儲器,基於 EEPROM,它已經成爲了一種重要的存儲技術。

訪問主存

  數據流經過稱爲總線(bus)的共享電子電路在處理器和DRAM主存之間來來回回。每次CPU和主存之間的數據傳送都是經過一系列步驟來完成的,這些步驟稱爲總線事務( bus transaction)。讀事務( read transaction)從主存傳送數據到CPU。寫事務( write trans-action)從CPU傳送數據到主存。

  總線是一組並行的導線,能攜帶地址、數據和控制信號。取決於總線的設計,數據和地址信號能夠共享同一組導線,也可使用不一樣的。同時,兩個以上的設備也能共享同一總線。控制線攜帶的信號會同步事務,並標識出當前正在被執行的事務的類型。例如,當前關注的這個事務是到主存的嗎?仍是到諸如磁盤控制器這樣的其餘I/O設備?這個事務是讀仍是寫?總線上的信息是地址仍是數據項?

  展現了一個示例計算機系統的配置。主要部件是CPU芯片、咱們將稱爲IO橋接器(I/ O bridge)的芯片組(其中包括內存控制器),以及組成主存的DRAM內存模塊這些部件由一對總線鏈接起來,其中一條總線是系統總線( system bus),它鏈接CPU和I/O橋接器,另外一條總線是內存總線( memory bus),它鏈接I/O橋接器和主存。I/O橋接器將系統總線的電子信號翻譯成內存總線的電子信號。

image-20201212101344668

局部性

  一個編寫良好的計算機程序經常具備良好的局部性( locality)。也就是,它們傾向於引用鄰近於其餘最近引用過的數據項的數據項,或者最近引用過的數據項自己。這種傾向性,被稱爲局部性原理( principle of locality),是一個持久的概念,對硬件和軟件系統的設計和性能都有着極大的影響。局部性一般有兩種不一樣的形式:時間局部性( temporal locality)和空間局部性( spatial locality)。在一個具備良好時間局部性的程序中,被引用過一次的內存位置極可能在不遠的未來再被屢次引用。在一個具備良好空間局部性的程序中,若是一個內存位置被引用了次,那麼程序極可能在不遠的未來引用附近的一個內存位置。通常而言,有良好局部性的程序比局部性差的程序運行得更快。

  以下所示的函數sumvec,它對一個向量的元素求和。在這個例子中,變量sum在每次循環迭代中被引用一次,所以,對於sum來講,有好的時間局部性。另外一方面,由於sun是標量,對於sum來講,沒有空間局部性。

int sumvec(int v[N])
{
    int i,sum = 0;
    for (i = 0; i < N; i++)
        sum += v[i];
    return sum;
}
引用模式:
地址:			0		4		8		12		16
內容:			v0		v1		v2		v3		v4
訪問順序:		1		2		3		4		5

  如上所示,向量v的元素是被順序讀取的,一個接一個,按照它們存儲在內存中的順序(爲了方便,咱們假設數組是從地址0開始的)。所以,對於變量v,函數有很好的空間局部性,可是時間局部性不好,由於每一個向量元素只被訪問一次

步長爲1的引用模式爲順序引用模式( sequential reference pattern)。一個連續向量中,每隔k個元素進行訪問,就稱爲步長爲k的引用模式( stride-k reference pattern)。步長爲1的引用模式是程序中空間局部性常見和重要的來源。通常而言,隨着步長的增長,空間局部性降低。

  以下的函數 sumarrayrows,它對一個二維數組的元素求和。雙重嵌套循環按照行優先順序(row major order)讀數組的元素。也就是,內層循環讀第一行的元素,而後讀第二行,依此類推。函數 sumarrayrows具備良好的空間局部性,由於它按照數組被存儲的行優先順序來訪問這個數組。其結果是獲得一個很好的步長爲1的引用模式,具備良好的空間局部性。

int sum_array_rows(int a[M][N])
{
    int i, j, sum = 0;

    for (i = 0; i < M; i++)
        for (j = 0; j < N; j++)
            sum += a[i][j];
    return sum;
}
引用模式:
地址:			0		4		8		12		16
內容:			a00		a01		a02		a10		a11
訪問順序:		1		2		3		4		5

存儲器層次結構

  存儲技術和計算機軟件的一些基本的和持久的屬性:
  存儲技術:不一樣存儲技術的訪問時間差別很大。速度較快的技術每字節的成本要比速度較慢的技術高,並且容量較小。CPU和主存之間的速度差距在增大。
  計算機軟件:一個編寫良好的程序傾向於展現出良好的局部性。

  硬件和軟件的這些基本屬性互相補充得很完美。它們這種相互補充的性質令人想到一種組織存儲器系統的方法,稱爲存儲器層次結構( memory hierarchy),下圖展現了一個典型的存儲器層次結構。通常而言,從高層往底層走,存儲設備變得更慢、更便宜和更大。在最高層(L0),是少許快速的CPU寄存器,CPU能夠在一個時鐘週期內訪問它們。接下來是一個或多個小型到中型的基於SRAM的高速緩存存儲器,能夠在幾個CPU時鐘週期內訪問它們。而後是一個大的基於DRAM的主存,能夠在幾十到幾百個時鐘週期內訪問它們。接下來是慢速可是容量很大的本地磁盤。最後,有些系統甚至包括了一層附加的遠程服務器上的磁盤,要經過網絡來訪問它們。

image-20201212112607240

存儲器結構中的緩存

  通常而言,高速緩存( cache,讀做「cash」)是一個小而快速的存儲設備,它做爲存儲在更大、也更慢的設備中的數據對象的緩衝區域。使用高速緩存的過程稱爲緩存( caching,讀做「 cashing")。

  存儲器層次結構的中心思想是,對於每一個k,位於k層的更快更小的存儲設備做爲位於k+1層的更大更慢的存儲設備的緩存。換句話說,層次結構中的每一層都緩存來自較低一層的數據對象。

  數據老是以塊大小爲傳送單元( transfer unit)在第k層和第k+1層之間來回複製的。雖然在層次結構中任何一對相鄰的層次之間塊大小是固定的,可是其餘的層次對之間能夠有不一樣的塊大小。如上圖所示,L1和L0之間的傳送一般使用的是1個字大小的塊。L2和L1之間(以及L3和I2之間、I4和I3之間)的傳送一般使用的是幾十個字節的塊。而L5和L4之間的傳送用的是大小爲幾百或幾千字節的塊。通常而言,層次結構中較低層(離CPU較遠)的設備的訪問時間較長,所以爲了補償這些較長的訪問時間,傾向於使用較大的塊。

緩存命中

  當程序須要第k+1層的某個數據對象d時,它首先在當前存儲在第k層的一個塊中查找d。若是d恰好緩存在第k層中,那麼就是咱們所說的緩存命中( cache hit)。

緩存不命中

  另外一方面,若是第k層中沒有緩存數據對象d,那麼就是咱們所說的緩存不命中( cache miss)。當發生緩存不命中時,第k層的緩存從第k+1層緩存中取出包含d的那個塊,若是第k層的緩存已經滿了,可能就會覆蓋現存的一個塊。(緩存的替換策略:隨機替換替換策略,最少被使用(LRU)替換策略)。

緩存不命中種類

  區分不一樣種類的緩存不命中有時候是頗有幫助的。若是第k層的緩存是空的,那麼對任何數據對象的訪問都會不命中。一個空的緩存有時被稱爲冷緩存( cold cache),此類不命中稱爲強制性不命中( compulsory miss)或冷不命中( cold miss)。冷不命中很重要,由於它們一般是短暫的事件,不會在反覆訪問存儲器使得緩存暖身( warmed up)以後的穩定狀態中出現。

緩存管理

  存儲器層次結構的本質是,每一層存儲設備都是較低一層的緩存。在每一層上,某種形式的邏輯必須管理緩存。這裏,咱們的意思是指某個東西要將緩存劃分紅塊,在不一樣的層之間傳送塊,斷定是命中仍是不命中,並處理它們。管理緩存的邏輯能夠是硬件、軟件,或是二者的結合。

高速緩存存儲器

  高速緩存關於讀的操做很是簡單。首先,在高速緩存中查找所需字\(w\)的副本。若是命中,當即返回字\(w\)給CPU。若是不命中,從存儲器層次結構中較低層中取出包含字\(w\)的塊,將這個塊存儲到某個高速緩存行中(可能會驅逐一個有效的行),而後返回字\(w\)

  寫的狀況就要複雜一些了。假設咱們要寫一個已經緩存了的字\(w\)(寫命中, write hit)。在高速緩存更新了它的\(w\)的副本以後,怎麼更新\(w\)在層次結構中緊接着低一層中的副本呢?最簡單的方法,稱爲直寫( write-through),就是當即將\(w\)的高速緩存塊寫回到緊接着的低一層中。雖然簡單,可是直寫的缺點是每次寫都會引發總線流量。另外一種方法,稱爲寫回( write-back),儘量地推遲更新,只有當替換算法要驅逐這個更新過的塊時,才把它寫到緊接着的低一層中。因爲局部性,寫回能顯著地減小總線流量,可是它的缺點是增長了複雜性。高速緩存必須爲每一個高速緩存行維護一個額外的修改位( dirty bit),代表這個高速緩存塊是否被修改過。

  另外一個問題是如何處理寫不命中。一種方法,稱爲寫分配( write-allocate),加載相應的低一層中的塊到高速緩存中,而後更新這個高速緩存塊。寫分配試圖利用寫的空間局部性,可是缺點是每次不命中都會致使一個塊從低一層傳送到高速緩存。另外一種方法,稱爲非寫分配(not- write-allocate),避開高速緩存,直接把這個字寫到低一層中。直寫高速緩存一般是非寫分配的。寫回高速緩存一般是寫分配的。

  高速緩存既保存數據,也保存指令。只保存指令的高速緩存稱爲 i-cache。只保存程序數據的高速緩存稱爲 d-cache。既保存指令又包括數據的高速緩存稱爲統一的高速緩存( unified cache)。現代處理器包括獨立的 i-cache和d-cache。這樣作有不少緣由。有兩個獨立的高速緩存,處理器可以同時讀一個指令字和一個數據字。 i-cache一般是隻讀的,所以比較簡單。一般會針對不一樣的訪問模式來優化這兩個高速緩存,它們能夠有不一樣的塊大小,相聯度和容量。使用不一樣的高速緩存也確保了數據訪問不會與指令訪問造成衝突不命中,反過來也是同樣,代價就是可能會引發容量不命中增長。

編寫高速緩存友好的代碼

  確保代碼高速緩存友好的基本方法。
  1)讓最多見的狀況運行得快。程序一般把大部分時間都花在少許的核心函數上,而這些函數一般把大部分時間都花在了少許循環上。因此要把注意力集中在覈心函數裏的循環上,而忽略其餘部分。
  2)儘可能減少每一個循環內部的緩存不命中數量。在其餘條件(例如加載和存儲的總次數)相同的狀況下,不命中率較低的循環運行得更快。

  考慮以下的函數

int sumvec(int v[N])
{
	int i,sum = 0;
    
	for(i = 0;i<N;i++)
		sum +=v[i];
	return sum;
}

  首先,注意對於局部變量i和sum,循環體有良好的時間局部性。如今考慮一下對向量v的步長爲1的引用。通常而言,若是一個高速緩存的塊大小爲B字節,那麼一個步長爲k的引用模式(這裏k是以字爲單位的)平均每次循環迭代會有\(\min (1,(wordsize \times k)/B)\)次緩存不命中。當k=1時,它取最小值,因此對v的步長爲1的引用確實是高速緩存友好的。

  例如,假設v是塊對齊的,字爲4個字節,高速緩存塊爲4個字,而高速緩存初始爲空(冷高速緩存)。在這個例子中,對v[0]的引用會不命中,而相應的包含v[0] ~v[3]的塊會被從內存加載到高速緩存中。所以,接下來三個引用都會命中。對v[4]的引用會致使不命中,而個新的塊被加載到高速緩存中,接下來的三個引用都命中,依此類推。總的來講,四個引用中,三個會命中,在這種冷緩存的狀況下,這是咱們所能作到的最好的狀況了。

  總之,簡單的 sumvec示例說明了兩個關於編寫高速緩存友好的代碼的重要問題:第一,對局部變量的反覆引用是好的,由於編譯器可以將它們緩存在寄存器文件中(時間局部性)。第二,步長爲1的引用模式是好的,由於存儲器層次結構中全部層次上的緩存都是將數據存儲爲連續的塊(空間局部性)。

總結

  本章主要介紹了各類各樣的存儲系統及其原理,通常來講,較小、較快的設備在頂部,較大、較慢的設備在底部。由於編寫良好的程序有好的局部性,大多數數據均可以從較高層獲得服務,結果就是存儲系統能以較高層的速度運行,但卻有較低層的成本和容量。咱們能夠經過編寫有良好空間和時間局部性的程序來顯著地改進程序的運行時間。例如,能夠利用基於SRAM的高速緩存存儲器。主要緣由是從高速緩存取數據的程序比主要從內存取數據的程序運行得快得多。

  養成習慣,先贊後看!若是以爲寫的不錯,歡迎關注,點贊,轉發,謝謝!

有任何問題,都可經過公告中的二維碼聯繫我

相關文章
相關標籤/搜索