STREAM 内存带宽基准测试真正衡量的是什么



我对STREAM(http://www.cs.virginia.edu/stream/ref.html#runrules)基准测试有几个问题。

  1. 以下是来自stream.c的评论。数组应该是缓存大小的 4 倍的要求的基本原理是什么?
*       (a) Each array must be at least 4 times the size of the
*           available cache memory. I don't worry about the difference
*           between 10^6 and 2^20, so in practice the minimum array size
*           is about 3.8 times the cache size.
  1. 我最初假设STREAM测量峰值内存带宽。但我后来发现,当我添加额外的阵列和阵列访问时,我可以得到更大的带宽数字。所以在我看来,STREAM 不能保证内存带宽饱和。那么我的问题是STREAM真正衡量的是什么,你如何使用STREAM报告的数字?

例如,我添加了两个额外的数组,并确保将它们与原始 a/b/c 数组一起访问。我相应地修改了字节记帐。有了这两个额外的阵列,我的带宽数字增加了 ~11.5%。

> diff stream.c modified_stream.c
181c181,183
<                       c[STREAM_ARRAY_SIZE+OFFSET];
---
>                       c[STREAM_ARRAY_SIZE+OFFSET],
>                       e[STREAM_ARRAY_SIZE+OFFSET],
>                       d[STREAM_ARRAY_SIZE+OFFSET];
192,193c194,195
<     3 * sizeof(STREAM_TYPE) * STREAM_ARRAY_SIZE,
<     3 * sizeof(STREAM_TYPE) * STREAM_ARRAY_SIZE
---
>     5 * sizeof(STREAM_TYPE) * STREAM_ARRAY_SIZE,
>     5 * sizeof(STREAM_TYPE) * STREAM_ARRAY_SIZE
270a273,274
>             d[j] = 3.0;
>             e[j] = 3.0;
335c339
<           c[j] = a[j]+b[j];
---
>           c[j] = a[j]+b[j]+d[j]+e[j];
345c349
<           a[j] = b[j]+scalar*c[j];
---
>           a[j] = b[j]+scalar*c[j] + d[j]+e[j];

CFLAGS = -O2 -fopenmp -D_OPENMP -DSTREAM_ARRAY_SIZE=50000000

我的最后一级缓存大约是 35MB。

任何通讯网?

谢谢!

这是针对Skylake Linux服务器。

现代计算机中的内存访问比人们想象的要复杂得多,并且很难判断"高级"模型何时会因为一些您以前不知道的"低级"细节而分崩离析。

STREAM 基准代码只测量执行时间——其他一切都是派生的。 得出的数字是基于我认为什么是"合理"的决定和关于大多数计算机如何工作的假设。 运行规则是反复试验的产物 - 试图在可移植性和通用性之间取得平衡。

STREAM 基准测试报告每个内核的"带宽"值。 这些是简单的计算,基于以下假设:每个循环右侧的每个数组元素必须从内存中读取,每个循环左侧的每个数组元素都必须写入内存。 那么"带宽"就是移动的数据总量除以执行时间。

这个简单的计算涉及数量惊人的假设。

  • 该模型假定编译器生成代码以执行内存流量计数隐含的所有加载、存储和算术指令。 STREAM 中用于鼓励这样做的方法相当可靠,但高级编译器可能会注意到每个数组中的所有数组元素都包含相同的值,因此实际上只需要处理每个数组中的一个元素。 (这是验证代码的工作方式。
  • 有时,编译器会将计时器调用移出其源代码位置。 这是对语言标准的(微妙)违反,但很容易抓住,因为它通常会产生荒谬的结果。
  • 该模型假设缓存命中数可以忽略不计。 (对于缓存命中,计算值仍然是"带宽",只是不是"内存带宽"。 STREAM 复制和缩放内核仅加载一个数组(并存储一个数组),因此如果存储绕过缓存,则每次迭代中通过缓存的流量总量为一个数组的大小。 缓存寻址和索引有时非常复杂,缓存替换策略可能是动态的(伪随机或基于运行时利用率指标)。 作为大小和准确性之间的折衷,我选择了 4x 作为相对于缓存大小的最小数组大小,以确保大多数系统的缓存命中率非常低(即,足够低,对报告的性能的影响可以忽略不计)。
  • STREAM 中的数据流量计数不会"记入"硬件执行的其他传输,但未明确请求。 这主要是指"写入分配"流量 - 大多数系统在存储可以更新相应的缓存行之前从内存中读取每个存储目标地址。 许多系统能够跳过这种"写入分配",方法是在缓存中分配一行而不读取它 (POWER),或者通过执行绕过缓存并直接进入内存的存储 (x86)。 有关这方面的更多说明,请参阅 http://sites.utexas.edu/jdm4372/2018/01/01/notes-on-non-temporal-aka-streaming-stores/
  • 具有 2 个以上 DRAM 通道的多核处理器通常仅使用单个内核无法达到渐近带宽。 如果要达到渐近带宽级别,现在必须在几乎每个具有 2 个以上 DRAM 通道的处理器上启用最初为大型共享内存系统提供的 OpenMP 指令。
  • 单核带宽仍然很重要,但通常受到单个内核可以生成的缓存未命中次数的限制,而不是系统的峰值DRAM带宽。 这些问题以 http://sites.utexas.edu/jdm4372/2016/11/22/sc16-invited-talk-memory-bandwidth-and-system-balance-in-hpc-systems/
  • 对于单核情况,未完成的一级数据缓存未命中数太少,无法获得全带宽 - 对于至强可扩展处理器,每个插槽大约需要 140 个并发缓存未命中,但单个内核只能支持 10-12 次 L1 数据缓存未命中。 L2 硬件预取程序可以生成额外的内存并发(如果我没记错的话,每个内核最多 ~24 次缓存未命中),但达到接近此范围上限的平均值需要同时访问更多 4KiB 页面。 额外的阵列读取使 L2 硬件预取程序有更多机会生成(接近)最大并发内存访问数。 增加11%-12%是完全合理的。
  • 增加读取比例也有望提高使用所有内核时的性能。 在这种情况下,好处主要是通过减少DDR4 DRAM接口上的"读写周转停顿"的数量。 在没有存储的情况下,此处理器上的持续带宽应达到 90% 的峰值(每个插槽使用 16 个或更多内核)。

有关避免"写入分配"流量的其他说明:

  1. 在 x86 体系结构中,缓存绕过存储通常会使本地缓存中的相应地址失效,并将数据保存在"写入组合缓冲区"中,直到处理器决定将数据推送到内存。 在此期间,允许其他处理器保留和使用缓存行的"过时"副本。 刷新写入组合缓冲区时,缓存行将在与 IO DMA 写入非常相似的事务中发送到内存控制器。 内存控制器负责在更新内存之前对地址发出"全局"失效。 使用这些流式处理存储来更新跨内核共享的内存时,必须小心。 一般模型是执行流式存储,执行存储围栏,然后执行"普通"存储到"标志"变量。 存储围栏将确保在所有流式存储的结果全局可见之前,没有其他处理器可以看到更新的"flag"变量。 (对于一系列"普通"商店,结果始终按程序顺序可见,因此不需要商店围栏。
  2. 在PowerPC/POWER架构中,DCBZ(或DCLZ)指令可用于避免写入分配流量。 如果该行在缓存中,则其内容设置为零。 如果该行不在缓存中,则会在缓存中分配一行,其内容设置为零。 此方法的一个缺点是缓存行大小在此处公开。 具有 32 字节缓存行的 PowerPC 上的 DCBZ 将清除 32 字节。 具有 128 字节高速缓存行的处理器上的相同指令将清除 128 字节。 这让同时使用两者的供应商感到恼火。 我不记得 POWER 内存排序模型的细节,无法评论如何/何时使用此指令显示一致性事务。

正如Bandwidth博士的回答所指出的,这里的关键点是STREAMS只计算源代码看到的有用带宽。 (他是基准测试的作者。

实际上,写入流也会产生RFO(所有权读取)请求的读取带宽成本。 例如,当 CPU 想要将 16 字节写入缓存行时,首先必须加载原始缓存行,然后在 L1d 缓存中对其进行修改。

(除非您的编译器使用 NT 存储自动矢量化,从而绕过缓存并避免该 RFO。一些编译器会为循环执行此操作,他们希望在重新读取任何数组之前写入一个太大的数组以进行缓存。

请参阅用于 memcpy 的增强型 REP MOVSB,了解有关避免 RFO 的缓存绕过存储的更多信息。


因此,增加读取流与写入流的数量将使软件观察到的带宽更接近实际硬件带宽。(此外,内存的混合读/写工作负载可能效率不高。

STREAM 基准测试的目的不是测量峰值内存带宽(即系统上可以实现的最大内存带宽),而是测量对 HPC 社区很重要的许多内核(COPY、SCALE、SUM 和 TRIAD)的"内存带宽"。因此,当 STREAM 报告的带宽较高时,这意味着 HPC 应用程序可能会在系统上运行得更快。

在 STREAM 基准测试的上下文中了解术语"内存带宽"的含义也很重要,文档的最后一部分对此进行了说明。如该部分所述,至少有三种方法可以计算基准测试的字节数。STREAM 基准测试使用 STREAM 方法,该方法计算在源代码级别读取和写入的字节数。例如,在 SUM 内核 (a(i) = b(i) + c(i)) 中,读取两个元素并写入一个元素。因此,假设所有访问都是对内存的访问,则每次迭代从内存访问的字节数等于数组数乘以元素的大小(即 8 个字节)。STREAM 通过将访问的元素总数(使用 STREAM 方法计数)乘以元素大小并除以内核的执行时间来计算带宽。为了考虑运行到运行的变化,每个内核运行多次,并报告算术平均值、最小和最大带宽。

如您所见,STREAM 报告的带宽不是真正的内存带宽(在硬件级别),因此说它是峰值带宽甚至没有意义。此外,它几乎总是远低于峰值带宽。例如,本文展示了 ECC 和 2MB 页面如何影响 STREAM 报告的带宽。编写一个在现代英特尔处理器上实际实现最大可能内存带宽(在硬件级别)的基准测试是一项重大挑战,对于整个博士论文来说可能是一个很好的问题。但实际上,峰值带宽不如 HPC 域中的 STREAM 带宽重要。(相关:有关在硬件级别测量内存带宽所涉及的问题的信息,请参阅我的回答。

关于您的第一个问题,请注意,STREAM 只是假设所有读取和写入都由主内存而不是任何缓存满足。分配一个比 LLC 大小大得多的数组有助于使这种情况更有可能出现这种情况。从本质上讲,LLC的复杂和未记录的方面,包括更换政策和安置政策,都需要被击败。它不必比LLC大4倍。我的理解是,这就是带宽博士在实践中发现的。

最新更新