每个程序员都应该了解的内存知识-Part1

转载自 lwn 网站的几篇关于内存的文章,使用 kimi 进行辅助翻译。

每个程序员都应该了解的关于内存的知识,第一部分

1 引言

在计算机的早期,它们要简单得多。系统的各个组件,如CPU、内存、大容量存储和网络接口是一起开发的,因此它们的性能相当平衡。例如,内存和网络接口在提供数据方面并不比CPU快多少。

一旦计算机的基本结构稳定下来,硬件开发人员集中优化各个子系统,这种情况就发生了变化。突然,计算机的一些组件的性能明显落后,出现了瓶颈。这尤其适用于大容量存储和内存子系统,由于成本原因,它们的改进速度相对于其他组件要慢得多。

大容量存储的缓慢性大多通过软件技术来解决:操作系统将最常使用(和最有可能被使用)的数据保留在主存储器中,这可以比硬盘快几个数量级的速度访问。缓存存储被添加到存储设备本身,这不需要操作系统的任何更改即可提高性能。然而,为了在使用存储设备缓存时保证数据完整性,需要进行更改。对于本文的目的,我们不会详细介绍大容量存储访问的软件优化。

与存储子系统不同,消除主存储器作为瓶颈要困难得多,几乎所有的解决方案都需要对硬件进行更改。今天,这些更改主要采取以下形式:

  • 随机存取存储器(RAM)硬件设计(速度和并行性)。
  • 内存控制器设计。
  • CPU缓存。
  • 设备的直接内存访问(DMA)。

本文将主要讨论CPU缓存和内存控制器设计的一些影响。在探索这些主题的过程中,我们将探讨DMA并将其纳入更大的图景。然而,我们将从对当今商品硬件设计的概述开始。这是理解有效使用内存子系统的问题和局限性的先决条件。我们还将详细了解不同类型的RAM,并说明为什么这些差异仍然存在。

本文并不全面且最终。它限于商品硬件,并且进一步限于该硬件的一个子集。此外,许多主题只讨论了足够的细节,以实现本文的目标。对于这些主题,建议读者查找更详细的文档。

当涉及到特定于操作系统的详细信息和解决方案时,文本专门描述了Linux。它不会包含任何关于其他操作系统的信息。作者对讨论其他操作系统的影响没有兴趣。如果读者认为他们需要使用不同的操作系统,他们必须去他们的供应商那里,并要求他们编写类似于本文的文档。

在开始之前,最后一点评论。文本中包含了许多“通常”和类似限定词的用法。这里讨论的技术在现实世界中存在许多变体,本文只涉及最常见、最主流的版本。关于这项技术,很少能做出绝对的陈述,因此使用了限定词。

1.1 文档结构

本文主要针对软件开发人员。它没有深入到对硬件技术细节的讨论,对面向硬件的读者来说可能不够有用。但在我们可以进入对开发人员实用的信息之前,必须奠定很多基础。

为此,第二节详细描述了随机存取存储器(RAM)。本节的内容是很好的知识,但并不是绝对关键,以便能够理解后续各节。在需要内容的地方添加了适当的后文引用,以便心急的读者可以首先跳过本节的大部分。

第三节详细讨论了CPU缓存的行为。为了保持文本不像其他情况下那样枯燥,使用了图表。这些内容对于理解本文的其余部分至关重要。第4节简要介绍了虚拟内存的实现。这也是其余部分所需的基础工作。

第5节详细讨论了非统一内存访问(NUMA)系统。

第6节是本文的核心部分。它汇总了前面各节的所有信息,并为程序员提供了如何在各种情况下表现良好的代码编写建议。非常不耐烦的读者可以从本节开始,如有必要,可以回到早期的部分以刷新对底层技术的知识。

第7节介绍了可以帮助程序员做得更好的工具。即使对技术有完全的理解,也远非显而易见在非平凡软件项目中问题所在。某些工具是必要的。

在第8节中,我们最终给出了可以预期在不久的将来出现的技术展望,或者可能只是很好的拥有的技术。

1.2 报告问题

作者打算在一段时间内更新本文。这包括由于技术进步而必须进行的更新,也包括纠正错误。愿意报告问题的读者被鼓励发送电子邮件。

1.3 致谢

我要感谢Johnray Fuller,特别是Jonathan Corbet,他们承担了将作者的英语形式转换为更传统形式的部分艰巨任务。Markus Armbruster在文本中的问题和遗漏提供了许多宝贵的意见。

1.4 关于本文

本文的标题是对David Goldberg的经典论文“每个计算机科学家都应该知道的关于浮点算术的知识”的致敬。Goldberg的论文仍然不为人所知,尽管它应该是任何敢于为严肃编程而触碰键盘的人的先决条件。

