【译】每个程序员都应该知道的内存知识-第一部分

译文背景

来自耗子叔(左耳朵耗子)在极客时间开的专栏文章推荐。原文成文于2007年左右,地址为https://lwn.net/Articles/250967/。全文分为8个部分,接下来想一篇篇翻译出来,一是为了自己好好读,并与大家一起学习。
译文中有错误或不准确,没理解到的地方,请多多包涵~

译文正文

编辑说明

Ulrich Drepper最近找到我们并询问我们是否有兴趣发布一篇关于内存与软件如何交互的长文。我们没看太多,就知道这一定会是我们的读者感兴趣的。内存使用通常来说是软件表现的关键性因素,但很难找到关于如何避免内存瓶颈的信息。接下来这一系列的文章将会改变这一状况。
原文长达100多页,我们将它分为7部分,每一部分将在前一部分发布的1-2周后发布。当系列文章出完,Ulrich Drepper将会放出全文。
将文章从原本的LaTeX格式重新整理是有一些挑战性的,但希望结果会好。为了方便在线阅读,Ulrich的注脚被替换为“{在文本行内}”这样的格式。超链(和[参考书目])要到全文放出时才会有。
非常感谢Ulrich让我们LWN来发布这篇文章,我们希望在不久的将来它能引导我们系统开发出更多的高效内存软件。

1.介绍

早期电脑更为简单。在各种组件中,如CPU,内存,大容量存储器,和网络接口都是一起开发的,作为结果他们的表现也相当均衡。举例来说,内存和网络接口在提供数据方面并不比CPU快很多。
这一情况在电脑的基础结构稳定后发生了改变,硬件开发开始专注于单个子系统的优化。突然间电脑的一些组件的表现被极大地甩在了后面,瓶颈也产生了。尤其是大容量存储器和内存,因为成本的原因,相对于其它的组件提升更慢。
大容量存储器的慢问题几乎已由软件技术处理:操作系统将最常用(也是最可能被使用)的数据存在主存,主存比硬盘的访问速度快几个数量级。缓存存储也被加到存储设备本身,这样操作系统不用改动就可提升表现{然而,在使用存储设备缓存时,还是需要一些改动来保证数据的完整性}。出于这篇文章的目的,我们不会对访问大容量存储器的软件优化的细节深入过多。
不像存储,把主存这一瓶颈移除是一个更为困难的事情,几乎所有的解决方案都需要对硬件进行改动。目前改动主要有如下形式:
(1)RAM硬件设计(速度和并行)
(2)内存控制器设计
(3)CPU缓存
(4)设备的直接内存访问(DMA)
本文大部分将讨论CPU缓存和内存控制器设计的一些效果。在探索这些主题时,我们将探索DMA,并把它带入更大的图景中。然而,我们以当今商业硬件的设计概要作为开始。这是理解高效使用内存的限制和问题的前提。我们也会学习到不同种类的RAM,并阐明这些不同为何仍然存在的细节。
这篇文章绝不是包罗所有,也绝不是最终稿。它受限于商业硬件,也因而受限于商业硬件的子集。同样,为了本文的主旨,很多话题只会讨论到一定程度的细节。对于这些话题,建议读者去找寻更细节的文献。
关于具体操作系统的细节和解决方案,本文只是描述了linux。在任何时候本文都不会涵盖其它操作系统的信息。作者也对其它操作系统的实现没有兴趣。如果读者不得不使用别的OS,得去经销商那要求写一份类似的文档。
在开始前得最后说一句,本文包含了很多的“通常来说”,以及其它类似的限定词。因为讨论到的技术在真实世界中有多种变体,本文只是谈及最普通和主流的版本,能对某一技术下绝对论断的情况是罕见的,所以也不会用类似的限定词。

1.1文章结构

这篇文章几乎是给软件开发者看的。对于想看硬件相关的读者,它没有对硬件技术细节涉及得足够多,不够实用。在我们为开发者探讨更多的实践信息之前,必须铺陈一些背景信息。
为此,第二节描述了随机存取缓存RAM的技术细节。这一部分的内容不是为理解后续章节所必须的,但了解更好。在谈及的地方将会有对有相关内容的适当引用,所以心急的读者可以先跳过这一part(第二节)大部分内容。
第三节探讨了CPU缓存行为的大量细节。为避免内容太干,使用了图表。这一部分的内容是理解后续内容的基础。第四节简述了虚拟内存的实现。这也是后续内容所须的背景信息。
第五节探讨了非均匀访存模型(Non Uniform Memory Access)系统的大量细节。
第六节是本文的中心。它汇总了前面所有部分的信息,给程序员在不同场景下写出表现优良的代码提供了建议。非常没耐心读者可从这一部分看起,有必要的时候再回到前面看底层技术。
第七节介绍了可以帮助程序员写出好代码的工具。即使对技术有了完整的了解,也很难在一个大型的系统中明确地定位问题。一些工具是必要的。
在第八节我们展望了在不久的将来会迎来怎样的技术,或者怎样的技术会更好。

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的文章还未广为人知,虽然它对任何敢于拿起键盘做严肃编程的人来说,是一个前提条件。

2.当今的商业硬件

