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

Why is Skylake so much better than Broadwell-E for single-threaded memory throughput?

我们有一个简单的内存吞吐量基准测试。它所做的只是为一大块内存重复 memcpy。

查看几台不同机器上的结果(针对 64 位编译),Skylake 机器明显优于 Broadwell-E,保持 OS (Win10-64)、处理器速度和 RAM 速度(DDR4-2133) 相同。我们说的不是几个百分点,而是大约 2 的一个因数。 Skylake 配置为双通道,Broadwell-E 的结果在 dual/triple/quad-channel.

上没有变化

知道为什么会这样吗?下面的代码在 VS2015 的 Release 中编译,并报告完成每个 memcpy 的平均时间:

64 位:Skylake 为 2.2 毫秒,Broadwell-E 为 4.5 毫秒

32 位:Skylake 为 2.2 毫秒,Broadwell-E 为 3.5 毫秒

我们可以通过利用多线程在四通道 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();
}

我终于搞定了 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%。

我不认为这真的是 "why" 的答案,所以我不会这样标记它,但我认为我暂时会得到一个。感谢一路上所有有用的评论。

单线程 现代 CPU 上的内存带宽受到从 L1D 到系统其余部分的传输的 max_concurrency / latency 限制,而不是 DRAM 控制器瓶颈。每个内核都有 10 个行填充缓冲区 (LFB),用于跟踪未完成的请求 to/from L1D。 (还有 16 个 "superqueue" 条目跟踪行 to/from L2)。

(更新:实验表明 Skylake 可能有 12 个 LFB,高于 Broadwell 中的 10 个。例如 the ZombieLoad paper, and other performance experiments including @BeeOnRope's testing of multiple store streams 中的 Fig7)


英特尔的多核芯片比四核或双核台式机/笔记本电脑芯片对 L3/内存的延迟更高,因此 单线程 内存在大型 Xeon 上,带宽实际上要差得多 ,即使多线程的最大总带宽要好得多。它们在连接核心、内存控制器和系统代理(PCIe 等)的环形总线上有更多的跃点。

SKX(Skylake-server / AVX512,包括 i9 "high-end desktop" 芯片)对此非常不利:L3/内存延迟明显高于 Broadwell-E / Broadwell-EP,因此单线程带宽甚至比具有类似核心数的 Broadwell 还要差。 (SKX 使用网格而不是环形总线,因为它的扩展性更好,see this for details on both。但显然新设计中的常数因子很糟糕;也许后代会有更好的 L3 bandwidth/latency 用于中小型内核计数。私有的每核 L2 增加到 1MiB,所以 L3 可能故意放慢以节省电量。)

(问题中的Skylake-client (SKL),以及后来的quad/hex-core desktop/laptop芯片如Kaby Lake和Coffee Lake,仍然使用更简单的ring-bus布局。只有服务器芯片改变了。我们还不确定 Ice Lake 客户端会做什么。)


四核或双核芯片只需要几个线程(特别是如果核心+非核心(L3)时钟频率很高)来饱和其内存带宽,而具有快速 DDR4 双通道的 Skylake 具有相当多的带宽.

有关此的更多信息,请参阅 关于 x86 内存带宽的延迟绑定平台部分。 (并阅读 memcpy/memset SIMD 循环与 rep movs/rep stos 以及 NT 存储与常规 RFO 存储等的其他部分。)

也相关:What Every Programmer Should Know About Memory?(2017 年关于那篇优秀文章自 2007 年以来仍然正确的内容和更改内容的更新)。