2 今日商品硬件

理解商品硬件很重要,因为专用硬件正在退却。如今,扩展通常是通过水平而不是垂直实现的,这意味着使用许多较小的、连接在一起的商品计算机比使用一些真正快速(和昂贵)的大型系统更具成本效益。这是因为快速且价格低廉的网络硬件广泛可用。仍然存在大型专用系统的情况,这些系统仍然提供了一个商机,但总体市场被商品硬件市场所掩盖。截至2007年,Red Hat预计,对于未来的产品,大多数数据中心的“标准构建块”将是一台计算机,最多有四个插座,每个插座装有一个四核CPU,就Intel CPU而言,将是超线程的。超线程使单个处理器核心能够仅通过一点额外的硬件就被用于两个或更多并发执行。这意味着数据中心的标准系统将有多达64个虚拟处理器。更大的机器将得到支持,但四插座、四CPU核心的情况目前被认为是最佳点,大多数优化都针对这样的机器。

商品计算机的结构存在很大差异。话虽如此,我们将通过关注最重要的差异来涵盖超过90%的此类硬件。请注意,这些技术细节变化迅速,因此建议读者考虑本文的日期。

多年来,个人计算机和小型服务器标准化了一个由两个部分组成的芯片组:北桥和南桥。图2.1显示了这种结构。

所有CPU(以前的例子中有两,但可以更多)都通过一个公共总线(前端总线,FSB)连接到北桥。北桥包含内存控制器,其实现决定了计算机使用的RAM芯片类型。不同类型的RAM,如DRAM、Rambus和SDRAM,需要不同的内存控制器。

为了到达所有其他系统设备,北桥必须与南桥通信。南桥通常被称为I/O桥,通过各种不同的总线处理与设备的通信。今天,PCI、PCI Express、SATA和USB总线最为重要,但南桥也支持PATA、IEEE 1394、串行和并行端口。旧系统有AGP插槽,它们连接到北桥。这是出于与北桥和南桥之间连接速度不足相关的性能原因。然而,今天所有的PCI-E插槽都连接到南桥。

这种系统结构有一些值得注意的后果:

  • 所有从一个CPU到另一个CPU的数据通信都必须通过与北桥通信的同一总线进行。
  • 与RAM的所有通信都必须通过北桥。
  • RAM只有一个端口。
  • 我们不会在本文中讨论多端口RAM,因为这种类型的RAM不存在于商品硬件中,至少在程序员可以访问的地方不存在。它可以在依赖于最高速度的专业硬件中找到,例如网络路由器。

立即显现出这种设计中的几个瓶颈。一个瓶颈涉及设备的RAM访问。在PC的最初几天,与两个桥上的所有设备的通信都必须通过CPU,对整体系统性能产生了负面影响。为了解决这个问题,一些设备能够进行直接内存访问(DMA)。DMA允许设备在北桥的帮助下直接在RAM中存储和接收数据,而无需CPU(及其固有的性能成本)的干预。今天,所有高性能设备都可以使用任何总线上的DMA。虽然这大大减少了CPU的工作量,但它也造成了北桥带宽的竞争,因为DMA请求与CPU对RAM的访问竞争。因此,必须考虑这个问题。

第二个瓶颈涉及从北桥到RAM的总线。总线的确切细节取决于部署的内存类型。在旧系统中,所有RAM芯片只有一个总线,因此无法进行并行访问。最近的RAM类型需要两个单独的总线(或称为通道,如DDR2中所示),这使得可用带宽翻倍。北桥在通道之间交错内存访问。更近期的内存技术(例如FB-DRAM)增加了更多通道。

由于可用带宽有限,重要的是以最小化延迟的方式安排内存访问。正如我们将看到的,处理器要快得多,并且必须等待才能访问内存,尽管使用了CPU缓存。如果多个超线程、核心或处理器同时访问内存,内存访问的等待时间甚至更长。DMA操作也是如此。

然而,访问内存不仅仅是并发性的问题。访问模式本身也极大地影响内存子系统的性能,尤其是具有多个内存通道时。有关RAM访问模式的更多详细信息,请参阅第2.2节。

在一些更昂贵的系统中,北桥实际上并不包含内存控制器。相反,北桥可以连接到多个外部内存控制器(以下示例中为四个)。图2.2显示了这样一个系统。

这种架构的优点是存在多于一个的内存总线,因此总带宽增加。这种设计还支持更多内存。并发内存访问模式通过同时访问不同的内存库减少延迟。这在多个处理器直接连接到北桥时尤其如此,如图2.2所示。对于这种设计,主要的限制是北桥的内部带宽,对于这种架构(来自Intel)来说是惊人的。