了解商业硬件很重要,因为专门化硬件逐渐式微。如今的伸缩性(scaling)大部分是通过水平伸缩,而非垂直伸缩。这意味着使用很多更小的,相连的商业电脑,比少量的非常大和快(贵)的系统更具性价比。这是因为快而不贵的网络硬件广为可用。也有一些大而专业的系统能发挥其作用的场景,仍是一个商机,但整体的市场被商业硬件市场矮化了。2007年的Red Hat预言未来的产品–给大多数数据中心所用的“标准建造块”(standard building blocks),将是一台这样的计算机:这个计算机有4个插槽,每个都配有一个4核CPU,如果是intel CPU的话,它是超线程的{超线程使得单一处理器核心可以并行2个或多个任务,只需要一点点额外的硬件}。这意味着数据中心的一台标准系统机器上将有64个虚拟处理器。也可有更大的机器,但当前认为4插槽,4核的机器是最佳的,并且大部分的优化也将基于这类机器。
商业电脑的架构会有更大的区别。我们将专注于讨论最重要的区别,讨论涵盖超90%的这类硬件。需要注意的是,这些技术细节会迅速变化,所以建议读者将本文的成文时间考虑在内。
这些年来个人电脑和小服务器被一块芯片标准化了,这一芯片由2部分组成:北桥和南桥。
图2.1 北桥和南桥的结构
所有的CPU(图中是2个,但也可能会更多)通过一个共用总线(the Front Side Bus, FSB)连至北桥。北桥包括了内存控制器(memory controller)和其它的一些组件,内存控制器的实现决定了计算机所用RAM芯片的类型。不同类型的RAM,如DRAM,Rambus,SDRAM,需要不同的内存控制器。
为抵达系统的其它设备,北桥须与南桥通信。南桥也被称为I/O桥,通过不同的总线与设备的沟通。目前PCI,PCI Express,SATA和USB总线最为重要,但南桥也支持PATA, IEEE 1394, 串口,并行口。老的系统有连到北桥的AGP槽,这是为解决南北桥连接不够快这一性能原因所为。然而,如今的PCI-E槽都连至南桥了。
这样一个系统架构导致了一系列值得注意的结果:
(1) 从一个CPU到另一个的通信必须经由CPU与北桥相连的总线
(2) 所有与RAM的通信要经过北桥
(3) RAM只有1个端口{本文我们不讨论多端口RAM因为在商业硬件中没有,至少不在程序员可以接触到的范围内。在专业化的硬件,如网络路由器中可以找到它,路由器依赖于极致的速度。}
(4) CPU和与南桥相连的设备的通信要经由北桥
在这个设计中一些瓶颈是显而易见的。其中之一就是设备对RAM的访问。在早期的PC中,与任何一个桥上的设备通信都要经过CPU,这对整个系统的性能造成负面影响。为解决这一问题,一些设备变得可以通过直接内存访问(DMA)。DMA技术使设备在北桥帮助下,可以直接存取RAM的数据,避免了CPU的干预(也避免了固有的性能消耗)。如今与总线相连的高性能设备都可以使用DMA。虽然这大大减少了CPU的负载,但它也造成了北桥的带宽竞争(contention for the bandwidth),因为DMA访问RAM是与CPU访问RAM竞争的。这一问题必须纳入考虑了。
第二个瓶颈涉及到北桥连至RAM的总线。总线的确切细节依赖于配置的内存类型。在老的系统中,仅有一条总线连接所有的RAM芯片,所以并行访问是不可能的。近期的RAM需要2条分开的总线(或通道,因为它们被称为DDR2,见图2.8),这将可用带宽翻了一番。
内存在通道中的通信在北桥交替进行。更近期的内存技术(如FB-DRAM)添加了更多的通道。
因为可用的带宽有限,以一种最小化延迟的方式规划内存访问很重要。我们将看到,即使使用了CPU缓存,处理器因为更快所以必须等着访问内存。在多个超线程,或核心,或处理器同时访问内存的情况下,等待内存访问的时间甚至更长。DMA操作也是如此。
然而除了并行,还有更多关于访问内存的东西要说。访问模式本身也极大地影响了内存子系统的性能表现,尤其是存在多个通道的情况下。参考第二节的2.2看关于RAM访问模式的更多细节。
在一些更贵的系统中,北桥实际上不包含内存控制器。取而代之的,北桥可与大量外部的内存控制器相连(在下面例子中,有4个)。
图2.2 有外部控制器的北桥
这一架构的优势是有多于一条的内存总线存在,所以总带宽增加了。该设计也支持了更大的内存。并行内存访问模式通过同时访问不同的内存库减少了延迟。在图2.2中,当多个处理器直接连至北桥时,这一效果尤为明显。在这样的设计中,主要的限制是北桥的内部带宽,而该架构的带宽是惊人的(来自Intel)。{作为补充需要提及这样的内存控制器排布可用于其它目的如内存RAID,这对于组合热插拔内存来说很有用。}
使用多个外部内存控制器不是提升内存带宽的唯一方法。有一个正愈发流行的方式是将内存控制器集成于CPU中,并给每个CPU都加内存。基于AMD皓龙处理器的SMP系统使得这一架构变得广为流行。下图展示了SMP系统。Intel将从Nehalem处理器开始支持通用系统接口(Common System Interface (CSI)),这基本上是同一种方式:集成一个内存控制器,可为每个处理器提供本地内存。

