Warning: file_get_contents(/data/phpspider/zhask/data//catemap/6/cplusplus/151.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C++ CLWB(缓存线写回)到同一位置时性能较低,而不是在几行之间循环_C++_Performance_Memory_X86_Intel - Fatal编程技术网

C++ CLWB(缓存线写回)到同一位置时性能较低,而不是在几行之间循环

C++ CLWB(缓存线写回)到同一位置时性能较低,而不是在几行之间循环,c++,performance,memory,x86,intel,C++,Performance,Memory,X86,Intel,为什么当我增加kNumCacheLines时,下面代码的运行时间会减少 在每次迭代中,代码修改一条kNumCacheLines缓存线,用clwb指令将缓存线写入DIMM,并阻塞,直到存储器用sfence命中内存控制器。此示例需要Intel Skylake server或更新的Xeon或IceLake客户端处理器 #include <stdlib.h> #include <stdint.h> #define clwb(addr) \ asm volatile(".by

为什么当我增加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;
}
直观地说,我希望
kNumCacheLines=1
能够提供最佳性能,因为内存控制器的写挂起队列中有命中。但是,它是最慢的

作为非直观减速的一种解释,内存控制器在完成对缓存线的写入时,可能会阻止对同一缓存线的其他写入。我怀疑增加
kNumCacheLines
会提高性能,因为内存控制器具有更高的并行性。当
kNumCacheLines
从4秒增加到5秒时,运行时间从1.82秒增加到1.00秒。这似乎与内存控制器的写挂起队列有一个线程256字节的空间这一事实有关[,第5.3节]


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

这可能是由-充分解释的,结果表明SKX运行
clwb
clflushopt
相同,即它是一个用于前向兼容性的存根实现,因此持久内存软件可以在不检查CPU功能级别的情况下开始使用它

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

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

我们原以为这些存储可以提交到已经处于独占状态的行中,但事实上它们将无效,因为逐出可能仍在缓存层次结构中,具体取决于
sfence
暂停的时间和时间

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

尽管Skylake(-X)有12条LFB(即可以跟踪12条飞行中缓存线的传入或传出),但使用更多缓存线似乎只能获得2倍的加速。也许这与《科学》有关


从4跳到5是令人惊讶的。(基本上是两个级别的性能,而不是连续的过渡)。这为这样一种假设提供了一定的依据,即这与我们在尝试重新加载之前,商店已经完成了所有的DRAM,而不是有多个RFO在飞行有关。或者至少让人怀疑这只是RFO的MLPCLWB强制驱逐很明显是关键,但具体发生了什么以及为什么会有任何加速,我只是纯粹的猜测。

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


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

注释不用于扩展讨论;这段对话已经结束。我不认为“注意,因为buf小于4KB,所以所有访问都使用相同的DIMM。”是正确的。你是如何得出这个结论的?从物理地址到DRAM地址的映射不是线性的:在许多硬件上,交替的缓存线连接到交替的DIMM,在其他硬件上,粒度是256字节,等等。确定系统上的映射本身就是一个实验。通过拉出其他DIMM,您可以确保所有东西都将进入一个DIMM:)。尝试禁用所有硬件预取器,看看结果是否有任何不同。@HadiBrais:这是一个8行缓冲区上的10000000迭代重复循环。与重新接触clwb上一次迭代(或8次迭代前)收回的同一行相比,缓存中是否所有8行都处于热状态是一个无关紧要的启动细节。我并不认为这是一种常见的MLP类型效果。为什么两条线的速度不是原来的2倍?最快的时间是100纳秒,一个完整的DRAM未命中,与任何MLP都不一致。基本上,所有的加速都发生在4到5之间,之后为零(实际上为负),之前几乎没有。OP声称,
sfence
在某种意义上等待
clwb
完成,这可能是真的:英特尔的文档似乎暗示了这一点,至少在持久内存的情况下是如此。这将排除大多数类型的MLP。我想它可以通过保持LFB直到写操作完成并且状态被发送回核心来实现,从而释放LFB(这就是为什么服务器NT存储速度如此之慢的理论)。我改变主意了,我认为你的答案是正确的。然而,还有一个谜题缺失,RFO并行是如何发生的?我们是否确实知道RFO可以在一个商店的栅栏上并行发生?另一种可能的解释是L2预取器正在预取处于E状态的其他行。即使一次只有一个请求RFO,它们也可能以这种方式命中二级缓存。我想我们可以查一下
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