使用多个外部内存控制器并不是增加内存带宽的唯一方法。另一种越来越流行的方式是将内存控制器集成到CPU中,并将内存连接到每个CPU。这种架构由基于AMD Opteron处理器的SMP系统推广。图2.3显示了这样一个系统。Intel将从Nehalem处理器开始支持通用系统接口(CSI),这基本上是相同的方法:具有集成内存控制器的本地内存的可能性。

在这种架构中,可用的内存库数量与处理器数量一样多。在四CPU机器上,内存带宽在不需要复杂的北桥和巨大带宽的情况下增加了四倍。将内存控制器集成到CPU中还有一些额外的优点;我们在这里不会深入探讨这项技术。

然而,这种架构也有缺点。首先,因为机器仍然必须使系统的整个内存对所有处理器都可访问,内存不再是统一的(因此称这种架构为NUMA - 非统一内存架构)。本地内存(连接到处理器的内存)可以以通常的速度访问。当访问连接到另一个处理器的内存时,情况就不同了。在这种情况下,必须使用处理器之间的互连。从CPU1访问连接到CPU2的内存需要通过一个互连进行通信。当同一个CPU访问连接到CPU4的内存时,必须穿过两个互连。

每次这样的通信都有相关的成本。当我们描述访问远程内存所需的额外时间时,我们谈论的是“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芯片之间的访问协议。程序员可能会发现这些信息很有启发性,因为这些细节解释了为什么RAM访问的工作方式。这是可选的知识,但是急于了解对日常生活有更直接相关性的主题的读者可以跳到第2.2.5节。

2.1 RAM类型

多年来出现了许多类型的RAM,每种类型都有所不同,有时差异显著。旧的类型今天真的只是对历史学家感兴趣。我们将不会探索那些细节。相反,我们将集中讨论现代RAM类型;我们只会触及一些细节,这些细节通过它们的性能特征对内核或应用程序开发人员是可见的。

首先有趣的细节围绕着为什么同一台机器中存在不同类型的RAM的问题。更具体地说,为什么会同时有静态RAM(SRAM { 在其他上下文中 SRAM可能意味着“同步RAM”。_})和动态RAM(DRAM)。前者要快得多,并提供相同的功能。为什么不是机器中的所有RAM都是SRAM?答案正如人们所预期的那样,是成本。SRAM的生产和使用成本比DRAM要高得多。这两个成本因素都很重要,第二个因素的重要性越来越增加。为了理解这些差异,我们看看SRAM和DRAM的比特存储实现。

在本节的其余部分中,我们将讨论RAM实现的一些低级细节。我们将尽可能保持细节水平低。为此,我们将在“逻辑级别”上讨论信号,而不是硬件设计师必须使用的级别。这里的细节水平对我们的目的来说是不必要的。

2.1.1 静态RAM

Figure 2.4: 6-T Static RAM

图2.4显示了一个6晶体管SRAM单元的结构。这个单元的核心是由M1到M4的四个晶体管形成的两个交叉耦合的反相器。它们有两个稳定状态,分别代表0和1。只要Vdd上的电源可用,状态就是稳定的。

如果需要访问单元的状态,可以提升字访问线WL。这使得单元的状态立即在BL和BL上可用进行读取。如果必须覆盖单元的状态,则首先将BL和BL线设置为所需的值,然后提升WL。由于外部驱动器比M1到M4的四个晶体管更强大,这允许旧状态被覆盖。

有关该单元工作方式的更详细描述,请参见[SRAM wiki]。对于接下来的讨论,重要的是要注意:

  • 一个单元需要六个晶体管。有变体使用四个晶体管,但它们有缺点。
  • 维持单元状态需要恒定的电源。
  • 一旦提升字访问线WL,单元状态几乎可以立即用于读取。信号像其他晶体管控制的信号一样是矩形的(在两个二进制状态之间快速变化)。
  • 单元状态是稳定的,不需要刷新周期。

还有其他更慢、功耗更低的SRAM形式可用,但这些在这里不感兴趣,因为我们正在寻找快速的RAM。这些慢变种主要是因为它们的接口更简单,所以更容易在系统中使用,而不是动态RAM。

2.1.2 动态RAM

动态RAM在其结构上比静态RAM要简单得多。图2.5显示了一个典型的DRAM单元设计。它只由一个晶体管和一个电容器组成。这种巨大的复杂性差异当然意味着它的功能与静态RAM非常不同。