图2.3 集成的内存控制器
在类似这样的架构中,有多少处理器就能有多少内存库。在一个4核机器中,内存带宽被扩大4倍,不需要一个有巨大带宽的复杂北桥。有个集成的内存控制器在CPU中,还有一些其它的优势,我们在这里不会对此技术继续深入了。
这一架构也有劣势。首先,因为机器还是要让所有的内存可被所有的处理器访问,内存就不再均一了。(所以针对这一架构有了这个名字NUMA, Non-Uniform Memory Architecture非均匀访存模型)。本地内存(连至处理器的内存)可以正常速度访问,而与其它处理器相连的内存访问的情况就不同了。在这一情况下不得不使用到不同处理器的互连(interconnect)。从CPU1访问CPU2连着的内存需要通过一次互连,当CPU1访问CPU4连着的内存时,2个互连就不得不交错了。
每次这样的通信都会有一个相关的消耗,我们用“NUMA因素“(NUMA factors)描述访问远程内存需要的额外时间。图2.3的例子有2个级别的CPU:一次互连就能访问的相邻CPU和需要2次互连才能访问的CPU。在更复杂的机器上这个级别的数量会有可观的上涨。也有一些机器有不止一种的互连,比如IBM的x445,和SGI的Altix系列。CPU们被组织成节点,在一个节点里的CPU访问内存的时间是统一的,且只有小的NUMA因素。但节点之间互连代价高昂,NUMA因素可能相当高。
商业NUMA机器如今存在而且可能在将来会扮演更大的角色。据预期,2008年尾之后,每一款SMP机器都会使用NUMA。由于与NUMA相关的损耗,意识到何时将程序跑在NUMA机器上是重要的。在第五节我们将会讨论更多的机器架构,以及Linux内核为这些程序提供的一些技术。
在本章剩下的部分讨论的技术细节之外,还有几个额外的因素会影响RAM的性能。它们不被软件控制,所以不在本部分讨论。感兴趣的读者可在第二节的2.1中看到这其中的一些因素。它们真的仅仅只是在你对RAM技术有一个更完整的认知时有用,或者可能是对你在购买电脑时做更好的选择有用。
接下来的2小节讨论了入门级别的硬件细节,和内存控制器与DRAM芯片间的访问协议。程序员可能会发现这些信息有启发性,因为这些细节解释了为何内存访问这样进行。但这是可选的知识,所以急于去到与日常更直接相关话题的读者可以跳至2.2.5。

2.1RAM类型

近年来出现了很多不同类型的RAM,每一种都不同,有一些与其它的RAM存在显著的差异。老的类型在现在只有考古学家对它们感兴趣。我们不探究这些老的类型。我们将专注于现代RAM类型,仅仅只是触及表面,探索对内核开发者或应用开发者可见的一些细节,并通过性能特征展示。
第一个有趣的细节围绕为何一台机器上有不同的RAM类型这一问题展开。更具体来说,为何既有静态RAM(SRAM{在别的文本中SRAM可能是“synchronous RAM”同步内存})又有动态RAM。前者更快,且提供一样的功能。为什么一台机器中不都是SRAM呢?答案是,可能你已预知,成本。SRAM比DRAM的生产和使用都更贵。这2个成本因素都重要,但第二个越来越重要。为了理解区别我们来看看一小块SRAM存储和一小块DRAM存储的实现。
在剩下的部分中我们将讨论RAM实现的一些底层细节。细节会尽可能的底层。为此,我们将会在一个逻辑层面上讨论信号量而不是硬件开发者的层面。那种层面上的细节对此处的主旨是不必要的。

2.1.1静态RAM

图2.4 6晶体管的静态RAM
图2.4展示了6晶体管SRAM元件的结构。这一元件的核心是由4个晶体管M1到M4所组成的2个交叉耦合逆变器。它们有2个稳定状态,分别代表0和1。只要Vdd连通电源,这一状态就是稳定的。
当需要访问元件的状态时,WL被设为高电平。这使得元件的状态马上可通过BL和/BL读到。如果状态需要重写,BL和/BL会首先被设置为需要的值,然后设WL高电平。由于外部驱动比这4个晶体管(M1至M4)更强,所以使得老状态可被重写。
查看[wiki上的SRAM]了解更多该元件工作的细节描述。对接下来的讨论重要的是:
(1)1个元件需要6个晶体管。有含4个晶体管的变体,但它们有劣势
(2)维持元件的状态需要持续供电
(3)元件的状态在WL设高电平后几乎即刻可读。信号量和其它由晶体管控制的信号量一样是矩形的(2个二进制状态间的变化快)
(4)元件的状态是稳定的,不需要刷新周期
有其它更慢的,低耗电的SRAM 可用,因为我们在看快速RAM,所以它们不在我们关心的范围内。这些更慢的变体,由于他们简单的接口而比动态RAM更易使用,而被感兴趣。

2.1.2动态RAM

