STREAM 内存带宽基准测试真正衡量的是啥?

Posted

技术标签:

【中文标题】STREAM 内存带宽基准测试真正衡量的是啥?【英文标题】:what does STREAM memory bandwidth benchmark really measure?STREAM 内存带宽基准测试真正衡量的是什么? 【发布时间】:2019-09-28 22:01:12 【问题描述】:

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

    以下是来自 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.
    我最初假设 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 服务器的。

【问题讨论】:

另外,我尝试了不同的 numactl 配置以使线程或内存固定在不同的 numa 节点上。我更改后的 stream.c 总是在所有配置中报告超过 10% 的带宽数。所以我认为我们可以排除 NUMA-ness 导致差异的可能性。 单线程通常不能使 DRAM 带宽饱和,尤其是在英特尔服务器芯片上。单核带宽受到延迟/最大并发数的限制,它可以在运行中具有未完成的核外请求数量,而不是受到 DRAM 控制器带宽的限制。 Why is Skylake so much better than Broadwell-E for single-threaded memory throughput? 比较 Broadwell-E 与四核 Skylake 桌面。 哦,但是您使用的是 OpenMP,所以我猜您正在测量所有核心都饱和的聚合带宽?您的更改看起来将平衡更多地转向读取。 IDK 如果你得到任何 L3 缓存命中。大概没有数据在线程之间共享,那么您会期望更多的读取会提供更多帮助。 STREAM 告诉您这样的循环可以运行多快。在所有内核都处于活动状态的情况下,它通常应该接近到饱和 DRAM 带宽,但缓存命中可能会使总数膨胀。现代 CPU 是极其复杂的野兽,在根据另一个循环的性能预测一个循环的性能时存在许多缺陷。对您自己的应用程序进行基准测试,或者如果您关心它的关键循环。但对于表征硬件,STREAM 是使用的基准之一,而其他基准包括 SiSoft Sandra。 我认为您忘记了写入(除非使用非临时/写入合并优化)包括隐式读取。通过添加两次读取,您将 明显 带宽增加了大约 11%(3 次明显访问和 4 次实际访问与 5 次明显访问和 6 次实际访问;(5/6)/(3/4) = (10/9)≈1.11)。这似乎可以解释大部分差异。 【参考方案1】:

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

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

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

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

【讨论】:

【参考方案2】:

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

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

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

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

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

关于避免“写分配”流量的附加说明:

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

【讨论】:

酷,我不知道你在 Stack Overflow。考虑更改您的用户名,以便人们知道是您。 :) 顺便说一句,即使是一些双通道台式机/笔记本电脑 CPU 在运行 glibc memcpymemset 时也不会完全饱和单核的内存带宽。它们比大型 Xeon 上的单核更接近,具体取决于核心时钟速度与内存时钟的比率,但特别是对于快速 DDR4,我认为 Skylake 可能会在有限的内存并行度上成为瓶颈,一个核心可以在有限的情况下保持运行行填充缓冲区和/或 L2 超级队列缓冲区。 除了您的演讲之外,还讨论了 Stack Overflow:Why is Skylake so much better than Broadwell-E for single-threaded memory throughput? 和 Travis Downs(@BeeOnRope's)在 Enhanced REP MOVSB for memcpy 上回答的延迟绑定平台部分 “许多系统都有能力跳过这个“写分配”,或者通过在缓存中分配一行而不读取它”。有关此功能的任何文档?如果跳过内存读取,处理器如何确保同一缓存行中未修改的数据保持完整?谢谢 @Peter Cordes -- 次要 nit:在 x86 上,非临时存储在大多数(但可能不是全部)方面都是“连贯的”。非临时商店遵循不同的订购模式——它们可能比预期的更晚才可见。这些有时被称为“弱排序”商店或“非全局排序”商店。唯一可以称为“不连贯”的方面是(如 IO DMA 写入),当刷新写入组合缓冲区时,会向所有缓存发送无效命令。这甚至会使脏的行无效,而不会导致脏数据的写回。 @PeterCordes 在进一步调查英特尔一致性协议的内容后,我发现一些证据表明 SKX/CLX 处理器会在 M 状态行被 DMA 写入(或流式存储)覆盖之前回写它们),但我还没有尝试过测试。 WB 可能需要正确更新缓存标签/窥探过滤器/内存目录/等。在实践中应该很少见,所以不是性能问题。 DMA 写入对 M 状态行的静默覆盖是我处理过的至少一个处理器的处理器中的一项功能.... ;-)【参考方案3】:

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

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

(除非你的编译器使用 NT 存储自动向量化,绕过缓存并避免 RFO。 一些编译器会在循环中执行此操作,他们希望在缓存之前写入一个太大的数组以供缓存重读。)

请参阅Enhanced REP MOVSB for memcpy,了解有关避免 RFO 的绕过缓存的存储的更多信息。


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

【讨论】:

我应该让我的comment 成为答案,叹息。

以上是关于STREAM 内存带宽基准测试真正衡量的是啥?的主要内容,如果未能解决你的问题,请参考以下文章

用 Java 衡量单线程复杂算法的最佳宏基准测试工具/框架是啥? [关闭]

单核可实现的内存带宽

性能测试软件Geekbench 5.2.3 Mac破解版

对运行 IIS 的生产系统进行负载测试的良好基准测试是啥?

hadoop 基准测试的最佳实践是啥?

网络基准测试