动态RAM单元将状态保持在电容器C中。晶体管M用于保护对状态的访问。要读取单元的状态,可以提升访问线AL;这将根据电容器中的电荷在数据线DL上引起或不引起电流流动。要向单元写入数据,适当地设置数据线DL,然后提升AL足够长的时间以充电或排放电容器。

动态RAM的设计有许多复杂性。使用电容器意味着读取单元会放电电容器。这个过程不能无限重复,电容器必须在某个时候重新充电。更糟糕的是,为了容纳大量的单元(现在常见的芯片有10^9或更多单元),电容器的容量必须很低(在飞法拉德范围或更低)。一个完全充电的电容器持有几万个电子。尽管电容器的电阻很高(几万亿欧姆),但它只需要很短的时间就能放电。这个问题称为“漏电”。

这种漏电是为什么DRAM单元必须不断刷新的原因。对于大多数DRAM芯片,现在必须每64ms刷新一次。在刷新周期期间,无法访问内存。对于一些工作负载,这个开销可能会使多达50%的内存访问停止(见[highperfdram])。

第二个问题是由于电荷微小,从单元读取的信息不能直接使用。数据线必须连接到感测放大器,该放大器可以在仍然必须算作1的整个电荷范围内区分存储的0或1。

第三个问题是充电和放电电容器不是瞬时的。感测放大器接收到的信号不是矩形的,因此必须保守估计电容器放电到足以使用的输出所需的时间。充电和放电电容器的公式为:

这意味着需要一些时间(由电容C和电阻R决定)来充电和放电电容器。这也意味着感测放大器可以检测到的电流不是立即可用的。图2.6显示了充电和放电曲线。X轴以RC(电阻乘以电容)为单位测量,这是时间的单位。

与静态RAM的情况不同,当提升字访问线时,输出立即可用,而DRAM的情况是,电容器总是需要一点时间才能足够放电。这个延迟严重限制了DRAM的速度。

简单的方法也有其优点。主要优点是尺寸。一个DRAM单元所需的芯片面积比一个SRAM单元小得多。SRAM单元还需要为维持状态的晶体管提供单独的电源。DRAM单元的结构也更简单、更规则,这意味着将许多单元紧密地组合在芯片上更简单。

总体而言,成本差异(相当戏剧性)赢了。除了专业硬件(例如网络路由器)之外,我们不得不使用基于DRAM的主存储器。这对程序员有巨大的影响,我们将在本文的其余部分讨论。但首先,我们需要查看一些关于实际使用DRAM单元的更多细节。

2.1.3 DRAM访问

程序使用虚拟地址选择一个内存位置。处理器将此转换为物理地址,最后内存控制器选择对应的RAM芯片。要在选择的RAM芯片上选择单个内存单元,物理地址的部分作为地址线的一组传递。

从内存控制器单独寻址内存位置是完全不切实际的:4GB的RAM将需要2^32条地址线。

相反,地址被编码为二进制数字传递,使用一组较小的地址线。以这种方式传递给DRAM芯片的地址必须首先被解复用。具有N条地址线的解复用器将有2^N条输出线。这些输出线可以用来选择存储单元。使用这种直接方法是没有问题的,对于容量较小的芯片来说不是大问题。

但是,如果单元数量增加,这种方法就不再适用了。一个具有1Gbit容量的芯片将需要30条地址线和2^30条选择线。解复用器的大小随着输入线路数量的增加而呈指数增长,当速度不是要牺牲的时候。对于30条地址线,需要大量的芯片面积,除了解复用器的复杂性(大小和时间)。更重要的是,同时传输30个脉冲到地址线上比“仅”传输15个脉冲要困难得多。更少的线路需要以完全相同的长度布置或适当地定时。现代DRAM类型,如DDR3,可以自动调整时间,但对于可以容忍的东西有一个限制。

图2.7以非常高的水平显示了DRAM芯片。DRAM单元以行和列的形式组织。它们可以全部对齐在一行中,但那样DRAM芯片将需要一个巨大的解复用器。通过阵列方法,设计可以在只有一个解复用器和一个一半大小的多路复用器的情况下得到满足。这是一个巨大的节省,在所有方面。在示例中,地址线a0和a1通过行地址选择(RAS)解复用器选择整行单元的地址线。在读取引时,所有单元的内容因此可以提供给列地址选择(CAS)多路复用器。基于地址线a2和a3,一列的内容然后可以提供给DRAM芯片的数据引脚。这在许多DRAM芯片上多次并行发生,以产生对应于数据总线宽度的总位数。

对于写入,新单元值放在数据总线上,当使用RAS和CAS选择单元时,它被存储在单元中。

