原文:What every programmer should know about memory, Part 1
1.简介
早期的计算机比较简单。各种各样的系统组件,例如CPU,内存,大容量存储,网络接口,是一起开发的,所以它们的性能比较均衡。例如,在给CPU提供数据的时候,内存并不比网络接口快(很多)。
当计算机的基本结构固化,硬件开发者开始专注优化各个子系统时,这种情况发生了改变,某些计算机组件的性能显著落后而且出现瓶颈。特别的,由于成本原因,相对于其他组件,大容量存储跟内存子系统提高的更慢。
大容量存储的性能问题主要靠软件来改善:操作系统将最常用(跟最有可能被使用)的数据放在主存中,因为其访问速率比硬盘要快几个数量级。将缓存添加到存储设备本身,这样就可以在不更改操作系统的情况下提高性能(然而,为了使用缓存时,保证数据的完整,改变还是需要的)。研究该内容不是本文的目的,我们不涉及过多的介绍大容量存储访问的软件优化。
与大容量存储不同,解决内存的瓶颈被证明更为困难,几乎每种方案都需要对硬件作出修改。目前,这些变更主要有以下这些方式:
- RAM(随机存取存储器)硬件的设计(速度与并发度)
- 内存控制设计
- CPU缓存
- 设备的直接内存访问(DMA)
本文主要介绍CPU缓存和内存控制器设计的一些影响。在这些主题的扩展过程中,我们将研究DMA跟进入更大的场景。我们从现代的商用硬件设计开始,这将有助于我们理解使用内存子系统的问题跟局限。我们将详细介绍RAM的分类,说明为什么会存在这么多不同类型的内存。
本文不会包括所有方面,也不会有最终性质的内容。我们的讨论范围仅限于商用硬件,并且进一步限于该硬件的一部分。此外,讨论更多的主题,本文以达到目标为止。对于这些主题,建议读者查找更详细的文档。
当本文提到操作系统特定的细节和解决方案时,说的都是Linux,不会包含任何有关其他操作系统的信息。作者无意讨论其他操作系统的情况,如果读者不得不使用别的操作系统,那么必须去要求供应商提供其操作系统类似于本文的文档。
如果读者认为他/她不得不使用别的操作系统,那么必须去要求供应商提供其操作系统类似于本文的文档。
1.1 文档结构
本文主要为软件开发者而写的,不会涉及太多硬件细节,所以可能对抱着阅读硬件知识而来的读者不是很有用。但是我们在开始为开发人员提供有用的信息之前,必须先为他们奠定足够多的基础内容。
本文的第二章节将描述RAM的技术细节。看懂该章节的内容很好,但是此部分的内容并不是懂得其后内容所必须掌握的。我们会在之后需要的地方适当引用该章节的内容,所以心急的读者可以先跳过该章节的大部分内容去读他们认为有用的部分。
第三章节会谈到很多关于CPU缓存行为的细节。我们会使用一些图表,避免你们觉得太枯燥。第三章节对于理解整篇文章非常重要。第四章节将简要的描述如何实现虚拟内存,这也是理解全文其他部分的必要基础。
第五章节会谈到很多关于非统一内存访问架构(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公司预计,对于未来的产品,大多数数据中心的“standard building blocks”将是一台有多达四个插槽的计算机,每个插槽上都有一个四核CPU,对于英特尔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之间的数据通信必须通过用于与北桥通信的同一总线。
- 与RAM的所有通信都必须通过南桥。
- RAM只有一个端口。(在本文档中,我们将不讨论多端口ARM,因为这种类型的RAM不存在于商用硬件中,至少程序员无法访问到。它可以在网络路由器等专用硬件中找到,这些硬件需要很快的速度。)
- CPU和连接到南桥的设备之间的通信通过北桥进行。
在这种设计中,有两个瓶颈立即显现出来。第一个瓶颈涉及访问设备的RAM,早期的PC,设备之间的通信(通过南/北桥)必须通过CPU,这对整体系统性能产生了负面的影响。为了解决这个问题,一些设备能够直接访问内存(DMA)。DMA允许设备在北桥的帮助下,无需通过CPU介入(及其固有的性能成本)便能存储数据到RAM中或访问RAM的数据。今天,所有连接到任何总线上的高性能设备都可以使用DMA。虽然这大大减少了CPU上的工作负载,却占用了北桥的带宽,与CPU形成了争用。因此,必须考虑到这个问题。
第二个瓶颈来自从北桥到RAM的总线。总线的详细细节取决于安装的内存类型。在较旧的系统上,只有一条总线连接到所有RAM芯片,因此无法进行并行访问。最近的RAM类型需要两个独立的总线(或称为DDR2的通道,见图2.8),它使可用带宽加倍。北桥将内存访问交错地分配到两个通道上。最近的内存技术(例如FB-DRAM)增加了更多的通道。
在可用带宽有限的情况下,我们需要以一种使延迟最小化的方式来对内存访问进行调度。正如我们将要看到的,尽管使用了CPU缓存,处理器的速度比内存要快得多,必须等待才能访问内存。如果多个超线程内核或处理器同时访问内存,则访问内存的等待时间甚至更长。对于DMA操作也是如此。
然而,除了并发以外,访问模式本身也对存储子系统的性能有很大的影响,特别是在多个存储通道的情况下。有关RAM访问模式的详细信息,请参阅2.2节。
在一些更昂贵的系统上,北桥实际上并不包含内存控制器。相反,北桥可以连接到许多外部内存控制器(在下面的示例中,其中四个)。
该体系结构的优点是存在多个内存总线,因此总带宽增加。此设计还支持更多的内存。并发内存访问模式通过同时访问不同的内存区来减少延迟。当多个处理器直接连接到北桥时尤其如此,如图2.2所示。对于这样的设计,最主要的限制是北桥的内部带宽,这对于这个体系结构(来自Intel)来说是非常明显的。(为了完整起见,应该指出,这种内存控制器可用于其他用途,如“memory RAID”,与热插拔内存结合使用。)
使用多个外部存储器控制器并不是增加存储器带宽的唯一方法。另一种日益流行的方法是将内存控制器集成到CPU中,并将内存连接到每个CPU。这种体系结构在基于AMD的Opteron处理器的SMP系统中得到了广泛的应用。图2.3显示了这样一个系统。Intel则会从Nehalem处理器开始支持通用系统接口(CSI);这基本上是相同的方法:集成内存控制器,,为每个处理器提供本地内存。
有了这样的体系结构,可用的内存库( memory banks)就可以像处理器一样多。在四CPU机器上,内存带宽是四倍的,而不需要复杂的具有巨大带宽的北桥。将内存控制器集成到CPU中还有一些额外的优点;我们不会在这里更深入地研究这一技术。
这种架构也有缺点。首先,由于机器仍然必须使所有处理器都可以访问系统的所有内存,因此内存不再是统一的(NUMA即得名于此)。处理器能以正常的速度访问本地内存(连接到该处理器的内存)。访问连接到另一个处理器的内存时,情况会有所不同。在这种情况下,必须使用处理器之间的互连通道。要从CPU1访问连接到CPU2的内存,需要跨一个互连通道进行通信。如果它需要访问CPU 4的内存,那么需要跨越两条互联通道。
每个这样的通信都有相关的成本。当我们描述访问远程内存所需的额外时间时,我们用「NUMA因子」这个词。图2.3中的示例体系结构对于每个CPU有两个级别:相邻的CPU和两个互连之外的CPU。更加复杂的系统中,层级也更多。还有一些机器体系结构(例如IBM的x 445和SGI的Altix系列),其中有多种类型的连接。CPU被组织成节点;在节点内,访问内存的时间可能是一致的,或者只有很小的NUMA因子。但是,节点之间的连接可能代价很大,NUMA因子可能相当高。
目前,商用的NUMA机器已经存在,将来可能会发挥更大的作用。预计从2008年底开始,每台SMP机器都将使用NUMA。每个在NUMA上运行的程序都应该认识到NUMA的代价。在第5节中,我们将讨论更多的架构,以及Linux内核为这些程序提供的一些技术。
除了本节中所介绍的技术之外,还有几个其他因素影响RAM的性能。它们不能被软件控制,这就是为什么本节没有讨论它们的原因。有兴趣的读者可以在2.1节中了解其中一些因素。介绍这些技术,仅仅是因为它们能让我们更全面地了解内存技术,或者是可能在大家购买计算机时能够提供一些帮助。
以下的两节主要介绍一些入门级的硬件知识,同时讨论内存控制器与DRAM芯片间的访问协议。这些知识解释了内存访问的原理,程序员可能会得到一些启发。不过,这部分并不是必读的,心急的读者可以直接跳到第2.2.5节。
待续。。。