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

9

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

  1. 下面是 stream.c 中的注释。为什么要求数组的大小应该是缓存大小的四倍?
 *       (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

这里设置了编译标志,包括启用OpenMP支持、数组大小等。

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

有任何意见吗?

谢谢!

这是针对一台Skylake Linux服务器的。


此外,我尝试了不同的numactl配置,使线程或内存固定在不同的NUMA节点上。在所有配置中,我的更改后的stream.c始终报告超过10%的带宽数字。因此,我认为我们可以排除NUMA导致差异的可能性。 - yeeha
1
一个单线程通常无法饱和DRAM带宽,特别是在英特尔服务器芯片上。单核带宽受延迟/最大并发数的限制,即它可以同时处理的离核请求的数量,而不是受DRAM控制器带宽的限制。为什么Skylake对于单线程内存吞吐量要比Broadwell-E好得多?比较了Broadwell-E和四核Skylake台式机。 - Peter Cordes
1
STREAM告诉您像那样的循环可以运行多快。当所有核心都处于活动状态时,它通常应该接近饱和DRAM带宽,但高速缓存命中可能会增加总量。现代CPU非常复杂,从一个循环的性能预测到另一个循环的性能存在许多陷阱。如果您关心这一点,请对自己的应用程序或其中的关键循环进行基准测试。但是,为了表征硬件,STREAM是其中一个使用的基准测试,而其他基准测试包括SiSoft Sandra。 - Peter Cordes
这篇文章可能会很有趣:http://www.admin-magazine.com/HPC/Articles/Finding-Memory-Bottlenecks-with-Stream。我只是粗略地浏览了一下,但它提出了一些关于各种众核系统的每个核心带宽的好观点。虽然不完全符合你的问题。 - Peter Cordes
2
我认为你忘记了写操作(除非使用非暂态/写合并优化),它们包含一个隐式读取。通过添加两个读取,您将增加表面带宽约11%(使用四个实际访问的三个表面访问与使用六个实际访问的五个表面访问;(5/6)/(3/4)=(10/9)≈1.11)。这似乎解释了大部分差异。 - user2467198
显示剩余3条评论
3个回答

16
现代计算机中的内存访问要比人们想象的复杂得多,很难确定“高级”模型何时会因为您之前不知道的一些“低级”细节而崩溃。
STREAM基准测试代码只测量执行时间 - 其他所有内容都是衍生的。衍生数据基于我认为是“合理”的决策和关于大多数计算机工作方式的假设。运行规则是试错的产物 - 试图在可移植性和通用性之间取得平衡。
STREAM基准测试报告每个内核的“带宽”值。这些是基于以下假设的简单计算:每个循环右侧的数组元素都必须从内存中读取,每个循环左侧的数组元素都必须写入内存。然后,“带宽”就是总数据移动量除以执行时间。
这个简单计算中涉及了很多假设。
  • 该模型假设编译器生成的代码执行了由内存流量计数所暗示的所有加载、存储和算术指令。在STREAM中采用的方法相当强健,但是高级编译器可能会注意到每个数组中的所有元素都包含相同的值,因此实际上只需要处理每个数组中的一个元素。(这是验证代码的工作原理。)
  • 有时编译器会将计时器调用移出它们的源代码位置。这是一种(微妙的)违反语言标准,但很容易捕捉到,因为它通常会产生无意义的结果。
  • 该模型假设缓存命中数量可忽略不计。如果有缓存命中,则计算出来的值仍然是“带宽”,但不是“内存带宽”。STREAM复制和比例内核只加载一个数组(并存储一个数组),因此如果存储器绕过缓存,则每次迭代通过缓存传输的总流量为一个数组的大小。缓存寻址和索引有时非常复杂,缓存替换策略可能是动态的(伪随机或基于运行时利用率度量)。为了在尺寸和精度之间取得折衷,我选择4倍作为最小数组大小相对于缓存大小,以确保大多数系统具有非常低的缓存命中比例(即足够低,以对报告的性能产生可以忽略的影响)。
  • STREAM中的数据流量计数不会“给予”硬件执行的额外传输,但这些传输没有明确请求。这主要是指“写入分配”流量 -- 大多数系统在存储器中读取每个存储目标地址,然后存储才能更新相应的缓存行。许多系统具有跳过此“写入分配”的能力,方法是分配一个缓存行而不读取它(Power),或者执行绕过缓存直接到内存的存储操作(x86)。更多关于此的注释,请参见http://sites.utexas.edu/jdm4372/2018/01/01/notes-on-non-temporal-aka-streaming-stores/
  • 多核处理器的DRAM通道数超过2个时,通常无法仅使用单个核心达到渐近带宽。原来为大型共享内存系统提供的OpenMP指令现在必须在几乎每个具有超过2个DRAM通道的处理器上启用,如果你想达到渐近带宽水平。
  • 单核带宽仍然很重要,但通常受限于单个核心可以生成的缓存未命中次数,而不是系统的峰值DRAM带宽。这些问题在http://sites.utexas.edu/jdm4372/2016/11/22/sc16-invited-talk-memory-bandwidth-and-system-balance-in-hpc-systems/中介绍。
  • 对于单核情况,L1数据缓存未命中的未完成数量太小,无法获得完整带宽 -- 对于您的Xeon可扩展处理器,每个插槽需要大

    避免“写分配”流量的其他注意事项:

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

1
哇,我不知道你也在Stack Overflow上。考虑更改用户名,这样人们就知道是你了。:) 顺便说一句,即使是一些双通道台式机/笔记本电脑CPU在运行glibc的memcpy或memset时,也不能完全饱和内存带宽。它们比大型Xeon上的单个核心更接近,具体取决于核心时钟速度与内存时钟速度之比,但特别是在快速DDR4上,我认为Skylake可能会受到限制,因为一个核心可以保持的有限线路填充缓冲区和/或L2超级队列缓冲区的内存并行性。 - Peter Cordes
许多系统都有跳过这个“写分配(write allocate)”的能力,即在不读取缓存中的行的情况下分配一行。有关此功能的任何文档吗?如果跳过了内存读取,处理器如何确保同一缓存行中的未修改数据保持完好无损?谢谢。 - yeeha
@yeeha:看我的回答:在x86上,这是通过NT存储器完成的,它们不是一致的。只有在进行完整行写入时才能跳过读取/修改/写入步骤;这就是为什么它们也被称为“流式存储器”,因为这是它们的用例。请参见我已经链接的Enhanced REP MOVSB for memcpy以获取有关此功能的更多信息。(该答案还有进一步的链接。) - Peter Cordes
1
@Peter Cordes -- 小问题:在x86上,非临时存储在大多数(但可能不是全部)方面都是“协同”的。 非临时存储遵循不同的排序模型--它们可能比预期的更晚可见。 这些有时被称为“弱有序”存储或“非全局有序”存储。 唯一可以称之为“非协同”的方面是(与IO DMA写入类似),当写组合缓冲区被刷新时,会向所有高速缓存发送无效命令。 这将使甚至是脏数据的行失效,而不会导致脏数据的回写。 - John D McCalpin
1
在进一步研究英特尔一致性协议的内部机制时,我发现了一些证据表明SKX/CLX处理器将在被DMA写入(或流式存储)覆盖之前回写M状态行,但我尚未尝试测试这一点。WB可能需要更新缓存标记/嗅探过滤器/内存目录等以确保正确性。实际上这应该很少见,因此不会影响性能。DMA写入静默覆盖M状态行是我曾经使用的至少一种处理器的功能....;-) - John D McCalpin
显示剩余2条评论

