为什么Skylake在单线程内存吞吐量方面比Broadwell-E好得多?

18
我们有一个简单的内存吞吐量基准测试,它只是重复执行大块内存的memcpy。
在几台不同的机器上(编译为64位),在保持操作系统(Win10-64)、处理器速度和RAM速度(DDR4-2133)相同时,Skylake机器的表现比Broadwell-E好得多。我们不是谈论一些百分点,而是约为2倍的因素。Skylake配置双通道,而对于双/三/四通道,Broadwell-E的结果并没有变化。
任何想法为什么会发生这种情况?随后的代码在VS2015 Release中编译,并报告每个memcpy完成的平均时间为:
64位:Skylake为2.2ms,Broadwell-E为4.5ms;
32位:Skylake为2.2ms,Broadwell-E为3.5ms。
我们可以通过利用多个线程在四通道Broadwell-E构建中获得更大的内存吞吐量,这很好,但是看到单线程内存访问如此明显的差异是令人沮丧的。有什么想法为什么差异如此明显?
我们还使用了各种基准测试软件,它们验证了这个简单例子所展示的 - 在Skylake上,单线程内存吞吐量要好得多。
#include <memory>
#include <Windows.h>
#include <iostream>

//Prevent the memcpy from being optimized out of the for loop
_declspec(noinline) void MemoryCopy(void *destinationMemoryBlock, void *sourceMemoryBlock, size_t size)
{
    memcpy(destinationMemoryBlock, sourceMemoryBlock, size);
}

int main()
{
    const int SIZE_OF_BLOCKS = 25000000;
    const int NUMBER_ITERATIONS = 100;
    void* sourceMemoryBlock = malloc(SIZE_OF_BLOCKS);
    void* destinationMemoryBlock = malloc(SIZE_OF_BLOCKS);
    LARGE_INTEGER Frequency;
    QueryPerformanceFrequency(&Frequency);
    while (true)
    {
        LONGLONG total = 0;
        LONGLONG max = 0;
        LARGE_INTEGER StartingTime, EndingTime, ElapsedMicroseconds;
        for (int i = 0; i < NUMBER_ITERATIONS; ++i)
        {
            QueryPerformanceCounter(&StartingTime);
            MemoryCopy(destinationMemoryBlock, sourceMemoryBlock, SIZE_OF_BLOCKS);
            QueryPerformanceCounter(&EndingTime);
            ElapsedMicroseconds.QuadPart = EndingTime.QuadPart - StartingTime.QuadPart;
            ElapsedMicroseconds.QuadPart *= 1000000;
            ElapsedMicroseconds.QuadPart /= Frequency.QuadPart;
            total += ElapsedMicroseconds.QuadPart;
            max = max(ElapsedMicroseconds.QuadPart, max);
        }
        std::cout << "Average is " << total*1.0 / NUMBER_ITERATIONS / 1000.0 << "ms" << std::endl;
        std::cout << "Max is " << max / 1000.0 << "ms" << std::endl;
    }
    getchar();
}

1
将23.8MiB复制所需的时间为2.2ms,每个读写操作的混合读写速度约为10.6GiB/s。英特尔表示Skylake i5-6600(以及使用DDR4-2133的其他SKL型号)的理论最大内存带宽为34.1 GB/s(或31.8 GiB/s)。因此,即使每次加载和存储都未命中L3并且必须访问主内存,也仅占理论最大值的2/3。虽然这可能对于单个线程来说是正常的。 - Peter Cordes
3
在启用内置函数的 MSVC 上,对于编译时常量缓冲区长度,调用 memcpy 将被内联。否则,在 64 位情况下,它将生成对库函数的调用,该库函数本身调用 RtlCopyMemory API 函数。这就是您的情况中会发生的事情,因为您已经阻止了 memcpy 调用的内联。不,它不会进行任何花哨的分派,只进行一些健全性检查和 rep movs。 - Cody Gray
1
好吧,我必须稍微修改一下上次的评论。从反汇编的角度来看,该函数的64位版本似乎使用SSE2指令,除非内存未对齐,否则会退回到rep movsb以处理尾部/结尾的未对齐字节。但是,两个处理器上都运行相同的代码(没有动态调度),因此实现不是性能差异的因素。 - Cody Gray
1
已编辑上方以指示收集编译为64位的度量标准。我实际测试了约3台Haswell/Broadwell-E和3台Skylake机器,每台Skylake机器在这个指标上都比Haswell/Broadwell-E更强。我的Broadwell-E系统不是NUMA。BIOS中的CPU配置没有被调整(已验证硬件预取和相邻缓存行预取均已启用)。我将查看两个系统类别的TLB/L3缓存未命中情况。 - aggieNick02
1
@PeterCordes i7-6800K,拥有6个核心/12个线程,在默认频率下为3.4 GHz。 - aggieNick02
显示剩余8条评论
2个回答