一个相当直接的设计。在现实中——显然——有更多的复杂性。需要有规定,信号在数据在数据总线上可用之前有多少延迟。电容器不会立即放电,如前一节所述。来自单元的信号太弱,需要放大。对于写入,必须指定在RAS和CAS完成后,数据必须在总线上可用多长时间才能成功地在单元中存储新值(再次,电容器不会立即充满或排空)。这些时序常数对DRAM芯片的性能至关重要。我们将在下一节中讨论这个问题。

一个次要的可扩展性问题是,将30条地址线连接到每个RAM芯片也是不可行的。芯片的引脚是宝贵的资源。数据必须尽可能多地并行传输(例如,以64位批次)。内存控制器必须能够寻址每个RAM模块(RAM芯片的集合)。如果为了性能原因需要并行访问多个RAM模块,并且每个RAM模块都需要自己的30条或更多地址线,那么内存控制器需要为8个RAM模块有240多个引脚仅用于地址处理。

为了应对DRAM芯片的这些次要可扩展性问题,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,顾名思义,相对于一个时间源工作。内存控制器提供一个时钟,其频率决定了DRAM芯片使用的内存控制器接口(前端总线,FSB)的速度。截至本文撰写时,可用的频率为800MHz、1,066MHz,甚至1,333MHz,下一代将宣布更高的频率(1,600MHz)。这并不意味着总线上实际使用的频率实际上是这么高。相反,今天的总线是双倍或四倍泵送的,这意味着每个周期传输两次或四次数据。更高的数字更好卖,所以制造商喜欢将一个四倍泵送的200MHz总线宣传为“有效”的800MHz总线。

对于SDRAM,今天的每个数据传输由64位——8字节组成。因此,FSB的传输速率因此是8字节乘以有效总线频率(对于四倍泵送的200MHz总线为6.4GB/s)。听起来像是很多,但它是突发速度,即永远不会被超越的最大速度。正如我们现在将看到的,与RAM模块通信的协议有很多停机时间,在此期间无法传输数据。我们必须理解并最小化这种停机时间,以实现最佳性能。

2.2.1 读取访问协议

图2.8显示了DRAM模块的一些连接器的活动,这些活动发生在三个不同颜色的阶段中。像往常一样,时间从左到右流动。这里省略了很多细节。我们在这里只讨论总线时钟、RAS和CAS信号,以及地址和数据总线。一个读取周期始于内存控制器在地址总线上提供行地址并降低RAS信号。所有信号都在时钟(CLK)的上升沿读取,因此只要信号在读取时稳定,它不完全方形就没关系。设置行地址会导致RAM芯片开始锁定地址所在的行。

CAS信号可以在tRCD(RAS到CAS延迟)时钟周期后发送。然后通过在地址总线上提供它并降低CAS线来传输列地址。在这里,我们可以看到地址的两个部分(或多或少是两半,没有其他意义)可以通过同一个地址总线传输。

现在寻址已经完成,可以传输数据了。RAM芯片需要一些时间来为此做准备。通常称这种延迟为CAS延迟(CL)。在图2.8中,CAS延迟为2。它可能更高或更低,这取决于内存控制器、主板和DRAM模块的质量。延迟也可以有半值。使用CL=2.5,第一个数据将在蓝色区域的第一个下降沿处可用。

有了所有这些准备工作来获取数据,如果只传输一个数据字就太浪费了。这就是为什么DRAM模块允许内存控制器指定要传输多少数据。通常选择是2、4或8个字。这允许在不使用新的RAS/CAS序列的情况下填满缓存中的整个行。内存控制器还可以在不重置行选择的情况下发送新的CAS信号。通过这种方式,可以显著更快地读取或写入连续的内存地址,因为不需要发送RAS信号,行也不需要被停用(见下文)。保持行“打开”是内存控制器必须决定的事情。一直推测性地保持它打开所有时间在现实世界的应用中有缺点(见[highperfdram])。发送新的CAS信号只受RAM模块的命令速率限制(通常指定为T_x,其中x是一个像1或2这样的值;对于每个周期都接受新命令的高性能DRAM模块,它将是1)。

在这个例子中,SDRAM每个周期输出一个字。这是第一代所做的。DDR每周期能够传输两个字。这减少了传输时间,但并没有改变延迟。原则上,DDR2的工作方式相同,尽管在实践中看起来不同。这里没有必要深入细节。重要的是注意到DDR2可以更快、更便宜、更可靠,并且更节能(有关更多信息,请参见[ddrtwo])。

2.2.2 预充电和激活

图2.8没有涵盖整个周期。它只显示了访问DRAM的部分完整周期。在可以发送新的RAS信号之前,当前锁定的行必须停用,并且必须预充电新的行。我们可以集中讨论在这种情况下使用显式命令完成的情况。对协议有一些改进,在某些情况下,允许避免这个额外步骤。然而,预充电引入的延迟仍然影响操作。