动态RAM在结构上比静态RAM简单很多。图2.5展示了一个通常的DRAM元件设计。所有它包含的是一个晶体管和一个电容器。这个在复杂性上巨大的差异意味着它与静态RAM发挥作用的机制非常不同。
图2.5 1晶体管动态RAM
一个动态RAM元件在电容器C上保持状态。晶体管M用来守卫对状态的获取。当需要访问元件的状态时,AL就会被设为高电平。这使得DL上电或断电,取决于电容器的充电过程。需要写入状态时,DL就会被设置,然后AL会被设置高电平持续足够长的时间来使电容器充电或放电。
动态RAM的设计有很多的复杂性。对电容器的使用意味着读取元件需要对电容放电。这一过程不能无限重复,某个点上电容器必须重新充电。更糟的是,为了部署大量的元件(有10^9或更多个元件的芯片如今很正常),电容的电容量必须低(在飞法拉范围或更低)。一个充满电的电容器可容纳数十或数千个电子。即使电容器的电阻高(几个太欧姆),只需要很短的时间,电量就会消失。这一问题被称为“泄漏”。
泄漏就是DRAM元件必须持续刷新的原因。对于如今大多数的DRAM芯片来说必须每64ms刷新一次。在刷新周期里不能访问。在一些负载中,这一开销可能阻塞50%的内存访问(见[highperfdram])。
第二个由这小小充电导致的问题是,从元件读取信息不是即刻的。DL必须连接信号放大器,来区分0和1,整个充电过程也要被算作1。
第三个问题是电容器的充放电不是即刻的。信号放大量接收到的信号不是矩形的,所以要对何时能取用元件的输出做保守评估。电容器的充放电时间的公式:
电容充放电公式
说明充电和放电都需要时间(决定于电容C和电阻R)。也说明能被信号放大器探测到的电流不是即刻可读的。图2.6展示了充电和放电曲线。X轴以RC(电阻和电容的乘积)为单位进行测量,也是时间单位。
图2.6 电容的充电和放电时间
不像静态RAM在WL高电平后输出马上可被读取,动态RAM总是需要一点时间让电容器充分放电。这一延迟严重限制了DRAM的速度。
这一简单的设计也有它的优势。主要的优势是大小。芯片上放一个DRAM元件所须的大小比一个SRAM的小很多倍。SRAM元件也需要单独为晶体管供电来维持状态。DRAM元件的结构也更简单和规律,意味着将许多个它们紧密地封闭在一个芯片上更为简单。
总的来说,在成本上的差异(非常戏剧性)赢了。除了在专门的硬件上,如网络路由器,我们不得不与基于DRAM的主存一起生活。这一影响对程序员是巨大的,本文其余的部分会讨论。但我们需要先来看看更多DRAM元件使用的细节。

2.1.3DRAM访问

程序使用虚拟地址来访问内存。处理器将它翻译成物理地址,最终由内存控制器据此相应地选择内存芯片。为了在内存芯片上找到单个的内存单元,部分物理地址以一些地址线数字的形式传递。
从内存控制器上单个地找出内存所在是完全不可行的,因为这样4G的RAM需要2 ^ 32条地址线。取而代之的是,将地址编码成二进制数来传递,会使用更少的地址线。被传递到DRAM芯片的这样的地址首先需要解复用。一个有N个地址线解复用器可输出2 ^ N个地址。这些输出地址被用来选择内存单元。用这种直接访问方式对小容量的芯片来说不是什么大问题。
但随元件数量的增长这个方式变得不再适用。有一个G容量的芯片需要30条地址线{我讨厌那些SI前缀,对我来说1G总是2 ^ 30而不是10 ^ 9个bit},也就是2^30个选择线。如果不牺牲速度,解复用器的大小会随输入线指数性地增长。30个地址线的解复用器除了复杂性(大小和时间)之外,还需要很大的芯片空间。更为重要的是,在地址线上同时传递30个脉冲,比仅仅传递15个难得多。Fewer lines have to be laid out at exactly the same length or timed appropriately.
图2.7 动态RAM的排布
图2.7在一个高层级显示了一个DRAM芯片的样子,DRAM元件被以行和列组织起来。它们也可以被排成一行,但这样DRAM芯片就需要一个巨大的解复用器。通过阵列方式,这一设计可由1个解复用器和半个它大小的复用器实现。{复用器和解复用器是一样的,这里的复用器在写的时候需要当成解复用器用。所以我们从这里开始去掉对它们的区分}这对于所有的前端来说是一个巨大节省。在例子中地址线a0和a1通过地址选择器/RAS解复用器选择由所有行的元件组成的地址线。当读取时,所有元件的内容就这样可被列地址选择器/CAS复用器读取。{名字上的线表明信号是负值}基于地址a2和a3, 一列的的内容就可被DRAM芯片的数据引脚读取。这会在很多DRAM芯片上并行发生多次,以产出与数据总线带宽相当的bit总数。
当写入时,新的元件值被放在数据总线,当元件被/RAS和/CAS选中后,就被存下来。一个非常直接的设计。当然在实际中显然会有更多的复杂性。需要对可读信号释出后有多久的延迟,数据总线上才可读做一个规范。如前文所述,电容器不会瞬间放电。从元件传出的信号是这么微弱以致于它需要被放大。在写入时也必须要规定在/RAS和/CAS完成选出工作后,数据总线上的数据需要保持多长时间来使新值被成功写入元件(再说一次,电容充放电不能瞬间完成)。这些时间常量对DRAM芯片的表现是至关重要的。在下一部分我们将继续讨论。
第2个伸缩性问题是跟每个RAM芯片连接30条地址线也是不可行的。芯片的引脚是珍贵的资源。数据必须尽可能并行传输已经足够“糟”了(比如64位批量地)。内存控制器必须寻址到每一块内存模块(RAM芯片的集合)。如果为了性能表现需要并行访问多个内存模块,且每个内存模块都需要30个或更多的地址线,在8个RAM模块的情形下,这样内存控制器仅仅是为寻址就要有240+个引脚。
为了解决这一伸缩性问题,DRAM芯片一直以来在自行复用地址。这意味着地址以两部分传递。第一部分包含地址位a0和a1,如图2.7中例子,来选择行。这一选择会保持激活直到下一次被唤起。然后是第二部分,地址位a2和a3,来选择列。这一关键性的区别在于仅需要2条外部地址线。还有少量线用来指示/RAS和/CAS的信号可读,但这只是将地址线数量减半而付出的小代价而已。但这种地址复用引起了一系列问题,我们将会在2.2节讨论它们。

