每個程式設計師都應該瞭解的記憶體知識

發表於2013-05-03

英文原文:lwn.net,翻譯:開源中國

[編輯的話: Ulrich Drepper最近問我們,是不是有興趣發表一篇他寫的記憶體方面的長文。我們不用看太多就已經知道,LWN的讀者們會喜歡這篇文章的。記憶體的使用常常是軟體效能的決定性因子,而如何避免記憶體瓶頸的好文章卻不好找。這篇文章應該會有所幫助。

他的原文很長,超過100頁。我們把它分成了7篇,每隔一到兩週發表一篇。7篇發完後,Ulrich會把全文發出來。

對原文重新格式化是個很有挑戰性的工作,但願結果會不錯吧。為了便於網上閱讀,我們把Ulrich的腳註{放在了文章裡},而互相引用的超連結(和[參考書目])要等到全文出來才能提供。

非常感謝Ultich,感謝他讓LWN發表這篇文章,期待大家在不久的將來都能寫出記憶體優化很棒的軟體。

1 概述

早期,計算機曾經很簡單。它的各種元件,比如CPU、記憶體、大容量儲存和網路介面,都是一起開發的,所以效能差不多。舉個例子來說,記憶體和網路介面提供資料的速度不會比CPU快多少。

這種情況隨著計算機構造的固化和各子系統的優化慢慢地發生了改變。其中一些元件的效能開始落後,成為系統的瓶頸。特別是大容量儲存和記憶體子系統,由於代價的原因,它們的發展嚴重滯後了。

大容量儲存的效能問題往往靠軟體來改善: 作業系統將常用(且最有可能被用)的資料放在主存中,因為後者的速度要快上幾個數量級。或者將快取加入儲存裝置中,這樣就可以在不修改作業系統的前提下提升效能。{然而,為了在使用快取時保證資料的完整性,仍然要作出一些修改。}這些內容不在本文的談論範圍之內,就不作贅述了。

而解決記憶體的瓶頸更為困難,它與大容量儲存不同,幾乎每種方案都需要對硬體作出修改。目前,這些變更主要有以下這些方式:

  • RAM的硬體設計(速度與併發度)
  • 記憶體控制器的設計
  • CPU快取
  • 裝置的直接記憶體訪問(DMA)

本文主要關心的是CPU快取和記憶體控制器的設計。在討論這些主題的過程中,我們還會研究DMA。不過,我們首先會從當今商用硬體的設計談起。這有助於我們理解目前在使用記憶體子系統時可能遇到的問題和限制。我們還會詳細介紹RAM的分類,說明為什麼會存在這麼多不同型別的記憶體。

本文不會包括所有內容,也不會包括最終性質的內容。我們的討論範圍僅止於商用硬體,而且只限於其中的一小部分。另外,本文中的許多論題,我們只會點到為止,以達到本文目標為標準。對於這些論題,大家可以閱讀其它文件,獲得更詳細的說明。

當本文提到作業系統特定的細節和解決方案時,針對的都是Linux。無論何時都不會包含別的作業系統的任何資訊,作者無意討論其他作業系統的情況。如果讀者認為他/她不得不使用別的作業系統,那麼必須去要求供應商提供其作業系統類似於本文的文件。

在開始之前最後的一點說明,本文包含大量出現的術語“經常”和別的類似的限定詞。這裡討論的技術在現實中存在於很多不同的實現,所以本文只闡述使用得最廣泛最主流的版本。在闡述中很少有地方能用到絕對的限定詞。

1.1文件結構

這個文件主要視為軟體開發者而寫的。本文不會涉及太多硬體細節,所以喜歡硬體的讀者也許不會覺得有用。但是在我們討論一些有用的細節之前,我們先要描述足夠多的背景。

在這個基礎上,本文的第二部分將描述RAM(隨機暫存器)。懂得這個部分的內容很好,但是此部分的內容並不是懂得其後內容必須部分。我們會在之後引用不少之前的部分,所以心急的讀者可以跳過任何章節來讀他們認為有用的部分。

第三部分會談到不少關於CPU快取行為模式的內容。我們會列出一些圖示,這樣你們不至於覺得太枯燥。第三部分對於理解整個文章非常重要。第四部分將簡短的描述虛擬記憶體是怎麼被實現的。這也是你們需要理解全文其他部分的背景知識之一。

第五部分會提到許多關於Non Uniform Memory Access (NUMA)系統。

第六部分是本文的中心部分。在這個部分裡面,我們將回顧其他許多部分中的資訊,並且我們將給閱讀本文的程式設計師許多在各種情況下的程式設計建議。如果你真的很心急,那麼你可以直接閱讀第六部分,並且我們建議你在必要的時候回到之前的章節回顧一下必要的背景知識。

本文的第七部分將介紹一些能夠幫助程式設計師更好的完成任務的工具。即便在徹底理解了某一項技術的情況下,距離徹底理解在非測試環境下的程式還是很遙遠的。我們需要藉助一些工具。

第八部分,我們將展望一些在未來我們可能認為好用的科技。

1.2 反饋問題

作者會不定期更新本文件。這些更新既包括伴隨技術進步而來的更新也包含更改錯誤。非常歡迎有志於反饋問題的讀者傳送電子郵件。

1.3 致謝

我首先需要感謝Johnray Fuller尤其是Jonathan Corbet,感謝他們將作者的英語轉化成為更為規範的形式。Markus Armbruster提供大量本文中對於問題和縮寫有價值的建議。

1.4 關於本文

本文題目對David Goldberg的經典文獻《What Every Computer Scientist Should Know About Floating-Point Arithmetic》[goldberg]表示致敬。Goldberg的論文雖然不普及,但是對於任何有志於嚴格程式設計的人都會是一個先決條件。

2 商用硬體現狀

鑑於目前專業硬體正在逐漸淡出,理解商用硬體的現狀變得十分重要。現如今,人們更多的採用水平擴充套件,也就是說,用大量小型、互聯的商用計算機代替巨大、超快(但超貴)的系統。原因在於,快速而廉價的網路硬體已經崛起。那些大型的專用系統仍然有一席之地,但已被商用硬體後來居上。2007年,Red Hat認為,未來構成資料中心的“積木”將會是擁有最多4個插槽的計算機,每個插槽插入一個四核CPU,這些CPU都是超執行緒的。{超執行緒使單個處理器核心能同時處理兩個以上的任務,只需加入一點點額外硬體}。也就是說,這些資料中心中的標準系統擁有最多64個虛擬處理器。當然可以支援更大的系統,但人們認為4插槽、4核CPU是最佳配置,絕大多數的優化都針對這樣的配置。在不同商用計算機之間,也存在著巨大的差異。不過,我們關注在主要的差異上,可以涵蓋到超過90%以上的硬體。需要注意的是,這些技術上的細節往往日新月異,變化極快,因此大家在閱讀的時候也需要注意本文的寫作時間。這麼多年來,個人計算機和小型伺服器被標準化到了一個晶片組上,它由兩部分組成: 北橋和南橋,見圖2.1。

每個程式設計師都應該瞭解的記憶體知識

圖2.1 北橋和南橋組成的結構

CPU通過一條通用匯流排(前端匯流排,FSB)連線到北橋。北橋主要包括記憶體控制器和其它一些元件,記憶體控制器決定了RAM晶片的型別。不同的型別,包括DRAM、Rambus和SDRAM等等,要求不同的記憶體控制器。為了連通其它系統裝置,北橋需要與南橋通訊。南橋又叫I/O橋,通過多條不同匯流排與裝置們通訊。目前,比較重要的匯流排有PCI、PCI Express、SATA和USB匯流排,除此以外,南橋還支援PATA、IEEE 1394、序列口和並行口等。比較老的系統上有連線北橋的AGP槽。那是由於南北橋間缺乏高速連線而採取的措施。現在的PCI-E都是直接連到南橋的。這種結構有一些需要注意的地方:

  • 從某個CPU到另一個CPU的資料需要走它與北橋通訊的同一條匯流排。
  • 與RAM的通訊需要經過北橋
  • RAM只有一個埠。{本文不會介紹多埠RAM,因為商用硬體不採用這種記憶體,至少程式設計師無法訪問到。這種記憶體一般在路由器等專用硬體中採用。}
  • CPU與南橋裝置間的通訊需要經過北橋在

上面這種設計中,瓶頸馬上出現了。第一個瓶頸與裝置對RAM的訪問有關。早期,所有裝置之間的通訊都需要經過CPU,結果嚴重影響了整個系統的效能。為了解決這個問題,有些裝置加入了直接記憶體訪問(DMA)的能力。DMA允許裝置在北橋的幫助下,無需CPU的干涉,直接讀寫RAM。到了今天,所有高效能的裝置都可以使用DMA。雖然DMA大大降低了CPU的負擔,卻佔用了北橋的頻寬,與CPU形成了爭用。

第二個瓶頸來自北橋與RAM間的匯流排。匯流排的具體情況與記憶體的型別有關。在早期的系統上,只有一條匯流排,因此不能實現並行訪問。近期的RAM需要兩條獨立匯流排(或者說通道,DDR2就是這麼叫的,見圖2.8),可以實現頻寬加倍。北橋將記憶體訪問交錯地分配到兩個通道上。更新的記憶體技術(如FB-DRAM)甚至加入了更多的通道。由於頻寬有限,我們需要以一種使延遲最小化的方式來對記憶體訪問進行排程。我們將會看到,處理器的速度比記憶體要快得多,需要等待記憶體。如果有多個超執行緒核心或CPU同時訪問記憶體,等待時間則會更長。對於DMA也是同樣。除了併發以外,訪問模式也會極大地影響記憶體子系統、特別是多通道記憶體子系統的效能。關於訪問模式,可參見2.2節。在一些比較昂貴的系統上,北橋自己不含記憶體控制器,而是連線到外部的多個記憶體控制器上(在下例中,共有4個)。

每個程式設計師都應該瞭解的記憶體知識

圖2.2 擁有外部控制器的北橋

這種架構的好處在於,多條記憶體匯流排的存在,使得總頻寬也隨之增加了。而且也可以支援更多的記憶體。通過同時訪問不同記憶體區,還可以降低延時。對於像圖2.2中這種多處理器直連北橋的設計來說,尤其有效。而這種架構的侷限在於北橋的內部頻寬,非常巨大(來自Intel)。{出於完整性的考慮,還需要補充一下,這樣的記憶體控制器佈局還可以用於其它用途,比如說「記憶體RAID」,它可以與熱插拔技術一起使用。}使用外部記憶體控制器並不是唯一的辦法,另一個最近比較流行的方法是將控制器整合到CPU內部,將記憶體直連到每個CPU。這種架構的走紅歸功於基於AMD Opteron處理器的SMP系統。圖2.3展示了這種架構。Intel則會從Nehalem處理器開始支援通用系統介面(CSI),基本上也是類似的思路——整合記憶體控制器,為每個處理器提供本地記憶體。

每個程式設計師都應該瞭解的記憶體知識

圖2.3 整合的記憶體控制器

通過採用這樣的架構,系統裡有幾個處理器,就可以有幾個記憶體庫(memory bank)。比如,在4 CPU的計算機上,不需要一個擁有巨大頻寬的複雜北橋,就可以實現4倍的記憶體頻寬。另外,將記憶體控制器整合到CPU內部還有其它一些優點,這裡就不贅述了。同樣也有缺點。首先,系統仍然要讓所有記憶體能被所有處理器所訪問,導致記憶體不再是統一的資源(NUMA即得名於此)。處理器能以正常的速度訪問本地記憶體(連線到該處理器的記憶體)。但它訪問其它處理器的記憶體時,卻需要使用處理器之間的互聯通道。比如說,CPU 1如果要訪問CPU 2的記憶體,則需要使用它們之間的互聯通道。如果它需要訪問CPU 4的記憶體,那麼需要跨越兩條互聯通道。使用互聯通道是有代價的。在討論訪問遠端記憶體的代價時,我們用「NUMA因子」這個詞。在圖2.3中,每個CPU有兩個層級: 相鄰的CPU,以及兩個互聯通道外的CPU。在更加複雜的系統中,層級也更多。甚至有些機器有不止一種連線,比如說IBM的x445和SGI的Altix系列。CPU被歸入節點,節點內的記憶體訪問時間是一致的,或者只有很小的NUMA因子。而在節點之間的連線代價很大,而且有巨大的NUMA因子。目前,已經有商用的NUMA計算機,而且它們在未來應該會扮演更加重要的角色。人們預計,從2008年底開始,每臺SMP機器都會使用NUMA。每個在NUMA上執行的程式都應該認識到NUMA的代價。在第5節中,我們將討論更多的架構,以及Linux核心為這些程式提供的一些技術。除了本節中所介紹的技術之外,還有其它一些影響RAM效能的因素。它們無法被軟體所左右,所以沒有放在這裡。如果大家有興趣,可以在第2.1節中看一下。介紹這些技術,僅僅是因為它們能讓我們繪製的RAM技術全圖更為完整,或者是可能在大家購買計算機時能夠提供一些幫助。以下的兩節主要介紹一些入門級的硬體知識,同時討論記憶體控制器與DRAM晶片間的訪問協議。這些知識解釋了記憶體訪問的原理,程式設計師可能會得到一些啟發。不過,這部分並不是必讀的,心急的讀者可以直接跳到第2.2.5節。

2.1 RAM型別

這些年來,出現了許多不同型別的RAM,各有差異,有些甚至有非常巨大的不同。那些很古老的型別已經乏人問津,我們就不仔細研究了。我們主要專注於幾類現代RAM,剖開它們的表面,研究一下核心和應用開發人員們可以看到的一些細節。第一個有趣的細節是,為什麼在同一臺機器中有不同的RAM?或者說得更詳細一點,為什麼既有靜態RAM(SRAM {SRAM還可以表示「同步記憶體」。}),又有動態RAM(DRAM)。功能相同,前者更快。那麼,為什麼不全部使用SRAM?答案是,代價。無論在生產還是在使用上,SRAM都比DRAM要貴得多。生產和使用,這兩個代價因子都很重要,後者則是越來越重要。為了理解這一點,我們分別看一下SRAM和DRAM一個位的儲存的實現過程。在本節的餘下部分,我們將討論RAM實現的底層細節。我們將盡量控制細節的層面,比如,在「邏輯的層面」討論訊號,而不是硬體設計師那種層面,因為那毫無必要。

2.1.1 靜態RAM

圖2.6 6-T靜態RAM

圖2.4展示了6電晶體SRAM的一個單元。核心是4個電晶體M1-M4,它們組成兩個交叉耦合的反相器。它們有兩個穩定的狀態,分別代表0和1。只要保持Vdd有電,狀態就是穩定的。當需要訪問單元的狀態時,升起字訪問線WL。BL和BL上就可以讀取狀態。如果需要覆蓋狀態,先將BL和BL設定為期望的值,然後升起WL。由於外部的驅動強於內部的4個電晶體,所以舊狀態會被覆蓋。更多詳情,可以參考[sramwiki]。為了下文的討論,需要注意以下問題:一個單元需要6個電晶體。也有采用4個電晶體的SRAM,但有缺陷。維持狀態需要恆定的電源。升起WL後立即可以讀取狀態。訊號與其它電晶體控制的訊號一樣,是直角的(快速在兩個狀態間變化)。狀態穩定,不需要重新整理迴圈。SRAM也有其它形式,不那麼費電,但比較慢。由於我們需要的是快速RAM,因此不在關注範圍內。這些較慢的SRAM的主要優點在於介面簡單,比動態RAM更容易使

2.1.2 動態RAM

動態RAM比靜態RAM要簡單得多。圖2.5展示了一種普通DRAM的結構。它只含有一個電晶體和一個電容器。顯然,這種複雜性上的巨大差異意味著功能上的迥異。

每個程式設計師都應該瞭解的記憶體知識

圖2.5 1-T動態RAM

動態RAM的狀態是保持在電容器C中。電晶體M用來控制訪問。如果要讀取狀態,升起訪問線AL,這時,可能會有電流流到資料線DL上,也可能沒有,取決於電容器是否有電。如果要寫入狀態,先設定DL,然後升起AL一段時間,直到電容器充電或放電完畢。動態RAM的設計有幾個複雜的地方。由於讀取狀態時需要對電容器放電,所以這一過程不能無限重複,不得不在某個點上對它重新充電。更糟糕的是,為了容納大量單元(現在一般在單個晶片上容納10的9次方以上的RAM單元),電容器的容量必須很小(0.000000000000001法拉以下)。這樣,完整充電後大約持有幾萬個電子。即使電容器的電阻很大(若干兆歐姆),仍然只需很短的時間就會耗光電荷,稱為「洩漏」。這種洩露就是現在的大部分DRAM晶片每隔64ms就必須進行一次重新整理的原因。在重新整理期間,對於該晶片的訪問是不可能的,這甚至會造成半數任務的延宕。(相關內容請察看【highperfdram】一章)這個問題的另一個後果就是無法直接讀取晶片單元中的資訊,而必須通過訊號放大器將0和1兩種訊號間的電勢差增大。最後一個問題在於電容器的衝放電是需要時間的,這就導致了訊號放大器讀取的訊號並不是典型的矩形訊號。所以當放大器輸出訊號的時候就需要一個小小的延宕,相關公式如下

每個程式設計師都應該瞭解的記憶體知識

這就意味著需要一些時間(時間長短取決於電容C和電阻R)來對電容進行衝放電。另一個負面作用是,訊號放大器的輸出電流不能立即就作為訊號載體使用。圖2.6顯示了衝放電的曲線,x軸表示的是單位時間下的R*C每個程式設計師都應該瞭解的記憶體知識

與靜態RAM可以即刻讀取資料不同的是,當要讀取動態RAM的時候,必須花一點時間來等待電容的衝放電完全。這一點點的時間最終限制了DRAM的速度。

當然了,這種讀取方式也是有好處的。最大的好處在於縮小了規模。一個動態RAM的尺寸是小於靜態RAM的。這種規模的減小不單單建立在動態RAM的簡單結構之上,也是由於減少了靜態RAM的各個單元獨立的供電部分。以上也同時導致了動態RAM模具的簡單化。

綜上所述,由於不可思議的成本差異,除了一些特殊的硬體(包括路由器什麼的)之外,我們的硬體大多是使用DRAM的。這一點深深的影響了我們們這些程式設計師,後文將會對此進行討論。在此之前,我們還是先了解下DRAM的更多細節。

2.1.3 DRAM 訪問

一個程式選擇了一個記憶體位置使用到了一個虛擬地址。處理器轉換這個到實體地址最後將記憶體控制選擇RAM晶片匹配了那個地址。在RAM晶片去選擇單個記憶體單元,部分的實體地址以許多地址行的形式被傳遞。它單獨地去處理來自於記憶體控制器的記憶體位置將完全不切實際:4G的RAM將需要 232 地址行。地址傳遞DRAM晶片的這種方式首先必須被路由器解析。一個路由器的N多地址行將有2N 輸出行。這些輸出行能被使用到選擇記憶體單元。使用這個直接方法對於小容量晶片不再是個大問題但如果許多的單元生成這種方法不在適合。一個1G的晶片容量(我反感那些SI字首,對於我一個giga-bit將總是230 而不是109位元組)將需要30地址行和230 選項行。一個路由器的大小及許多的輸入行以指數方式遞增當速度不被犧牲時。一個30地址行路由器需要一大堆晶片的真實身份另外路由器也就複雜起來了。更重要的是,傳遞30脈衝在地址行同步要比僅僅傳遞15脈衝困難的多。較少列能精確佈局相同長度或恰當的時機(現代DRAM型別像DDR3能自動調整時序但這個限制能讓他什麼都能忍受)

每個程式設計師都應該瞭解的記憶體知識

圖2.7展示了一個很高階別的一個DRAM晶片,DRAM被組織在行和列裡。他們能在一行中對奇但DRAM晶片需要一個大的路由器。通過陣列方法設計能被一個路由器和一個半的multiplexer獲得{多路複用器(multiplexer)和路由器是一樣的,這的multiplexer需要以路由器身份工作當寫資料時候。那麼從現在開始我們開始討論其區別.}這在所有方面會是一個大的儲存。例如地址linesa0和a1通過行地址選擇路由器來選擇整個行的晶片的地址列,當讀的時候,所有的晶片目錄能使其縱列選擇路由器可用,依據地址linesa2和a3一個縱列的目錄用於資料DRAM晶片的介面型別。這發生了許多次在許多DRAM晶片產生一個總記錄數的位元組匹配給一個寬範圍的資料匯流排。對於寫操作,記憶體單元的資料新值被放到了資料匯流排,當使用RAS和CAS方式選中記憶體單元時,資料是存放在記憶體單元內的。這是一個相當直觀的設計,在現實中——很顯然——會複雜得多,對於讀,需要規範從發出訊號到資料在資料匯流排上變得可讀的時延。電容不會像前面章節裡面描述的那樣立刻自動放電,從記憶體單元發出的訊號是如此這微弱以至於它需要被放大。對於寫,必須規範從資料RAS和CAS操作完成後到資料成功的被寫入記憶體單元的時延(當然,電容不會立刻自動充電和放電)。這些時間常量對於DRAM晶片的效能是至關重要的,我們將在下章討論它。另一個關於伸縮性的問題是,用30根地址線連線到每一個RAM晶片是行不通的。晶片的針腳是非常珍貴的資源,以至資料必須能並行傳輸就並行傳輸(比如:64位為一組)。記憶體控制器必須有能力解析每一個RAM模組(RAM晶片集合)。如果因為效能的原因要求併發行訪問多個RAM模組並且每個RAM模組需要自己獨佔的30或多個地址線,那麼對於8個RAM模組,僅僅是解析地址,記憶體控制器就需要240+之多的針腳。在很長一段時間裡,地址線被複用以解決DRAM晶片的這些次要的可擴充套件性問題。這意味著地址被轉換成兩部分。第一部分由地址位a0和a1選擇行(如圖2.7)。這個選擇保持有效直到撤銷。然後是第二部分,地址位a2和a3選擇列。關鍵差別在於:只需要兩根外部地址線。需要一些很少的線指明RAS和CAS訊號有效,但是把地址線的數目減半所付出的代價更小。可是地址複用也帶來自身的一些問題。我們將在2.2章中提到。2.1.4 總結如果這章節的內容有些難以應付,不用擔心。縱觀這章節的重點,有:

  • 為什麼不是所有的儲存器都是SRAM的原因
  • 儲存單元需要單獨選擇來使用
  • 地址線數目直接負責儲存控制器,主機板,DRAM模組和DRAM晶片的成本
  • 在讀或寫操作結果之前需要佔用一段時間是可行的

接下來的章節會涉及更多的有關訪問DRAM儲存器的實際操作的細節。我們不會提到更多有關訪問SRAM的具體內容,它通常是直接定址。這裡是由於速度和有限的SRAM儲存器的尺寸。SRAM現在應用在CPU的快取記憶體和晶片,它們的連線件很小而且完全能在CPU設計師的掌控之下。我們以後會討論到CPU快取記憶體這個主題,但我們所需要知道的是SRAM儲存單元是有確定的最大速度,這取決於花在SRAM上的艱難的嘗試。這速度與CPU核心相比略慢一到兩個數量級。

2.2 DRAM訪問細節

在上文介紹DRAM的時候,我們已經看到DRAM晶片為了節約資源,對地址進行了複用。而且,訪問DRAM單元是需要一些時間的,因為電容器的放電並不是瞬時的。此外,我們還看到,DRAM需要不停地重新整理。在這一節裡,我們將把這些因素拼合起來,看看它們是如何決定DRAM的訪問過程。我們將主要關注在當前的科技上,不會再去討論非同步DRAM以及它的各種變體。如果對它感興趣,可以去參考[highperfdram]及[arstechtwo]。我們也不會討論Rambus DRAM(RDRAM),雖然它並不過時,但在系統記憶體領域應用不廣。我們將主要介紹同步DRAM(SDRAM)及其後繼者雙倍速DRAM(DDR)。同步DRAM,顧名思義,是參照一個時間源工作的。由記憶體控制器提供一個時鐘,時鐘的頻率決定了前端匯流排(FSB)的速度。FSB是記憶體控制器提供給DRAM晶片的介面。在我寫作本文的時候,FSB已經達到800MHz、1066MHz,甚至1333MHz,並且下一代的1600MHz也已經宣佈。但這並不表示時脈頻率有這麼高。實際上,目前的匯流排都是雙倍或四倍傳輸的,每個週期傳輸2次或4次資料。報的越高,賣的越好,所以這些廠商們喜歡把四倍傳輸的200MHz匯流排宣傳為“有效的”800MHz匯流排。以今天的SDRAM為例,每次資料傳輸包含64位,即8位元組。所以FSB的傳輸速率應該是有效匯流排頻率乘於8位元組(對於4倍傳輸200MHz匯流排而言,傳輸速率為6.4GB/s)。聽起來很高,但要知道這只是峰值速率,實際上無法達到的最高速率。我們將會看到,與RAM模組交流的協議有大量時間是處於非工作狀態,不進行資料傳輸。我們必須對這些非工作時間有所瞭解,並儘量縮短它們,才能獲得最佳的效能。

2.2.1 讀訪問協議

每個程式設計師都應該瞭解的記憶體知識

圖2.8: SDRAM讀訪問的時序

圖2.8展示了某個DRAM模組一些聯結器上的活動,可分為三個階段,圖上以不同顏色表示。按慣例,時間為從左向右流逝。這裡忽略了許多細節,我們只關注時脈頻率、RAS與CAS訊號、地址匯流排和資料匯流排。首先,記憶體控制器將行地址放在地址匯流排上,並降低RAS訊號,讀週期開始。所有訊號都在時鐘(CLK)的上升沿讀取,因此,只要訊號在讀取的時間點上保持穩定,就算不是標準的方波也沒有關係。設定行地址會促使RAM晶片鎖住指定的行。CAS訊號在tRCD(RAS到CAS時延)個時鐘週期後發出。記憶體控制器將列地址放在地址匯流排上,降低CAS線。這裡我們可以看到,地址的兩個組成部分是怎麼通過同一條匯流排傳輸的。至此,定址結束,是時候傳輸資料了。但RAM晶片任然需要一些準備時間,這個時間稱為CAS時延(CL)。在圖2.8中CL為2。這個值可大可小,它取決於記憶體控制器、主機板和DRAM模組的質量。CL還可能是半週期。假設CL為2.5,那麼資料將在藍色區域內的第一個下降沿準備就緒。既然資料的傳輸需要這麼多的準備工作,僅僅傳輸一個字顯然是太浪費了。因此,DRAM模組允許記憶體控制指定本次傳輸多少資料。可以是2、4或8個字。這樣,就可以一次填滿快取記憶體的整條線,而不需要額外的RAS/CAS序列。另外,記憶體控制器還可以在不重置行選擇的前提下傳送新的CAS訊號。這樣,讀取或寫入連續的地址就可以變得非常快,因為不需要傳送RAS訊號,也不需要把行置為非啟用狀態(見下文)。是否要將行保持為“開啟”狀態是記憶體控制器判斷的事情。讓它一直保持開啟的話,對真正的應用會有不好的影響(參見[highperfdram])。CAS訊號的傳送僅與RAM模組的命令速率(Command Rate)有關(常常記為Tx,其中x為1或2,高效能的DRAM模組一般為1,表示在每個週期都可以接收新命令)。在上圖中,SDRAM的每個週期輸出一個字的資料。這是第一代的SDRAM。而DDR可以在一個週期中輸出兩個字。這種做法可以減少傳輸時間,但無法降低時延。DDR2儘管看上去不同,但在本質上也是相同的做法。對於DDR2,不需要再深入介紹了,我們只需要知道DDR2更快、更便宜、更可靠、更節能(參見[ddrtwo])就足夠了。

2.2.2 預充電與啟用

圖2.8並不完整,它只畫出了訪問DRAM的完整迴圈的一部分。在傳送RAS訊號之前,必須先把當前鎖住的行置為非啟用狀態,並對新行進行預充電。在這裡,我們主要討論由於顯式傳送指令而觸發以上行為的情況。協議本身作了一些改進,在某些情況下是可以省略這個步驟的,但預充電帶來的時延還是會影響整個操作。

每個程式設計師都應該瞭解的記憶體知識

圖2.9: SDRAM的預充電與啟用

圖2.9顯示的是兩次CAS訊號的時序圖。第一次的資料在CL週期後準備就緒。圖中的例子裡,是在SDRAM上,用兩個週期傳輸了兩個字的資料。如果換成DDR的話,則可以傳輸4個字。即使是在一個命令速率為1的DRAM模組上,也無法立即發出預充電命令,而要等資料傳輸完成。在上圖中,即為兩個週期。剛好與CL相同,但只是巧合而已。預充電訊號並沒有專用線,某些實現是用同時降低寫使能(WE)線和RAS線的方式來觸發。這一組合方式本身沒有特殊的意義(參見[micronddr])。發出預充電信命令後,還需等待tRP(行預充電時間)個週期之後才能使行被選中。在圖2.9中,這個時間(紫色部分)大部分與記憶體傳輸的時間(淡藍色部分)重合。不錯。但tRP大於傳輸時間,因此下一個RAS訊號只能等待一個週期。

如果我們補充完整上圖中的時間線,最後會發現下一次資料傳輸發生在前一次的5個週期之後。這意味著,資料匯流排的7個週期中只有2個週期才是真正在用的。再用它乘於FSB速度,結果就是,800MHz匯流排的理論速率6.4GB/s降到了1.8GB/s。真是太糟了。第6節將介紹一些技術,可以幫助我們提高匯流排有效速率。程式設計師們也需要儘自己的努力。SDRAM還有一些定時值,我們並沒有談到。在圖2.9中,預充電命令僅受制於資料傳輸時間。除此之外,SDRAM模組在RAS訊號之後,需要經過一段時間,才能進行預充電(記為tRAS)。它的值很大,一般達到tRP的2到3倍。如果在某個RAS訊號之後,只有一個CAS訊號,而且資料只傳輸很少幾個週期,那麼就有問題了。假設在圖2.9中,第一個CAS訊號是直接跟在一個RAS訊號後免的,而tRAS為8個週期。那麼預充電命令還需要被推遲一個週期,因為tRCD、CL和tRP加起來才7個週期。
DDR模組往往用w-z-y-z-T來表示。例如,2-3-2-8-T1,意思是:

w 2 CAS時延(CL)
x 3 RAS-to-CAS時延(t RCD)
y 2 RAS預充電時間(t RP)
z 8 啟用到預充電時間(t RAS)
T T1 命令速率

當然,除以上的引數外,還有許多其它引數影響命令的傳送與處理。但以上5個引數已經足以確定模組的效能。在解讀計算機效能引數時,這些資訊可能會派上用場。而在購買計算機時,這些資訊就更有用了,因為它們與FSB/SDRAM速度一起,都是決定計算機速度的關鍵因素。喜歡冒險的讀者們還可以利用它們來調優系統。有些計算機的BIOS可以讓你修改這些引數。SDRAM模組有一些可程式設計暫存器,可供設定引數。BIOS一般會挑選最佳值。如果RAM模組的質量足夠好,我們可以在保持系統穩定的前提下將減小以上某個時延引數。網際網路上有大量超頻網站提供了相關的文件。不過,這是有風險的,需要大家自己承擔,可別怪我沒有事先提醒喲。

2.2.3 重充電

談到DRAM的訪問時,重充電是常常被忽略的一個主題。在2.1.2中曾經介紹,DRAM必須保持重新整理。……行在充電時是無法訪問的。[highperfdram]的研究發現,“令人吃驚,DRAM重新整理對效能有著巨大的影響”。根據JEDEC規範,DRAM單元必須保持每64ms重新整理一次。對於8192行的DRAM,這意味著記憶體控制器平均每7.8125µs就需要發出一個重新整理命令(在實際情況下,由於重新整理命令可以納入佇列,因此這個時間間隔可以更大一些)。重新整理命令的排程由記憶體控制器負責。DRAM模組會記錄上一次重新整理行的地址,然後在下次重新整理請求時自動對這個地址進行遞增。對於重新整理及發出重新整理命令的時間點,程式設計師無法施加影響。但我們在解讀效能引數時有必要知道,它也是DRAM生命週期的一個部分。如果系統需要讀取某個重要的字,而剛好它所在的行正在重新整理,那麼處理器將會被延遲很長一段時間。重新整理的具體耗時取決於DRAM模組本身。

2.2.4 記憶體型別

我們有必要花一些時間來了解一下目前流行的記憶體,以及那些即將流行的記憶體。首先從SDR(單倍速)SDRAM開始,因為它們是DDR(雙倍速)SDRAM的基礎。SDR非常簡單,記憶體單元和資料傳輸率是相等的。

每個程式設計師都應該瞭解的記憶體知識
圖2.10: SDR SDRAM的操作

在圖2.10中,DRAM單元陣列能以等同於記憶體匯流排的速率輸出內容。假設DRAM單元陣列工作在100MHz上,那麼匯流排的資料傳輸率可以達到100Mb/s。所有元件的頻率f保持相同。由於提高頻率會導致耗電量增加,所以提高吞吐量需要付出很高的的代價。如果是很大規模的記憶體陣列,代價會非常巨大。{功率 = 動態電容 x 電壓2 x 頻率}。而且,提高頻率還需要在保持系統穩定的情況下提高電壓,這更是一個問題。因此,就有了DDR SDRAM(現在叫DDR1),它可以在不提高頻率的前提下提高吞吐量。


圖2.11 DDR1 SDRAM的操作

我們從圖2.11上可以看出DDR1與SDR的不同之處,也可以從DDR1的名字裡猜到那麼幾分,DDR1的每個週期可以傳輸兩倍的資料,它的上升沿和下降沿都傳輸資料。有時又被稱為“雙泵(double-pumped)”匯流排。為了在不提升頻率的前提下實現雙倍傳輸,DDR引入了一個緩衝區。緩衝區的每條資料線都持有兩位。它要求記憶體單元陣列的資料匯流排包含兩條線。實現的方式很簡單,用同一個列地址同時訪問兩個DRAM單元。對單元陣列的修改也很小。SDR DRAM是以頻率來命名的(例如,對應於100MHz的稱為PC100)。為了讓DDR1聽上去更好聽,營銷人員們不得不想了一種新的命名方案。這種新方案中含有DDR模組可支援的傳輸速率(DDR擁有64位匯流排):

100MHz x 64位 x 2 = 1600MB/s

於是,100MHz頻率的DDR模組就被稱為PC1600。由於1600 > 100,營銷方面的需求得到了滿足,聽起來非常棒,但實際上僅僅只是提升了兩倍而已。{我接受兩倍這個事實,但不喜歡類似的數字膨脹戲法。}

每個程式設計師都應該瞭解的記憶體知識

圖2.12: DDR2 SDRAM的操作

為了更進一步,DDR2有了更多的創新。在圖2.12中,最明顯的變化是,匯流排的頻率加倍了。頻率的加倍意味著頻寬的加倍。如果對單元陣列的頻率加倍,顯然是不經濟的,因此DDR2要求I/O緩衝區在每個時鐘週期讀取4位。也就是說,DDR2的變化僅在於使I/O緩衝區執行在更高的速度上。這是可行的,而且耗電也不會顯著增加。DDR2的命名與DDR1相仿,只是將因子2替換成4(四泵匯流排)。圖2.13顯示了目前常用的一些模組的名稱。

陣列頻率 匯流排頻率 資料率 名稱(速率) 名稱
(FSB)
133MHz 266MHz 4,256MB/s PC2-4200 DDR2-533
166MHz 333MHz 5,312MB/s PC2-5300 DDR2-667
200MHz 400MHz 6,400MB/s PC2-6400 DDR2-800
250MHz 500MHz 8,000MB/s PC2-8000 DDR2-1000
266MHz 533MHz 8,512MB/s PC2-8500 DDR2-1066

圖2.13: DDR2模組名

在命名方面還有一個擰巴的地方。FSB速度是用有效頻率來標記的,即把上升、下降沿均傳輸資料的因素考慮進去,因此數字被撐大了。所以,擁有266MHz匯流排的133MHz模組有著533MHz的FSB“頻率”。DDR3要求更多的改變(這裡指真正的DDR3,而不是圖形卡中假冒的GDDR3)。電壓從1.8V下降到1.5V。由於耗電是與電壓的平方成正比,因此可以節約30%的電力。加上管芯(die)的縮小和電氣方面的其它進展,DDR3可以在保持相同頻率的情況下,降低一半的電力消耗。或者,在保持相同耗電的情況下,達到更高的頻率。又或者,在保持相同熱量排放的情況下,實現容量的翻番。DDR3模組的單元陣列將執行在內部匯流排的四分之一速度上,DDR3的I/O緩衝區從DDR2的4位提升到8位。見圖2.14。

每個程式設計師都應該瞭解的記憶體知識
圖2.14: DDR3 SDRAM的操作

一開始,DDR3可能會有較高的CAS時延,因為DDR2的技術相比之下更為成熟。由於這個原因,DDR3可能只會用於DDR2無法達到的高頻率下,而且頻寬比時延更重要的場景。此前,已經有討論指出,1.3V的DDR3可以達到與DDR2相同的CAS時延。無論如何,更高速度帶來的價值都會超過時延增加帶來的影響。DDR3可能會有一個問題,即在1600Mb/s或更高速率下,每個通道的模組數可能會限制為1。在早期版本中,這一要求是針對所有頻率的。我們希望這個要求可以提高一些,否則系統容量將會受到嚴重的限制。圖2.15顯示了我們預計中各DDR3模組的名稱。JEDEC目前同意了前四種。由於Intel的45nm處理器是1600Mb/s的FSB,1866Mb/s可以用於超頻市場。隨著DDR3的發展,可能會有更多型別加入。

陣列頻率 匯流排頻率 資料速率 名稱(速率) 名稱
(FSB)
100MHz 400MHz 6,400MB/s PC3-6400 DDR3-800
133MHz 533MHz 8,512MB/s PC3-8500 DDR3-1066
166MHz 667MHz 10,667MB/s PC3-10667 DDR3-1333
200MHz 800MHz 12,800MB/s PC3-12800 DDR3-1600
233MHz 933MHz 14,933MB/s PC3-14900 DDR3-1866

圖2.15: DDR3模組名

所有的DDR記憶體都有一個問題:不斷增加的頻率使得建立並行資料匯流排變得十分困難。一個DDR2模組有240根引腳。所有到地址和資料引腳的連線必須被佈置得差不多一樣長。更大的問題是,如果多於一個DDR模組通過菊花鏈連線在同一個匯流排上,每個模組所接收到的訊號隨著模組的增加會變得越來越扭曲。DDR2規範允許每條匯流排(又稱通道)連線最多兩個模組,DDR3在高頻率下只允許每個通道連線一個模組。每條匯流排多達240根引腳使得單個北橋無法以合理的方式驅動兩個通道。替代方案是增加外部記憶體控制器(如圖2.2),但這會提高成本。這意味著商品主機板所搭載的DDR2或DDR3模組數將被限制在最多四條,這嚴重限制了系統的最大記憶體容量。即使是老舊的32位IA-32處理器也可以使用64GB記憶體。即使是家庭對記憶體的需求也在不斷增長,所以,某些事必須開始做了。一種解法是,在處理器中加入記憶體控制器,我們在第2節中曾經介紹過。AMD的Opteron系列和Intel的CSI技術就是採用這種方法。只要我們能把處理器要求的記憶體連線到處理器上,這種解法就是有效的。如果不能,按照這種思路就會引入NUMA架構,當然同時也會引入它的缺點。而在有些情況下,我們需要其它解法。Intel針對大型伺服器方面的解法(至少在未來幾年),是被稱為全緩衝DRAM(FB-DRAM)的技術。FB-DRAM採用與DDR2相同的器件,因此造價低廉。不同之處在於它們與記憶體控制器的連線方式。FB-DRAM沒有用並行匯流排,而用了序列匯流排(Rambus DRAM had this back when, too, 而SATA是PA他的繼任者,就像PCI Express是PCI/AGP的繼承人一樣)。序列匯流排可以達到更高的頻率,序列化的負面影響,甚至可以增加頻寬。使用序列匯流排後

  1. 每個通道可以使用更多的模組。
  2. 每個北橋/記憶體控制器可以使用更多的通道。
  3. 序列匯流排是全雙工的(兩條線)。

FB-DRAM只有69個腳。通過菊花鏈方式連線多個FB-DRAM也很簡單。FB-DRAM規範允許每個通道連線最多8個模組。在對比下雙通道北橋的連線性,採用FB-DRAM後,北橋可以驅動6個通道,而且腳數更少——6×69對比2×240。每個通道的佈線也更為簡單,有助於降低主機板的成本。全雙工的並行匯流排過於昂貴。而換成序列線後,這不再是一個問題,因此序列匯流排按全雙工來設計的,這也意味著,在某些情況下,僅靠這一特性,匯流排的理論頻寬已經翻了一倍。還不止於此。由於FB-DRAM控制器可同時連線6個通道,因此可以利用它來增加某些小記憶體系統的頻寬。對於一個雙通道、4模組的DDR2系統,我們可以用一個普通FB-DRAM控制器,用4通道來實現相同的容量。序列匯流排的實際頻寬取決於在FB-DRAM模組中所使用的DDR2(或DDR3)晶片的型別。我們可以像這樣總結這些優勢:

DDR2 FB-DRAM

DDR2 FB-DRAM
240 69
通道 2 6
每通道DIMM數 2 8
最大記憶體 16GB 192GB
吞吐量 ~10GB/s ~40GB/s

如果在單個通道上使用多個DIMM,會有一些問題。訊號在每個DIMM上都會有延遲(儘管很小),也就是說,延遲是遞增的。不過,如果在相同頻率和相同容量上進行比較,FB-DRAM總是能快過DDR2及DDR3,因為FB-DRAM只需要在每個通道上使用一個DIMM即可。而如果說到大型記憶體系統,那麼DDR更是沒有商用元件的解決方案。

2.2.5 結論

通過本節,大家應該瞭解到訪問DRAM的過程並不是一個快速的過程。至少與處理器的速度相比,或與處理器訪問暫存器及快取的速度相比,DRAM的訪問不算快。大家還需要記住CPU和記憶體的頻率是不同的。Intel Core 2處理器執行在2.933GHz,而1.066GHz FSB有11:1的時鐘比率(注: 1.066GHz的匯流排為四泵匯流排)。那麼,記憶體匯流排上延遲一個週期意味著處理器延遲11個週期。絕大多數機器使用的DRAM更慢,因此延遲更大。在後續的章節中,我們需要討論延遲這個問題時,請把以上的數字記在心裡。前文中讀命令的時序圖表明,DRAM模組可以支援高速資料傳輸。每個完整行可以被毫無延遲地傳輸。資料匯流排可以100%被佔。對DDR而言,意味著每個週期傳輸2個64位字。對於DDR2-800模組和雙通道而言,意味著12.8GB/s的速率。但是,除非是特殊設計,DRAM的訪問並不總是序列的。訪問不連續的記憶體區意味著需要預充電和RAS訊號。於是,各種速度開始慢下來,DRAM模組急需幫助。預充電的時間越短,資料傳輸所受的懲罰越小。硬體和軟體的預取(參見第6.3節)可以在時序中製造更多的重疊區,降低延遲。預取還可以轉移記憶體操作的時間,從而減少爭用。我們常常遇到的問題是,在這一輪中生成的資料需要被儲存,而下一輪的資料需要被讀出來。通過轉移讀取的時間,讀和寫就不需要同時發出了。

2.3 主存的其它使用者

除了CPU外,系統中還有其它一些元件也可以訪問主存。高效能網路卡或大規模儲存控制器是無法承受通過CPU來傳輸資料的,它們一般直接對記憶體進行讀寫(直接記憶體訪問,DMA)。在圖2.1中可以看到,它們可以通過南橋和北橋直接訪問記憶體。另外,其它匯流排,比如USB等也需要FSB頻寬,即使它們並不使用DMA,但南橋仍要通過FSB連線到北橋。DMA當然有很大的優點,但也意味著FSB頻寬會有更多的競爭。在有大量DMA流量的情況下,CPU在訪問記憶體時必然會有更大的延遲。我們可以用一些硬體來解決這個問題。例如,通過圖2.3中的架構,我們可以挑選不受DMA影響的節點,讓它們的記憶體為我們的計算服務。還可以在每個節點上連線一個南橋,將FSB的負荷均勻地分擔到每個節點上。除此以外,還有許多其它方法。我們將在第6節中介紹一些技術和程式設計介面,它們能夠幫助我們通過軟體的方式改善這個問題。最後,還需要提一下某些廉價系統,它們的圖形系統沒有專用的視訊記憶體,而是採用主存的一部分作為視訊記憶體。由於對視訊記憶體的訪問非常頻繁(例如,對於1024×768、16bpp、60Hz的顯示設定來說,需要95MB/s的資料速率),而主存並不像顯示卡上的視訊記憶體,並沒有兩個埠,因此這種配置會對系統效能、尤其是時延造成一定的影響。如果大家對系統效能要求比較高,最好不要採用這種配置。這種系統帶來的問題超過了本身的價值。人們在購買它們時已經做好了效能不佳的心理準備。繼續閱讀:

 

相關文章