图2.9显示了从一个CAS信号到另一行的CAS信号的活动。使用第一个CAS信号请求的数据像以前一样,在CL周期后可用。在示例中,请求了两个单词,在一个简单的SDRAM上,需要两个周期来传输。或者,想象一下在DDR芯片上的四个单词。

即使在命令速率为一的DRAM模块上,也不能立即发出预充电命令。必须等待足够长的时间以传输数据。在这种情况下,需要两个周期。这恰好是CL,但这只是巧合。预充电信号没有专用线;相反,一些实现通过同时降低写使能(WE)和RAS线来发出它。这个组合本身没有有用的含义(有关编码细节,请参阅[micronddr])。

一旦发出预充电命令,就需要tRP(行预充电时间)周期,直到可以选中行。在图2.9中

如果我们继续图表中的时间线,我们会找到下一个数据传输发生在前一个停止后5个周期。这意味着数据总线在七个周期中只使用了两个周期。将这个数字乘以FSB速度,理论上800MHz总线的6.4GB/s就变成了1.8GB/s。这很糟糕,必须避免。第6节中描述的技术有助于提高这个数字。但程序员通常必须尽自己的一份力。

SDRAM模块还有一个我们没有讨论的时序值。在图2.9中,预充电命令仅受数据传输时间限制。另一个限制是SDRAM模块需要时间,在RAS信号之后,才能预充电另一行(表示为tRAS)。这个数字通常相当高,大约是tRP值的两到三倍。如果,在RAS信号之后,只跟了一个CAS信号,并且数据传输在几个周期内完成,那么这就是一个问题。假设在图2.9中,初始CAS信号直接由RAS信号先导,并且tRAS是8个周期。那么预充电命令将不得不延迟一个额外的周期,因为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 | 命令速率

还有许多其他时序常数影响命令的发出和处理方式。但这五个常数在实践中足以确定模块的性能。

有时了解这些信息对于使用的计算机是有用的,以能够解释某些测量。在购买计算机时,了解这些细节肯定有用,因为它们以及FSB和SDRAM模块速度是决定计算机速度的最重要因素之一。

非常大胆的读者也可以尝试调整系统。有时BIOS允许更改其中一些或所有这些值。SDRAM模块有可编程寄存器,可以设置这些值。通常BIOS选择最佳默认值。如果RAM模块的质量很高,可能可以减少一个或另一个延迟,而不影响计算机的稳定性。互联网上有许多超频网站提供了大量文档供这样做。但请自行承担风险,并且不要说你没有被警告过。

2.2.3 充电

在访问DRAM时,一个经常被忽视的话题是充电。正如第2.1.2节中所解释的,DRAM单元必须不断刷新。这并不是对系统的其余部分完全透明的。在行被充电的时候,无法进行访问。在[highperfdram]中的一项研究发现,“DRAM刷新组织可能会戏剧性地影响性能”。

每个DRAM单元必须根据JEDEC规范每64ms刷新一次。如果一个DRAM阵列有8,192行,这意味着内存控制器必须平均每7.8125µs发出一个刷新命令(刷新命令可以排队,因此实际上两个请求之间的最大间隔可以更高)。调度刷新命令是内存控制器的责任。DRAM模块跟踪最后刷新行的地址,并自动为每个新请求增加地址计数器。

程序员实际上对刷新和发出命令的时间点无能为力。但重要的是要记住DRAM生命周期的这一部分,当解释测量时。如果必须从当前正在刷新的行中检索关键单词,处理器可能会被停用相当长的时间。每次刷新需要多长时间取决于DRAM模块。

2.2.4 内存类型

值得花时间了解当前和即将使用的内存类型。我们将从SDR(单数据速率)SDRAM开始,因为它们是DDR(双数据速率)SDRAM的基础。SDRs相当简单。内存单元和数据传输速率是相同的。

在图2.10中,DRAM单元阵列可以以与内存总线传输相同的速率输出内存内容。如果DRAM单元阵列能够以100MHz的频率运行,那么总线的数据传输速率就是100Mb/s。所有组件的频率 ( f ) 是相同的。增加DRAM芯片的吞吐量是昂贵的,因为能耗随着频率的增加而增加。对于拥有大量阵列单元的系统来说,这尤其昂贵。实际上,增加频率通常还需要增加电压以维持系统的稳定性,这进一步增加了复杂性。电源功耗的计算公式是 ( \text{Power} = \text{Dynamic Capacity} \times \text{Voltage}^2 \times \text{Frequency} )。