2.1.4结论

不用担心对这一部分的细节掌握得有点吃力。从中学习到的重要点是:
(1) 为什么不是所有的内存都是SRAM是有原因的
(2) 内存元件需要被单个地选出来加以使用
(3) 地址线的数量直接决定了内存控制器,主板,DRAM模块,DRAM芯片的成本
(4) 读和写操作的结果获取需要一些时间
接下来的内容我们探讨关于访问DRAM内存的实际过程的更多细节。我们不打算讲访问SRAM的更多细节,因为它通常是直接寻址。这是为了速度,也是因为SRAM的大小有限。SRAM目前使用在CPU缓存上,以片上集成的方式,这方式中连接是小而完全在CPU设计者的掌握中的。CPU缓存是一个我们后面会谈的主题,但所有我们所需要知道的是SRAM元件有一个确定的最大速度,它取决于花费在SRAM的努力。这一速度有从仅稍慢于CPU内核到慢1-2个数量级的区别。

2.2 DRAM访问技术细节

在介绍DRAM一部分中我们看到DRAM芯片为节省资源复用地址。我们也看到因为元件中的电容不能瞬间放电以产生稳定的信号,所以访问DRAM元件需要一些时延。我们也看到DRAM元件必须被刷新。现在我们把它们放在一起说,并看看这些因素如何决定了DRAM访问进行。
我们将专注于当前的技术,不讨论异步DRAM和它的变体,因为这与现在的技术不相关了。感兴趣的读者可看[highperfdram] 和[arstechtwo].。即使Rambus DRAM (RDRAM)还没过时,但我们也不会谈及。因为它没有在系统内存中被广泛使用。我们将专门集中在同步RAM(SDARM)和它的后继者Double Data Rate DRAM (DDR)。
同步DRAM,正如其名,按时间顺序工作。内存控制器提供一个时钟,时钟的频率决定了前端总线(Front Side Bus FSB)的速度,FSB是DRAM芯片使用的内存控制器的接口。写文时,市面上有800MHz,1066MHz,甚至1333MHz的频率的芯片,下一代宣布了更高频的1600MHz。这不意味着总线频率真这么高。如今的总线是被乘了2倍,或4倍的,即每一时钟周期数据是2倍或4倍地传输。更高的数据更好卖,所以制造商喜欢宣传一个加了4倍的200MHz总线为一个“高效的”800MHz总线。
如今SDRAM传递的数据有64位,8字节。FSB的数据传输率也因此是8bytes乘高效总线频率(对加4倍的200MHz总线来说是6.4G/s)。听起来很多,但这是一个突发速度(burst speed),即一个不可能被超越的最大值。我们可以看到,与RAM模块通信的协议中存在很多停机时间,这一段时间中没有数据被传递。正是这个停机时间我们需要理解和最小化,以达到最佳性能。

2.2.1读取协议

图2.8 SDRAM的读取时序图
图2.8展示了3个颜色所示的不同阶段中一些DRAM模块中的连接器件上的活动。如往常,时间流是从左向右的。忽略了很多细节。在这里我们仅讨论总线时钟,/RAS和/CAS信号,地址总线,和数据总线。读周期开始于内存控制器使行地址在地址总线上可用,并给/RAS一个低电平。所有的信号在时钟的上升沿可读,所以如果信号不完全是方形的,只要在读的时候是稳定的就不成问题。设置行地址使RAM芯片开始锁住寻址的行。
/CAS信号可以在tRCD(/RAS-/CAS的延时)个时钟周期后发送。给/CAS一个低电平后,列地址在地址总线上可用。这里我们可以看见地址的2个部分(哪一半有更多或更少的地址,并不重要)怎样通过相同的地址总线进行传输。
现在寻址已经完成,数据可以传输了。RAM芯片需要一点时间准备。这一时延称为/CAS时延(CL)。在图2.8中CL是2。它可能更高或更低,取决于内存控制器,主板,和DRAM模块的质量。这一时延也可能有.5的值。CL=2.5时,第一个数据将在紫色区的第一个下降沿上可读。
在这所有的为读取数据所做的准备工作之后,如果只传输一个数据就浪费了。这也是为什么DRAM模块允许内存控制器指定它要传输多少数据。通常这一选择在2,4,8之间。这使得缓存中整条线被填满,而不需要新的RAS/CAS系列过程。内存控制器也可能在不重置行选择的情况下发送新的/CAS信号。这样,连续的内存地址可以以更快的速度读和写,因为不需要发送/RAS信号,行也不会被停用(可看下面)。使行“激活”是一件内存控制器需要决定的事情。在真实应用中,推测性地使行一直激活有其劣势(见[highperfdram])。发送新的/CAS信号仅受RAM模块的首命令延迟(Command Rate)影响(通常被设置为Tx,x是一个1或2这样的值),对于高性能DRAM模块来说,1表示它在每个时钟周期都接受指令。
在这个例子中SDRAM每个周期都吐出1个字,这是第一代的功能。DDR则可在一个周期传输2个字。这减少了传输时间,但并没有改变时延。原则上来说,DDR2是一样工作的虽然实践上表现不同。这里没必要深入细节。只要知道DDR2会更快,更便宜,更稳定,更节能就足够了。(参阅[ddrtwo]获取更多信息)

