CLWB(缓存行回写)到同一位置与循环通过几行的性能低下

Anu*_*lia 5 c++ memory performance x86 intel

为什么当我增加时,下面代码的运行时间会减少kNumCacheLines

在每次迭代中,代码都会修改kNumCacheLines缓存行之一,使用clwb指令将该行写入 DIMM ,然后阻塞,直到存储到达内存控制器sfence。此示例需要 Intel Skylake-server 或更新的 Xeon 或 IceLake 客户端处理器。

#include <stdlib.h>
#include <stdint.h>

#define clwb(addr) \
  asm volatile(".byte 0x66; xsaveopt %0" : "+m"(*(volatile char *)(addr)));

static constexpr size_t kNumCacheLines = 1;

int main() {
  uint8_t *buf = new uint8_t[kNumCacheLines * 64];
  size_t data = 0;
  for (size_t i = 0; i < 10000000; i++) {
    size_t buf_offset = (i % kNumCacheLines) * 64;
    buf[buf_offset] = data++;
    clwb(&buf[buf_offset]);
    asm volatile("sfence" ::: "memory");
  }

  delete [] buf;
}
Run Code Online (Sandbox Code Playgroud)

(编者注:_mm_sfence()并且_mm_clwb(void*)会避免需要内联 asm,但是这个内联 asm 看起来是正确的,包括"memory"clobber)。

以下是我的 Skylake Xeon 机器上的一些性能数据,通过运行time ./bench不同的 值来报告kNumCacheLines

kNumCacheLines  Time (seconds)
1               2.00
2               2.14
3               1.74
4               1.82
5               1.00
6               1.17
7               1.04
8               1.06
Run Code Online (Sandbox Code Playgroud)

直观地说,我希望kNumCacheLines = 1由于内存控制器的写入挂起队列中的命中而提供最佳性能。但是,它是最慢的之一。

作为对非直观减速的解释,可能是当内存控制器完成对缓存线的写入时,它会阻止对同一缓存线的其他写入。我怀疑kNumCacheLines由于内存控制器可用的并行度更高,因此增加性能会增加。运行时间kNumCacheLines从 4 到 5时从 1.82 秒跳到 1.00 秒。这似乎与以下事实相关:内存控制器的写挂起队列具有来自线程的 256 字节空间 [ https://arxiv.org/pdf/1908.03583.pdf,第 5.3 节]。

请注意,由于buf小于 4 KB,因此所有访问都使用相同的 DIMM。(假设它已对齐,因此不会跨越页面边界)

Pet*_*des 1

这可能完全可以通过英特尔的 CLWB 指令使缓存线无效来解释-结果 SKX 的运行方式与 SKXclwb相同clflushopt,即它是一个用于向前兼容的存根实现,因此持久内存软件可以开始使用它而无需检查 CPU 功能级别。

更多的缓存行意味着在为下一个存储重新加载无效行时具有更多的内存级并行性。或者在我们尝试重新加载之前冲洗部分已完成。非此即彼; 有很多细节我没有具体的解释。

在每次迭代中,您将计数器值存储到缓存行中并对其进行 clwb。(和sfence)。该缓存行上的先前活动是kNumCacheLines迭代之前的。

我们期望这些存储可以只提交到已经处于独占状态的行中,但实际上它们将变得无效,驱逐可能仍在缓存层次结构中进行,具体取决于确切的停顿时间和停顿时间sfence

因此,每个存储都需要等待 RFO(读取所有权)将行以独占状态返回到缓存,然后才能从存储缓冲区提交到 L1d。

尽管 Skylake(-X) 有12 个 LFB(即可以跟踪传入或传出的 12 个运行中的缓存行),但使用更多缓存行似乎只能获得 2 倍的加速。也许sfence与此有关。


从 4 到 5 的大幅跃升令人惊讶。(基本上是两个级别的性能,而不是连续的过渡)。这为以下假设提供了一定的依据:这与我们尝试重新加载之前存储已将其一路存储到 DRAM 有关,而不是与多个正在运行的 RFO 有关。或者至少对“这只是 RFO 的 MLP”的想法产生怀疑。 CLWB 强制驱逐显然是关键,但具体发生了什么以及为什么会出现加速的具体细节纯粹是我的猜测。

如果有人想做的话,更详细的分析可能会告诉我们一些有关微架构细节的信息。希望这不是一个非常正常的访问模式,所以我们可能可以在大多数时候避免做这样的事情!

(可能相关:显然,对 Optane DC PM 内存同一行的重复写入比顺序写入慢,因此您也不希望在此类非易失性内存上使用直写式缓存或类似的访问模式。)