DDR SDRAM(通常被称为DDR1)实现了在不增加任何相关频率的情况下提高吞吐量的技术。这意味着DDR1能够在不提高内存单元阵列或总线的工作频率的前提下,实现双倍的数据传输速率。这是通过在每个时钟周期的上升沿和下降沿都传输数据来实现的,这种方法有时被称为“双沿泵送”或“双泵送”总线。通过这种机制,DDR1在不增加单元阵列的运行频率的情况下,能够在保持能耗相对较低的同时,提供更高的数据传输速率。

SDR DRAMs以其频率命名(例如,PC100代表100MHz的SDR)。为了使DDR1 DRAM听起来更先进,市场营销人员不得不想出一种新的命名方案,因为工作频率并没有变化。他们采用了一个包含DDR模块(它们拥有64位宽的总线)能够持续支持的传输速率的名称:

100MHz × 64bit × 2 = 1,600MB/s

因此,一个频率为100MHz的DDR模块被称为PC1600。1600>100,所有营销要求都得到了满足;听起来好多了,尽管改进实际上只是两倍的因素。我将接受这个两倍的因素,但我不必喜欢这些夸大的数字。

为了获得更多的内存技术,DDR2包括了更多的创新。在图2.12中可以看到的最明显的变化是,总线频率翻倍了。频率翻倍意味着带宽翻倍。由于这种频率翻倍对于单元阵列来说并不经济,因此现在要求I/O缓冲区每秒获得四个位,然后将其发送到总线上。这意味着对DDR2模块的更改仅涉及使DIMM的I/O缓冲区组件能够以更高的速度运行。这当然是可能的,不会需要显著更多的能量,它只是一个小组件,而不是整个模块。市场营销人员为DDR2想出的名字与DDR1类似,只是在计算值时,将两倍的因素替换为四倍(我们现在有一个四倍泵送的总线)。

图2.13显示了今天使用的模块的名称。

还有另一种命名方式。CPU、主板和DRAM模块使用的FSB速度是使用_有效_频率指定的。也就是说,它考虑了时钟周期的两个边沿的传输,从而提高了数字。因此,一个133MHz模块,有一个266MHz的总线,有一个533MHz的FSB“频率”。

DDR3(真正的一个,不是用于图形卡的假GDDR3)的规范要求更多的变化,类似于过渡到DDR2。电压将从DDR2的1.8V降低到DDR3的1.5V。由于功耗方程是使用电压的平方计算的,仅此一项就带来了30%的改进。加上减小的芯片尺寸以及其他电气进步,DDR3可以在相同的频率下,功耗减半。或者,使用更高的频率,可以击中相同的功率包络。或者,以双倍的容量实现相同的热量排放。DDR3模块的单元阵列将以外部总线速度的四分之一运行,需要一个8位I/O缓冲区,而DDR2是4位。见图2.14以获取图解。

最初,DDR3模块可能会有略高的CAS延迟,只是因为DDR2技术更成熟。这将导致DDR3只在频率高于DDR2可以实现的频率时才有用,并且即使在那时,主要在带宽比延迟更重要时。已经有关于1.3V模块的讨论,这些模块可以实现与DDR2相同的CAS延迟。在任何情况下,由于更快的总线可以实现更高的速度,这种可能性将超过增加的延迟。一个可能的问题是,对于1,600Mb/s的传输速率或更高,每个通道的模块数量可能减少到只有一个。在早期版本中,这个要求适用于所有频率,所以人们希望这个要求最终能对所有频率取消。否则,系统的容量将受到严重限制。

图2.15显示了预期的DDR3模块的名称。JEDEC到目前为止已经同意了前四种类型。鉴于Intel的45nm处理器具有1,600Mb/s的FSB速度,1,866Mb/s是超频市场所需的。我们可能会在DDR3生命周期的后半段看到更多这样的情况。

所有DDR内存都有一个问题:增加的总线频率使得创建并行数据总线变得困难。一个DDR2模块有240个引脚。所有连接到数据和地址引脚的连接必须布置得长度大致相同。更大的问题是,如果要在同一个总线上将多个DDR模块串联,每个额外的模块信号就会变得越来越失真。DDR2规范只允许每个总线(即通道)有两个模块,DDR3规范对于高频率只有一个模块。对于240个引脚的通道,一个单独的北桥合理地不能驱动超过两个通道。替代方案是拥有外部内存控制器(如图2.2所示),但这很昂贵。

这意味着商品主板被限制只能拥有最多四个DDR2或DDR3模块。这个限制严重限制了系统可以拥有的内存量。即使是旧的32位IA-32处理器也可以处理64GB的RAM,即使是家庭用途的内存需求也在增长,所以必须采取一些措施。