2.2.2预充电和激活

图2.8没有包含整个周期。它只展示了访问DRAM的整个周期中的一部分。在一个新的/RAS信号发送前,当前被锁的行需要停用,新的行必须预充电。我们可以专注于通过一个显式指令实现的情况。某些情况下对协议的改进,允许避免这一额外的步骤。但预充电造成的时延仍对操作有影响。
图2.9 SDRAM的预充电与激活
图2.9展示了从1个/CAS到针对另一行的/CAS间发生的活动。如前所述,在第1个/CAS信号后的CL时延后可访问数据。例子中获取了2个字,在一个简单SDRAM中,需要2个时钟周期传输。而在一个DDR芯片上,2个时钟周期可以传输4个字。
即使在首命令延迟为1的DRAM模块上,预充电命令也不是可以立即发出的。有必要等待足够长的时间来传输数据。上例中是2个时钟周期。有时这和CL时延一样长,但例子中仅仅只是巧合。预充电信号没有专用线路。相反的,某些实现是通过在/WE(可写)和/RAS线上同时给一个低电平。这一组合本身没有有用的含义。(参阅[micronddr]查看编码详情)
预充电信号发出后需要等待tRP(行预充电时间)的时间后行才可以被选中。在图2.9中大部分的tRP(紫色)与内存传输时间重叠(浅紫色)。这很好!但是tRP大于传输时间,所以下一个/RAS信号要滞留一个时钟周期。
如果我们继续画图中的时间线可以发现,下一次的数据传输在前一次停止后的5个时钟周期后发生。这意味着数据总线在7个时钟周期中只有2个周期被使用。乘上FSB的速度后理论上的6.4G/s的800M总线变成1.8G/s。这很糟且必须避免。在第六节中讲到的技术将会帮助提高这一数值,但程序员通常也得参与进来。
还有一个关于SDRAM模块的时间参数我们没提到。在图2.9中预充电命令仅被数据传输时间所限制。另一个限制是SDRAM模块在/RAS信号后需要一定时间才能预充电另一行(该时间用tRAS表示)。这个值通常比较大,是2到3倍tRP的量级。如果在/RAS信号后,仅跟着1个/CAS且数据传输在几个时钟周期后就结束的话,这就是个问题。假设在图2.9中最初的/CAS紧跟着/RAS,tRAS是8个时钟周期。这样一来,预充电指令不得不再延迟1个时钟周期,因为tRCD, CL, 和 tRP(它大于数据传输时间,所以只计它)的和仅为7个时钟周期。
DDR模块通常用这个特殊方式描述:w-x-y-z-T,例如 2-3-2-8-T1,意为:
w 2 /CAS时延(CL)
x 3 /RAS至/CAS 时延 (tRCD)
y 2 /RAS的预充电时延(tRP)
z 8 激活至预充电时延(tRAS)
T T1 首命令时延
还有大量影响发出与执行指令的时间常量,但这5个在实践中足够决定模块的性能表现。
有时了解使用中的电脑的这些信息可以来解释某些测量结果。在买电脑时了解这些细节是绝对有用的,因为这些参数,与FSB和SDRAM模块的速度一起,是决定电脑速度的最重要的因素。
很有冒险精神的读者也可以尝试调整系统。有时BIOS上可以调整部分或所有的这些参数。SDRAM模块有可编程的寄存器来设置这些参数。通常BIOS会默认采用最佳参数。如果RAM模块的质量好,可能可以减少1个或其它的时延,且不影响电脑的稳定性。
网上有大量的超频网站提供了充足的文档来做到这个。不过这样的风险就要你自己承担了,不要说没有被警告哦。

2.2.3重充电

关于DRAM访问的一个最容易被忽略的话题就是重充电。2.2.1中已解释过,DRAM必须持续地被刷新。这对于系统其它的部分来说不是完全透明的。有时当一行{行用来表示这一情况发生的粒度,我们不管它别的称谓[highperfdram] [micronddr]}被重充电时它是不可访问的。在[highperfdram]的研究中发现“令人惊讶的是,DRAM的刷新会极大地影响性能”。
根据JEDEC规范,DRAM元件必须每64ms刷新一次。如果一个DRAM阵列有8192行,内存控制器就得平均每7.8125us发一次刷新命令(由于刷新命令可能要排队,所以实际上2个请求之间的最大时间间隔可能更高)。内存控制器要为刷新命令做规划。DRAM模块会记录上一次刷新的行地址,并在每个新请求来到时自动增加地址计数器。
关于刷新和何时下达这些指令,程序员能做的不多。但在理解测量结果时,知道DRAM生命周期中的这部分知识是重要的。如果一个关键的字必须从一个正被刷新的行中取出来,处理器可能要等相当长的时间。每一次刷新要多久取决于DRAM模块。

2.2.4内存类型

