英文原文: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是PATA的繼任者,就像PCI Express是PCI/AGP的繼承人一樣)。串行總線可以達到更高的頻率,串行化的負面影響,甚至可以增加帶寬。使用串行總線后
- 每個通道可以使用更多的模塊。
- 每個北橋/內存控制器可以使用更多的通道。
- 串行總線是全雙工的(兩條線)。
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的數據速率),而主存并不像顯卡上的顯存,并沒有兩個端口,因此這種配置會對系統性能、尤其是時延造成一定的影響。如果大家對系統性能要求比較高,最好不要采用這種配置。這種系統帶來的問題超過了本身的價值。人們在購買它們時已經做好了性能不佳的心理準備。繼續閱讀:
- 第2節: CPU的高速緩存
- 第3節: 虛擬內存
- 第4節: NUMA系統
- 第5節: 程序員可以做什么 – 高速緩存的優化
- 第6節:?程序員可以做什么?- 多線程的優化
- 第7節: 內存性能工具
- 第8節: 未來的技術
- 第9節: 附錄與參考書目