一种解决方案是将内存控制器添加到每个处理器中,如第2节所述。AMD在Opteron系列中这样做,Intel将使用他们的CSI技术。只要处理器能够使用的合理数量的内存可以连接到单个处理器,这就会有所帮助。在某些情况下,这不是情况,这种设置将引入NUMA架构及其负面影响。对于某些情况,需要另一种解决方案。

Intel对于大型服务器机器的解决方案,至少在未来几年内,称为全缓冲DRAM(FB-DRAM)。FB-DRAM模块使用与今天的DDR2模块相同的组件,这使得它们相对便宜地生产。不同之处在于与内存控制器的连接。FB-DRAM不是使用并行数据总线,而是使用串行总线(Rambus DRAM在那时也有这个,SATA是PATA的后继者,PCI Express是PCI/AGP的后继者)。串行总线可以在更高的频率下驱动,抵消了串行化的负面影响,甚至增加了带宽。使用串行总线的主要效果是:

  1. 每个通道可以使用更多的模块。
  2. 每个北桥/内存控制器可以使用更多的通道。
  3. 串行总线被设计为全双工(两条线)。

与双通道北桥的连接要求相比,FB-DRAM模块现在可以驱动6个通道,引脚数量更少:2×240引脚与6×69引脚。每个通道的路由要简单得多,这也可能有助于降低主板的成本。

全双工并行总线对于传统的DRAM模块来说是禁止的,复制所有这些线路太昂贵了。使用串行线路(即使它们是差分的,如FB-DRAM所要求的)就不是这样,因此串行总线被设计为全双工,这意味着在某些情况下,仅通过这一点,带宽理论上可以翻倍。但它不是用于增加带宽的唯一并行性使用地方。由于FB-DRAM控制器可以同时运行多达六个通道,即使对于系统内存量较小的情况,也可以通过使用FB-DRAM增加带宽。在DDR2系统上,如果四个模块有两个通道,那么相同的容量可以通过使用普通的FB-DRAM控制器通过四个通道来处理。串行总线的实际带宽取决于FB-DRAM模块上使用的DDR2(或DDR3)芯片的类型。

我们可以这样总结优点:

DDR2 FB-DRAM
引脚 240 69
通道 2 6
每个通道的DIMMs 2 8
最大内存 16GB 192GB
吞吐量 ~10GB/s ~40GB/s

如果在一个通道上使用多个DIMMs,FB-DRAMs有一些缺点。信号在链中的每个DIMM上都会延迟——尽管是最小的——这意味着延迟会增加。但对于相同数量的内存和相同频率的FB-DRAM,由于每个通道只需要一个DIMM,它总是可以比DDR2和DDR3更快;对于大型内存系统,DDR使用商品组件根本没有办法。

2.2.5 结论

本节应该已经展示了访问DRAM不是任意快速的过程。至少不像处理器运行的速度那样快,以及它可以访问寄存器和缓存的速度。重要的是要记住CPU和内存频率之间的差异。一个运行在2.933GHz的Intel Core 2处理器和一个1.066GHz的FSB有一个11:1的时钟比率(注意:1.066GHz的总线是四倍泵送的)。内存总线上的每个周期的停顿意味着处理器的11个周期的停顿。对于大多数机器,实际使用的DRAMs更慢,因此增加了延迟。在接下来的部分中讨论停顿时,请记住这些数字。

读取命令的时序图表明,DRAM模块能够实现高持续的数据速率。整个DRAM行可以传输而不会产生单个停顿。数据总线可以100%保持占用。对于DDR模块,这意味着每个周期传输两个64位字。使用DDR2-800模块和两个通道,这意味着速率为12.8GB/s。

但是,除非这样设计,DRAM访问并不总是顺序的。使用的是非连续的内存区域,这意味着需要预充电和新的RAS信号。这就是事情变慢的时候,当DRAM模块需要帮助时。预充电可以多快发生,RAS信号可以多快发送,当行实际使用时的惩罚就越小。

硬件和软件预取(见第6.3节)可以用来在时序上创建更多的重叠,并减少停顿。预取还有助于将内存操作在时间上移动,以便在稍后,即实际需要数据之前,减少竞争。当一轮产生的数据需要存储,而下一轮所需的数据需要读取时,这经常是一个问题。通过及时移动读取,写入和读取操作不必几乎同时发出。通过及时移动读取,写入和读取操作不必几乎同时发出。

转载


每个程序员都应该了解的内存知识-Part1
https://ysc2.github.io/ysc2.github.io/2024/04/28/每个程序员都应该了解的内存知识-Part1/
作者
Ysc
发布于
2024年4月28日
许可协议