了解当前可用,和不久后可用的内存类型是值得花时间的。我们从SDR (Single Data Rate) SDRAMs开始,因为它是DDR (Double Data Rate) SDRAMs的基础。SDR很简单。内存元件的传输率就是数据传输率。
图2.10 SDR SDRAM 操作
在图2.10中,DRAM元件阵列输出内存数据的速率,和它在内存总线上的传输率是一样的。如果元件是100MHz,那么数据传输率是100Mb/s。所有组件的频率f是一样的。因为随着频率提高,耗能也提高,所以提高DRAM芯片的吞吐量代价高昂。在存在大量元件时,会惊人的贵。{功率=动态容量 x 电压^2 x 频率}实际上,提高频率通常也需要提高电压来维持系统的稳定,就更是一个问题。DDR SDRAM(被称为DDR1)做到了在不提高频率的情况下,提升了吞吐量。
图2.11 DDR1 SDRAM操作
可以从图2.11中看到,或从名字中猜到,DDR1和SDR的区别是,DDR1在每个时钟周期中传输的数据量翻了一番。即,DDR1在时钟的上升沿和下降沿都传输了数据。这有时被称为“双泵”总线。为了不提高元件阵列的频率,得引入一个缓冲区。缓冲区为每一条数据线保存2个bit。这反过来要求,图2.7中的元件阵列的数据总线包含2条线。要现实这些很简单:只需对2个DRAM元件使用相同的列地址,并并发访问它们。元件阵列上的改动也很小。
SDR DRAM仅是通过它们的频率来被知道(如100MHz的 SDR是PC100)。因为频率没有改变,为了让DDR1 DRAM更好听,市场人员得想出一个新方案。他们想出一个新名字,这名字包含了DDR模块可以维持的传输率。
100MHz x 64bit x 2=1600M/s
因此一个100MHz的DDR模块被称为PC1600。因为1600>100,市场营销的需求圆满完成。虽然真正的提升只是乘2,但听起来好太多。 {我接受乘2,但不喜欢那个浮夸的数字}
图2.12 DDR2 SDRAM操作
为了充分利用内存技术,DDR2加入了一点创新。从图2.12中可以看到,最明显的改变是,总线的频率加倍了。频率加倍则带宽加倍。因为这一加倍对于元件阵列来说在成本上做不到,所以需要I/O缓冲区在每个时钟周期接收4bit用来发到总线上。这意味着DDR2模块上的改动仅在于使DIMM I/O缓冲区组件以更高的速度运行。这明确可行且不需要可测量到的更多的耗能,它是一个小组件,不是整个模块。市场人员为DDR2想出来的名字和DDR1类似,仅仅是在计算时把乘2换成乘4(我们现在有一个四泵总线了)。图2.13展示了当今在用的模块名。
图2.13 DDR2模块的名称
在命名上还有一个改动。为CPU,主板和DRAM模块所用的FSB的速度是通过有效频率所确定的。即它受时钟周期的2个沿都发生的传输影响而数值膨胀。因此,一个有266MHz总线的133MHz的模块,FSB频率是533MHz。
DDR3(真实DDR3,而不是在图片卡中用到的假GDDR3)的规范需要在沿着DDR2所做的改变路径上变动更多。电压将从DDR2的1.8V到1.5V。耗能公式中用到电压的平方,所以电压这一项带来了功耗30%的节省。结合集成片大小的减小和其它的电子器件的改良DDR3做到了同频率下,功耗减半。换句话来说,可在相同的功耗(相比DDR2)下达到更高的频率,或者说在双倍的容量下,实现相同的热排放。
DDR3模块的元件阵列将以1/4的外部总线的速度运行,这需要一个8bit的I/O缓冲区,从DDR2的4bit上提升了。看图2.14所示的原理:
图2.14 DDR3 SDRAM 操作
最初的DDR3较DDR2可能有一个稍高的/CAS时延,因为DDR2的技术更成熟。这使得DDR3只有在DDR2达不到的高频率的场景下才有用,还得是带宽重要性大于延迟的情况下。已经有关于既是1.3V且能做到与DDR2相同/CAS时延的模块的讨论了。无论如保,做到更快的总线而达到更高速率,将超过延时的增加。
DDR3可能存在一个问题:在1600M/s或更高传输率的情况下,每个通道的模块数将减少到1个。在更早期的版本中,所有频率都有这个要求,所以我们可以期待未来某天对所有频率的这一要求会提升。不然系统的容量会被严重限制。
图2.15展示了预期中的DDR3模板名。JEDEC目前为止批准了前4个。在Intel 45nm处理器的FSB速度达1600M/s的情况下,超频市场需要1866M/s的速度。在DDR3生命周期的末期我们可能看到更多这样的。