3
关键点在于,正如Bandwidth博士的回答所指出的那样,STREAMS只计算源代码所见到的有用的带宽。(他是基准测试的作者。)
实际上,写流也会产生读带宽成本,用于RFO(Read For Ownership)请求。当CPU想要向缓存行写入16个字节(例如)时,首先必须加载原始缓存行,然后在L1d缓存中进行修改。
(除非你的编译器自动向NT存储器量化,绕过缓存并避免RFO。一些编译器将为它们期望写入一个比缓存更大的数组的循环执行此操作,而不是在任何数据被重新读取之前。)
有关避免RFO的缓存旁路存储器,请参见Enhanced REP MOVSB for memcpy
因此,增加读流与写流的数量将使软件观察到的带宽接近实际硬件带宽。(此外,内存的混合读/写工作负载可能不完全高效。)

1
我应该把我的评论写成答案,唉。 - user2467198

2
STREAM基准测试的目的不是衡量峰值内存带宽(即系统可达到的最大内存带宽),而是测量一些对HPC社区重要的内核(COPY,SCALE,SUM和TRIAD)的“内存带宽”。因此,当STREAM报告的带宽更高时,意味着HPC应用程序在该系统上可能会运行得更快。
在了解 STREAM 基准测试的上下文中理解“内存带宽”这个术语的含义非常重要,文档的最后一节对此进行了解释。正如该部分所述,有至少三种方法来计算基准测试的字节数。STREAM 基准测试使用 STREAM 方法,在源代码级别计算读取和写入的字节数。例如,在 SUM 内核(a(i) = b(i) + c(i))中,读取两个元素并写入一个元素。因此,假设所有访问都是针对内存的,则每次迭代从内存访问的字节数等于数组数量乘以一个元素的大小(即 8 字节)。STREAM 通过将使用 STREAM 方法计算的总访问元素数乘以元素大小,并将其除以内核的执行时间来计算带宽。为了考虑到运行间变化,每个内核运行多次,并报告算术平均值、最小值和最大带宽。
正如你所看到的,STREAM 报告的带宽并不是真实的内存带宽(在硬件层面上),因此说它是峰值带宽甚至没有意义。此外,它几乎总是比峰值带宽低得多。例如,this 这篇文章展示了 ECC 和 2MB 页大小如何影响 STREAM 报告的带宽。编写一个能够实现现代 Intel 处理器硬件层面上最大可能内存带宽的基准测试是一个巨大的挑战,也许是整个博士论文的好课题。然而,在实践中,峰值带宽比 HPC 领域中的 STREAM 带宽不那么重要。(相关:请参见我的回答以获取有关在硬件层面上测量内存带宽涉及的问题的信息。)

关于您的第一个问题,注意STREAM假设所有读写都由主内存而不是任何缓存满足。分配比LLC大小大得多的数组有助于使这种情况更有可能发生。实际上,需要克服LLC的复杂和未记录的方面,包括替换策略和放置策略。它不必正好比LLC大4倍。我的理解是,这是Bandwidth博士在实践中发现有效的方法。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接