18
现代CPU的单线程内存带宽受限于从L1D到系统其余部分的传输的最大并发数/延迟,而不是DRAM控制器瓶颈。每个核心有10个Line-Fill Buffers(LFB),用于跟踪与L1D的未完成请求。(以及16个“超级队列”条目,用于跟踪与L2的行)。(更新:实验表明Skylake可能具有12个LFB,而不是Broadwell的10个。例如the ZombieLoad paper中的Fig7,以及其他性能实验,包括@BeeOnRope's testing of multiple store streams
Intel的多核芯片相比四核或双核台式机/笔记本电脑芯片,与L3 /内存的延迟更高,因此单线程内存带宽实际上要差得多。尽管使用多个线程的最大聚合带宽要好得多。在连接核心、内存控制器和系统代理(PCIe等)的环形总线上,它们有更多的跳数。
SKX(Skylake-server / AVX512,包括i9“高端桌面”芯片)对此非常糟糕:L3 /内存延迟明显高于Broadwell-E / Broadwell-EP,因此单线程带宽甚至比具有类似核心数量的Broadwell还要差。(SKX使用网格而不是环形总线,因为这样更容易扩展,请参阅此处了解详细信息。但显然新设计的常数很差;也许未来的一些代将会有更好的L3带宽/延迟,适用于小/中型核心计数。虽然每个核心的私有L2被提升到1MiB,所以也许L3故意缓慢以节省功率。)
(像问题中的Skylake-client(SKL)以及后来的四核/六核台式机/笔记本电脑芯片,如Kaby Lake和Coffee Lake,仍使用较简单的环形总线布局。只有服务器芯片发生了变化。我们还不确定Ice Lake客户端将采取什么措施。)

一个四核或双核芯片只需要几个线程(特别是如果内核和非内核(L3)时钟高)就可以饱和其内存带宽,而具有快速DDR4双通道的Skylake具有相当多的带宽。

关于这一点的更多信息,请参见此答案中有关x86内存带宽的延迟限制平台部分。 (还可以阅读其他部分,了解使用SIMD循环的memcpy / memset与rep movs / rep stos,NT存储与常规RFO存储等内容)。

还有相关内容:每个程序员都应该知道的内存知识?(2017年更新的关于这篇优秀文章的仍然正确和已经改变的内容)。


1
是的,但OP不是说他在Skylake上看到了更高的带宽/更低的延迟吗? - Stephan Dollberg
1
@inf: 没错。他们有一款四核心的Skylake客户端芯片,但是有一款多核心的Broadwell-E。SKL仍然使用简单快速的环形总线;只有SKX转向了更慢但更可扩展的网状网络。 - Peter Cordes
1
@inf:无论如何,感谢您的反馈,我没有意识到可能会引起的混淆。已经进行了编辑以澄清。 - Peter Cordes
1
Xeon CPU将核心与非核心频率分开,因此需要异步缓冲,除了额外的环形跳跃之外,还会增加更多的延迟。四核“客户端”芯片将所有核心(和非核心)锁定在相同的频率上;它们无法独立扩展。这是(我认为)使非核心延迟显着降低的部分原因。SKX的网状结构显然具有更高的延迟,或者由于某种原因具有更差的单核带宽,但即使其环形总线不是很大,任何Xeon也都是一种不同的动物,与客户端芯片不同。(除了基于客户端硅的四核工作站Xeon。) - Peter Cordes
1
我认为客户端的SKL芯片有单独的uncore时钟。更多的ring stops似乎无法解释内存延迟的差异,除非您需要多次完整的往返?如果我没记错,每个停止需要一个uncore周期。服务器芯片(即使在SKX之前)也有更多的NT存储器,因此我想设计上存在一些重要的差异。也许预取器都被调整得不同。 - BeeOnRope
显示剩余11条评论

3
我终于成功使用VTune(评估版)。在Broadwell-E上,它给出了一个DRAM限制得分为0.602(介于0和1之间),而在Skylake上则是0.324。Broadwell-E的延迟巨大部分来自内存延迟。考虑到内存条速度相同(除了Skylake中的双通道配置和Broadwell-E中的四通道配置),我的最佳猜测是Skylake的内存控制器要好得多。
这使得购买Broadwell-E架构变得更加困难,并且需要确实需要额外的核心才能考虑它。
我还获得了L3/TLB缺失计数。在Broadwell-E上,TLB缺失计数约高20%,而L3缺失计数约高36%。
我认为这不是“为什么”的真正答案,因此我不会将其标记为答案,但这是我目前能够接受的最接近答案的解释了。感谢一路上所有有用的评论。

即使是同一架构的芯片,如Haswell vs Haswell,客户端和服务器芯片之间的延迟差异始终存在。 Skylake在内存延迟方面没有取得大幅度进展,IMC中也没有这样的魔法。 - BeeOnRope
2
@BeeOnRope “客户端”和“服务器”吞吐量之间的差异可以部分地通过客户端系统较低(指针追踪)内存负载延迟来解释。对于memcpy操作,“大”复制应使用流式存储。在大多数英特尔服务器处理器的世代中,流式存储的占用率高于等效的客户端处理器。这类似于负载延迟,但更受一致性的限制。例如,在SKX中,内存目录减少了负载延迟,但没有减少流式存储的占用率。 - John D McCalpin
感谢McCalpin博士。有一件事我不太清楚:在单插槽系统上,负载-负载延迟和/或NT存储器占用是否在服务器芯片上更糟糕?如果是(我的回忆是肯定的),为什么? - BeeOnRope

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