图2.15 DDR3模块的名称
所有的DDR内存都存在一个问题:增长的总线频率使得制造并发总线很困难。一个DDR2模块有240个引脚。所有连接到数据和地址引脚的连接都必须能被路由,所以它们必几乎要一样长。更大的问题是,如果有多个DDR模块有以菊花链式相连在同一条总线上,每加一个DDR模块,信号就会愈加变形。DDR2规范仅允许每条总线上有2个模块(也称为通道),DDR3模范为了高频率仅允许1个。对于240脚的通道来说,在一个北桥上不能连接超过2个。另一种方式是使用外部内存控制器(如图2.2),但这很贵。
这意味着一块商业主板上最多只能有4个DDR2或DDR3模块。这极大地限制了系统的内存量。即使是老的32位IA-32处理器需要64G的RAM,且家用的内存量需求也在上升,这种情况下必须做点什么了。
一种方式是,如2.2中说到的,为每个处理器加入内存控制器。ADM在皓龙产品线上是这样做的,Intel也将连同它们的CSI技术一起使用。只要合理量的内存可以连上使用到它的处理器,这一方式就有用。在某些情况下不可用,这时将引进NUMA架构,也会因此相应受它不好的影响。在一些情况下需要用到另一种解决方式。
Intel对大服务器,至少是在接下来几年里是,提出了Fully Buffered DRAM (FB-DRAM)的解决方式。FB-DRAM模块使用了与当今DDR2相同的组件,生产它们相对便宜。区别在与内存控制器的连接。FB-DRAM使用了一个串行总线(Rambus DRAM也曾这样做,同样做法的还有作为PATA后继者的SATA,作为PCI/AGP后继者的PCI Express),取代了并行总线。串行总线可以以更高的频率驱动,这抵消了串行的负面影响甚至还增加了带宽。使用串行总线的作用是:
(1) 每个通道能有更多的模块
(2) 每个北桥/内存控制器能有更多的通道
(3) 串行总线是全双工的(2条线)
一个FB-DRAM模块仅有69个引脚,而DDR2有240个。菊花链式相连的FB-DRAM模块更简单了,因为总线的电效应能被更好地应对。FB-DRAM规范允许每个通道上有8个DRAM模块。
对比双通道北桥的连接要求,现在可以用更少的引脚来驱动6个通道的FB-DRAM了:2 x 240 相比6 x 69。对每个通道的路由变得更简单,这也减少了主板的成本。
对于传统的DRAM模块来说,全双工并行总线太贵了,因为复制所有的线路非常花钱。使用串行总线(即使它们是差分的,如FB-DRAM要求的那样),就没有这个情况,所以串行总线被设计为全双工的,理论上在某些情况下带宽加倍了。这不是并发被用来提升带宽的唯一之处。因为FB-DRAM控制器可以同时运行6个通道,对于更小RAM的系统来说使用FB-DRAM也能增加带宽。一个有4个模块的DDR2系统有2个通道,同样的容量可通过一个普通的FB-DRAM控制器通过4个通道实现。串行总线的实际带宽取决于FB-DRAM模块上使用的DDR2(或DDR3)芯片类型。
我们总结优点如下:

在这里插入图片描述
在通道上使用多个DIMM有一些缺点。信号会延迟,在每个DIMM上甚至非常小,但总的延迟增加了。在同样大小,同样频率的情况下,FB-DRAM总是比DDR2和DDR3更快,这是因为它每个通道仅需要一个DIMM。对于需要大内存容量的系统,商业组件的DDR是无解的。

2.2.5总结

这一节旨在说明访问DRAM不是一个任意快的过程。至少不如处理器访问寄存器和缓存快。需要知道CPU和内存频率上的差异。Intel Core2处理器是2.933GHz,它的FSB是1.066GHz,时钟比是11:1(注意:1.066是乘了4倍的)。在内存总线上滞留1个时钟周期,等于处理器滞留11个时钟周期。对于大多数的机器来说DRAM实际上更慢,更增加了时延。记住这些数值,在未来的章节中会讨论滞留。
读指令的时序图展示了DRAM模块有一个很高的持续输出速率。DRAM所有的行可以不需滞留地全部输出。这样数据总线就100%使用。对于DDR模板这等于每个时钟周期传输64bit。对于有2个通道的DDR2-800来说,是12.8G/s。
但即使设计如此,访问DRAM不总是连续的。使用不连续的内存区意味着预充电和发新的/RAS信号。这种情况下就会变慢,DRAM模块需要一些帮助。越快能预充电,越快能发/RAS信号,则使用行时支付的代价越小。
硬件和软件的预获取(见第六节6.3)可以创造更多的时间交叠,因而减小滞留。预获取有助于及时的内存交换,在后面获取所需数据时就会更少发生竞争。 这是在一轮中产生的数据得被存储而下一轮需要的数据得被读取的情况下,会有的一个高发的问题。及时地交换读取,这样写和读就不需要同时发出了。

2.3其它的主存的使用者

除CPU之外,其它的组件也会访问主存。高性能卡如网卡,大容量存储控制器不能直接通过CPU获取或提供数据。相反,它们从主存直接读写数据(DMA)。在图2.1中,这些卡可通过南桥和北桥直接与内存通信。其它的总线,如USB,也需要FSB带宽,虽然它们不用DMA,因为南桥也是通过FSB与北桥相连的。
DMA固然很好,但也增加了FSB带宽竞争。在高DMA的情况下,CPU会在等待主存数据时滞留得比平时更久。通过提供相应的硬件可以解决这一问题。用图2.3中的架构,可以确保用节点中的内存,这不受DMA影响。每个节点都连接南桥也可以,这在所有的节点上均摊了FSB的负载。还有太多的解决方案。在第6节中我们将会介绍一些技术和编程接口,可从软件上实现提升。
最后要提下,一些便宜系统上图形系统没有独立专门的视频RAM。它们用部分的主存作为视频RAM。因为对视频RAM的访问很频繁(对于1024*768,16bpp,60Hz的显示来说,数据传输率是94M/s),而系统内存不像显卡上的内存有2个端口,所以会持续地影响到系统的性能,尤其是延时。如果性能优先的话,最好不要考虑这样的系统。它们是麻烦。买它们的人们没想得到最佳性能。

后续

第二部分(CPU缓存
第三部分(虚拟内存)
第四部分(NUMA系统)
第五部分(程序员可以做什么-缓存调优)
第六部分(程序员可以做什么-多线程调优)
第七部分(内存表现工具)
第八部分(未来的技术)
第九部分(附录和参考书目)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值