与缓存关联性有关的缓存颠簸的不直观的性能结果

时间:2018-10-15 10:33:46

标签: c++ performance caching x86 cpu-cache

我试图通过以等于2的幂的步幅访问内存来演示Intel CPU上的缓存关联性。 我以列方式(在C ++中)访问一个双精度的K x N矩阵,并期望对于K> 8,如果N是2的幂,性能将会下降(因为我使用的是8路CPU设置关联的L1缓存)。因此,我绘制了K = 1..40,N = 2 ^ 20,N = 2 ^ 20 + 8和N = 2 ^ 20 + 64的内存访问性能,从而填充了0、1和8个缓存行分别。

#include <cstdio>
#include <cstdlib>
#include <chrono>

double buffer[((1 << 20) + 64) * 40];

void measure_flops(int N, int K) {
    // Warm-up.
    for (int j = 0; j < 10; ++j)
        for (int i = 0; i < N * K; ++i)
            buffer[i] += 1e-10 * i + 0.123 * j;

    // Iterate.
    int repeat = 500 / K;
    auto start = std::chrono::steady_clock::now();
    for (int r = 0; r < repeat; ++r)
        for (int i = 0; i < N; ++i)
            for (int j = 0; j < K; ++j)
                buffer[j * N + i] += 1.0123;
    auto end = std::chrono::steady_clock::now();
    std::chrono::duration<double> diff = end - start;

    volatile double tmp = buffer[rand() % (N * K)]; (void)tmp;

    // Report.
    double flops = (double)repeat * N * K / diff.count();
    printf("%d  %2d  %.4lf\n", N, K, flops * 1e-9);
    fflush(stdout);
}

void run(int N) {
    printf("      N   K  GFLOPS\n");
    for (int K = 1; K <= 40; ++K)
        measure_flops(N, K);
    printf("\n\n");
}

int main() {
    const int N = 1 << 20;

    run(N);
    run(N + 64 / sizeof(double));
    run(N + 512 / sizeof(double));

    return 0;
}

我在以下CPU上运行基准测试:

  • Intel Xeon E5-2680 v3(Haswell,L1,L2和L3的关联度分别为8、8、20)
  • 英特尔至强E5-2670 v3(Haswell,关联性为8、8、20)
  • Intel Xeon Gold 6150(Skylake,关联度为8、16、11)

...结果不一致,某些形状我不明白。

enter image description here

我的问题是:

1)为什么对于E5-2680,对于padding = 64 B,性能不能恢复,如果对于E5-2670,性能不能恢复?对于padding = 512 B,性能会恢复,但是为什么呢? (预取器是否有可能破坏缓存?)

编辑:在另一个群集上也在E5-2690和E5-2650上进行了测试。 E5-2690表现为E5-2680(填充= 64 B时性能不佳),而E5-2650表现为E5-2670(性能良好)。因此,似乎确实在2670和2680之间发生了一些变化。

2)为什么性能在K达到8之前这么早下降? (我怀疑这是“伪造的商店转发摊位”,如在here稍有不同的设置中所述,但我不确定。)

3)为什么为什么E5-2680的K =〜32和Gold 6150的K =〜35再次降低性能? (perf stat -ddd显示在那里增加了L3缓存未命中的数量,但是我不知道为什么的确切原因,因为K与L3关联性不匹配。)


设置详细信息:

E5-2680和Gold 6150都在群集上运行,在该群集中,我采用一个完整的节点来确保不会干扰其他用户。在这两种情况下,一个节点都包含2个CPU,但无论如何我都只使用一个内核。 E5-2670是在单CPU共享计算机上(无法获得完整节点),但是在非活动状态下运行了基准测试。

我用g++ -O1 -std=c++11(E5-2680的版本6.3.0和Gold,E5-2670的版本7.3.0)编译了代码。所有结果都是可重复的。更改编译器(或例如向-O3添加volatile或向buffer添加{{1}})只会或多或少地导致低K的结果,例如K = 2变快/变慢。

0 个答案:

没有答案