存储器层次结构
为了有效地利用CPU中提供的所有硬件资源,需要在正确的时间提供正确的数据。理解存储器层次结构对于充分发挥CPU性能至关重要。大多数程序都表现出局部性的属性:它们不会均匀地访问所有代码或数据。CPU存储器层次结构建立在两个基本属性上:
- 时间局部性:当访问给定的内存位置时,很可能在不久的将来再次访问同一位置。理想情况下,我们希望在下一次需要时,该信息位于缓存中。
- 空间局部性:当访问给定的内存位置时,很可能会在不久的将来访问附近的位置。这指的是将相关数据放在彼此附近。当程序从内存中读取一个字节时,通常会获取更大的内存块(缓存行),因为该程序很可能很快就会需要该数据。
本节概述了现代CPU支持的存储器层次结构系统的关键属性。
缓存层次结构
缓存是从CPU流水线发出的任何请求(用于代码或数据)的存储器层次结构的第一级。理想情况下,具有最小访问延迟的无限缓存是流水线的最佳选择。然而,在现实中,任何缓存的访问时间都会随着大小的增加而增加。因此,缓存被组织为一系列靠近执行单元的小型、快速的存储块,由较大、较慢的块支持。缓存层次结构的特定级别可以专门用于代码(指令缓存,i-cache)或数据(数据缓存,d-cache),或者在代码和数据之间共享(统一缓存)。此外,层次结构的某些级别可以是特定核心专用的,而其他级别可以在核心之间共享。
缓存被组织为具有定义的块大小(缓存行)。现代CPU中典型的缓存行大小为64字节。靠近执行流水线的缓存通常的大小范围从8KiB到32KiB不等。在层次结构中更远的缓存可以在64KiB到16MiB之间。任何级别的缓存的体系结构由以下四个属性定义。
数据在缓存中的放置。
用于请求的地址用于访问缓存。在直接映射缓存中,给定块地址只能出现在缓存中的一个位置,并由下面的映射函数定义。
在完全关联的缓存中,给定块可以放置在缓存中的任何位置。
直接映射缓存和完全关联映射之间的中间选项是集合关联映射。在这样的缓存中,块被组织为集合,通常每个集合包含2、4、8或16个块。首先将给定地址映射到一个集合。在集合中,地址可以放置在该集合中的任何位置,即在该集合的块中。具有m个块的每个集合的缓存被描述为m路集合关联缓存。集合关联缓存的公式为:
在缓存中查找数据
集合关联缓存中的每个块都与一个地址标签相关联。此外,标签还包含状态位,例如有效位,用于指示数据是否有效。标签还可以包含其他位,用于指示访问信息、共享信息等,这将在本章的后续部分描述。
图 @fig:CacheLookup 显示了从流水线生成的地址如何用于检查缓存。最低位地址位定义了给定块内的偏移量;块偏移位(对于32字节缓存行为5位,对于64字节缓存行为6位)。使用上面描述的公式根据索引位选择集合。选择集合后,标签位用于与该集合中的所有标签进行比较。如果其中一个标签与传入请求的标签匹配并且有效位已设置,则会发生缓存命中。与该块条目相关联的数据(与标签查找并行读出缓存的数据数组)将提供给执行流水线。如果标签不匹配,则会发生缓存未命中。
处理未命中
当发生缓存未命中时,控制器必须选择要替换的缓存中的块,以分配导致未命中的地址。对于直接映射缓存,由于新地址只能分配到单个位置,因此取消分配映射到该位置的先前条目,并在其位置安装新条目。在集合关联缓存中,由于新的缓存块可以放置在集合的任何块中,因此需要替换算法。常用的替换算法是最近最少使用(LRU)策略,其中最近最少访问的块被淘汰以腾出空间以存放未命中地址。另一种选择是随机选择一个块作为受害块。大多数CPU在硬件中定义了这些功能,使执行软件更加容易。
处理写操作
相对于数据读取,对缓存的写入访问较少。在缓存中处理写入更加困难,CPU实现使用各种技术来处理这种复杂性。软件开发人员应特别注意硬件支持的各种写缓存流程,以确保其代码的最佳性能。
CPU设计使用两种基本机制来处理命中缓存的写操作:
- 在写透写缓存中,命中的数据被写入缓存中的块和层次结构中的下一级。
- 在写回缓存中,命中的数据仅被写入缓存中。随后,层次结构的较低级别包含陈旧的数据。修改行的状态通过标签中的脏位进行跟踪。当修改的缓存行最终从缓存中淘汰时,写回操作会强制将数据写回到下一级。
写操作导致的缓存未命中可以通过两种方式处理:
- 在写分配或写未命中获取缓存中,从层次结构的下一级加载未命中位置的数据到缓存中,随后处理写操作,就像写命中一样。
- 如果缓存使用非写分配策略,则将缓存未命中事务直接发送到层次结构的下一级,并且该块不加载到缓存中。
在这些选项中,大多数设计通常选择实现具有写分配策略的写回缓存,因为这两种技术都尝试将后续写事务转换为缓存命中,而无需向下一级发送额外的流量。写透写缓存通常使用非写分配策略。
其他缓存优化技术
对于程序员来说,理解缓存层次结构的行为对于从任何应用程序中提取性能至关重要。从流水线的角度来看,访问任何请求的延迟由以下公式给出,该公式可以递归应用到缓存层次结构的所有级别,直到主存: 硬件设计者面临的挑战是通过许多新颖的微体系结构技术来减少命中时间和未命中惩罚。从根本上说,缓存未命中会阻塞流水线并损害性能。任何缓存的未命中率高度依赖于缓存体系结构(块大小、关联性)和机器上运行的软件。因此,优化未命中率成为硬件-软件协同设计的工作。正如前面讨论的,CPU为缓存提供了最佳的硬件组织。下面描述了可以在硬件和软件中实现的降低缓存未命中率的其他技术。
硬件和软件预取
避免缓存未命中和随后的停顿的一种方法是在流水线需求之前将指令和数据预取到缓存层次结构的不同级别。假设处理未命中的惩罚的时间大部分可以被隐藏,如果预取请求在流水线中足够提前发出。大多数CPU提供了隐式基于硬件的预取,由程序员控制的显式软件预取来补充。
硬件预取器观察正在运行的应用程序的行为,并在缓存未命中的重复模式上启动预取。硬件预取可以自动适应应用程序的动态行为,例如不同的数据集,并且不需要优化编译器或分析支持。此外,硬件预取工作而不需要额外的地址生成和预取指令的开销。然而,硬件预取仅限于学习和预取有限集的缓存未命中模式。
软件内存预取补充了硬件进行的预取。开发人员可以通过专用的硬件指令(见[@sec:memPrefetch])指定提前需要的内存位置。编译器还可以自动将预取指令添加到代码中,以请求使用之前的数据。预取技术需要在需求和预取请求之间进行平衡,以防止预取流量减慢需求流量。
主存储器
主存储器是缓存之后的下一个层次,由内存控制器单元(MCU)发起对数据的加载和存储请求。过去,这个电路位于主板上的北桥芯片中。但是现在,大多数处理器都将此组件嵌入其中,因此CPU与主存储器之间有一个专用的内存总线连接。
主存储器使用DRAM(动态随机存取存储器)技术,支持以合理的成本点获取大容量。在比较DRAM模块时,人们通常会关注内存密度和内存速度,当然还有其价格。内存密度定义了模块拥有的内存量,以GB为单位。显然,可用内存越多越好,因为它是操作系统和应用程序使用的宝贵资源。
主存储器的性能由延迟和带宽描述。内存延迟是发出内存访问请求和CPU可用于使用数据之间经过的时间。内存带宽定义了在一定时间内可以获取多少字节,通常以每秒字节数(GB/s)表示。
DDR
DDR(双倍数据速率)DRAM技术是大多数CPU支持的主要DRAM技术。从历史上看,DRAM的带宽每一代都在提高,而DRAM的延迟却保持不变甚至增加。表@tbl:mem_rate显示了过去三代DDR技术的最高数据速率、峰值带宽以及相应的读取延迟。数据速率以每秒传输的百万次(MT/s)为单位。此表中显示的延迟对应于DRAM器件本身的延迟。通常,由于缓存控制器、内存控制器和芯片内连接引起的额外延迟和排队延迟,从CPU流水线(在加载到使用的缓存未命中时)看到的延迟较高(在50纳秒至150纳秒范围内)。您可以在[@sec:MemLatBw]中看到测量观察到的内存延迟和带宽的示例。
DDR | 年份 | 最高数据速率(MT/s) | 峰值带宽(GB/s) | 设备内读取延迟(ns) |
---|---|---|---|---|
DDR3 | 2007 | 2133 | 17.1 | 10.3 |
DDR4 | 2014 | 3200 | 25.6 | 12.5 |
DDR5 | 2020 | 6400 | 51.2 | 14 |
表:过去三代DDR技术的性能特征。
值得一提的是,DRAM芯片需要定期刷新其内存单元。这是因为位值被存储为微小电容器上的电荷存在,所以它可能会随着时间的推移失去电荷。为了防止这种情况发生,有特殊的电路读取每个单元并将其写回,有效地恢复电容器的电荷。当DRAM芯片处于刷新过程中时,它不会响应内存访问请求。
DRAM模块组织为一组DRAM芯片。内存rank是一个术语,用于描述模块上存在多少组DRAM芯片。例如,单rank(1R)内存模块包含一组DRAM芯片。双rank(2R)内存模块有两组DRAM芯片,因此将单rank模块的容量加倍。同样,还可以购买四rank(4R)和八rank(8R)内存模块。
每个rank由多个DRAM芯片组成。内存width定义了每个DRAM芯片的总线宽度。由于每个rank的总线宽度为64位(或ECC RAM为72位),它还定义了rank内存在芯片中的数量。内存宽度可以是x4
、x8
或x16
中的一个值,这定义了发送到每个芯片的总线宽度。例如,图@fig:Dram_ranks DRAM DDR4模块的组织,总容量为2GB。每个rank中有四个芯片,总线宽度为16位。四个芯片共同提供64位输出。两个rank通过一个rank选择信号逐个选择。
单rank或双rank的性能哪个更好并没有直接的答案,因为它取决于应用程序的类型。通过rank选择信号从一个rank切换到另一个rank需要额外的时钟周期,这可能会增加访问延迟。另一方面,如果一个rank没有被访问,它可以在其他rank忙碌时并行进行刷新周期。一旦上一个rank完成数据传输,下一个rank就可以立即开始传输。此外,单rank模块产生的热量更少,故障的可能性更低。
进一步说,我们可以在系统中安装多个DRAM模块,不仅增加内存容量,还增加内存带宽。多个内存通道的设置用于扩展内存控制器和DRAM之间的通信速度。
具有单个内存通道的系统在DRAM和内存控制器之间有一个64位宽的数据总线。多通道架构通过增加内存数据总线的宽度,允许同时访问DRAM模块。例如,双通道架构将内存数据总线的宽度从64位扩展到128位,将可用带宽加倍,参见图@fig:Dram_channels。请注意,每个内存模块仍然是一个64位设备,但我们将它们连接方式有所不同。如今,服务器机器通常具有四个和八个内存通道。
或者,您也可能遇到具有复制内存控制器的设置。例如,处理器可能具有两个集成的内存控制器,每个内存控制器都可以支持多个内存通道。这两个控制器是独立的,只查看总物理内存地址空间的自己的部分。
我们可以通过以下简单的公式快速计算给定内存技术的最大内存带宽:
例如,对于单通道DDR4配置,数据速率为2400 MT/s
,每个内存周期可以传输64位(8字节),因此最大带宽等于2400 * 8 = 19.2 GB/s
。双通道或双内存控制器设置将带宽加倍至38.4 GB/s
。但要记住,这些数字是假设数据传输将在每个内存时钟周期中发生的理论最大值,在实践中实际上是不会发生的。因此,当测量实际内存速度时,您将始终看到比最大理论传输带宽低的值。
要启用多通道配置,您需要具备支持这种架构的CPU和主板,并在主板上正确的内存插槽中安装相同数量的内存模块。在Windows上检查设置的最快方法是运行诸如CPU-Z
或HwInfo
之类的硬件识别实用程序;在Linux上,您可以使用dmidecode
命令。或者,您可以运行内存带宽基准测试,例如Intel的mlc
或Stream
。
要在系统中利用多个内存通道,有一种称为交错的技术。它在一个页面中将相邻地址在多个内存设备之间分布。图@fig:Dram_channel_interleaving。与以前一样,我们有双通道内存配置(通道A和B),具有两个独立的内存控制器。现代处理器按每四个缓存行(256字节)进行交错,即,前四个相邻缓存行发送到通道A,然后下一组四个缓存行发送到通道B。
如果不使用交错,连续的相邻访问将发送到同一个内存控制器,而不是利用第二个可用的控制器。相反,交错使硬件并行性更好地利用了可用的内存带宽。对于大多数工作负载,当所有通道都被填充时,性能最大化,因为它将单个内存区域扩展到尽可能多的DRAM模块中。
虽然增加内存带宽通常是有益的,但它并不总是转化为更好的系统性能,而且高度依赖于应用程序。另一方面,注意可用和已使用的内存带宽非常重要,因为一旦它成为主要瓶颈,应用程序就会停止扩展,即,添加更多核心并不能使其运行更快。
GDDR和HBM
除了多通道DDR外,还有其他技术针对需要更高内存带宽以实现更高性能的工作负载。 GDDR(图形DDR)和HBM(高带宽内存)等技术是最显着的技术。它们在高端图形、高性能计算(如气候建模、分子动力学、物理模拟),但也包括自动驾驶和人工智能/机器学习等领域中得到应用。它们在这些领域中非常适用,因为这些应用需要非常快速地移动大量数据。
GDDR最初是为图形设计的,现在几乎每个高性能图形卡都在使用。虽然GDDR与DDR共享一些特征,但它也有很大的不同。虽然DRAM DDR设计用于更低的延迟,但GDDR则设计用于更高的带宽,因为它位于处理器芯片本身的同一封装中。与DDR类似,GDDR接口每个时钟周期传输两个32位字(总共64位)。最新的GDDR6X标准可以实现高达168 GB/s的带宽,以相对较低的656 MHz频率运行。
[待办事项]: 解释GDDR6X如何实现168 GB/s的带宽
HBM是一种新型的CPU/GPU内存,它垂直堆叠内存芯片,也称为3D堆叠。与GDDR类似,HBM极大地缩短了数据到达处理器的距离。与DDR和GDDR的主要区别在于,HBM内存总线非常宽:每个HBM堆栈为1024位。这使HBM能够实现超高带宽。最新的HBM3标准支持每个封装高达665 GB/s的带宽。它还以500 MHz的低频率运行,并具有每个封装高达48 GB的内存密度。
如果您想获得尽可能多的内存带宽,那么拥有HBM的系统将是一个不错的选择。但是,在撰写本文时,这项技术相当昂贵。由于GDDR主要用于图形卡,HBM可能是加速在CPU上运行的某些工作负载的好选择。事实上,第一批具有集成HBM的x86通用服务器芯片现已上市。