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

时间:2019-05-11 03:44:32

标签: benchmarking cpu-architecture microbenchmark memory-bandwidth

我对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.
    我最初假定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服务器。

3 个答案:

答案 0 :(得分:7)

现代计算机中的内存访问比人们预期的要复杂得多,并且由于某些您不知道的“低级”细节,很难说出“高级”模型何时崩溃。之前......

STREAM基准代码仅测量执行时间-其他所有内容都派生出来。得出的数字基于有关我认为“合理”的决定以及有关大多数计算机的工作原理的假设。运行规则是反复试验的产物-试图在可移植性和通用性之间取得平衡。

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

这种简单的计算涉及许多令人惊讶的假设。

  • 该模型假定编译器生成代码以执行内存流量计数所隐含的所有加载,存储和算术指令。 STREAM中使用的鼓励这种方法的方法相当健壮,但是高级编译器可能会注意到每个数组中的所有数组元素都包含相同的值,因此每个数组中实际上只需要处理一个元素。 (这就是验证代码的工作方式。)
  • 有时,编译器会将计时器调用移出其源代码位置。这是(微妙的)违反语言标准的行为,但很容易抓住,因为它通常会产生无意义的结果。
  • 该模型假设缓存命中数可以忽略。 (对于缓存命中,计算值仍然是“带宽”,而不仅仅是“内存带宽”。)STREAM Copy和Scale内核仅加载一个阵列(并存储一个阵列),因此,如果存储绕过缓存,则每次迭代中通过缓存的总流量就是一个数组的大小。高速缓存寻址和索引有时非常复杂,并且高速缓存替换策略可能是动态的(伪随机或基于运行时利用率指标)。作为大小和准确性之间的折衷,我选择4x作为相对于高速缓存大小的最小数组大小,以确保 most 系统具有极低的高速缓存命中率(即,足够低以至于可以忽略不计)有关报告的效果)。
  • STREAM中的数据流量计数不会“归功于”硬件所做的额外传输,但是并未明确请求。这主要是指“写分配”流量-大多数系统在存储器可以更新相应的缓存行之前从存储器中 read 每个存储器的目标地址。许多系统都有能力跳过此“写分配”,方法是在高速缓存中分配一行而不读取它(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/ 中介绍
  • 对于单核情况,未完成的L1数据高速缓存未命中的数量太少而无法获得完整的带宽-对于您的Xeon可扩展处理器,每个插槽大约需要140个并发高速缓存未命中,但是单个核只能支持10-12 L1数据高速缓存未命中。 L2硬件预取器可以生成更多的内存并发性(如果我没有记错的话,每个内核最多可以发生约24个高速缓存未命中),但是要达到该范围上限附近的平均值,则需要同时访问更多4KiB页。额外的阵列读取使L2硬件预取器有更多机会生成(接近)并发内存访问的最大数量。增加11%-12%是完全合理的。
  • 使用所有内核时,增加读取比例也有望提高性能。在这种情况下,好处主要是减少了DDR4 DRAM接口上“读写周转停顿”的数量。如果根本没有存储,则此处理器上的持续带宽应达到90%的峰值(每个插槽使用16个或更多核)。

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

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

答案 1 :(得分:2)

正如Bandwidth博士的答案所指出的那样,关键是STREAMS仅计算源代码中可见的有用带宽。 (他是基准测试的作者。)

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

除非您的编译器使用绕过缓存的NT存储自动向量化并避免了RFO。一些编译器会针对循环执行此操作,因为他们希望在缓存中的任何一个都写之前写一个更大的数组重新阅读。)

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


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

答案 2 :(得分: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博士在实践中发现的。