指针追逐基准:读取+写入(+ CLFLUSH)比读取速度快(+ CLFLUSH)

时间:2014-05-02 13:34:30

标签: c++ performance caching cpu

我试图了解使用CLFLUSH的性能影响。为此,我写了一个追逐基准的小指针。我取std::vector<std::pair<size_t, size_t>>,其中第一个元素是下一个条目的偏移量,第二个元素是有效载荷。我从第0项开始到下一个条目,依此类推,直到我到达开头。在我的路上,我计算所有有效载荷的总和。

另外,我有两个参数:如果是write==1,我会在读取后修改有效负载(从而使高速缓存行无效)。如果clflush==1,我会在转到下一个元素之前执行CLFLUSH

向量的大小等于L1高速缓存的大小(32 KiB)。

以下是我的结果:

write   clflush runtime
0       0       5324060
0       1       298751237
1       0       4366570
1       1       180303091

我明白为什么使用clflush的运行比没有运行慢。但是为什么读取和写入比写入更快?为什么它看起来比干净的高速缓存行CLFLUSH更快?

作为参考,您可以找到我的基准here,我使用g++-4.8 -std=c++11 -lrt -O3进行编译。

1 个答案:

答案 0 :(得分:1)

这可能不是一个答案,但我不认为你看到的效果是真实的。这是我在Haswell i7-4770上使用一些不同编译器运行测试程序时看到的内容:

nate@haswell:~/stack$ chase-g481-orig
write   clflush runtime
0   0   3238411
0   1   55916728
1   0   3220700
1   1   88299263
nate@haswell:~/stack$ chase-icpc-orig
write   clflush runtime
0   0   3226673
0   1   53840185
1   0   4858013
1   1   88143220
nate@haswell:~/stack$ chase-clang-orig
write   clflush runtime
0   0   13521595
0   1   54542441
1   0   3394006
1   1   88344640

它们之间存在很多差异,但没有与您所看到的相符。我还在Sandy Bridge E5-1620上运行并发现了类似的结果(与你的不匹配),尽管该机器上的旧版clang ++并没有在无写无冲洗情况下爆炸。

首先,您的程序尝试使用整个L1缓存有点尴尬。如果您完全控制了系统(启动时保留的CPU),这可能是合理的,但似乎可能会引入混淆效应。如果您的目标是了解此效果而不是查看缓存在满负荷运行时的行为,我建议您将总大小更改为缓存大小的1/2。

我认为最可能的解释是,不同的编译器将clflush提升到函数中的不同位置,而其中一些编译器并没有按照您的意图执行操作。当你在这个级别工作时,实际上说服编译器做你想做的事情是非常困难的。由于clflush内在实际上并没有改变结果,优化器规则通常会破坏你的意图。

我试着查看生成的程序集(objdump -d -C chase),并且无法获得我的方位。所有内容都直接内联到main中,因此不仅仅是查看chase()函数来查看正在发生的事情。使用-g(用于调试)进行编译并将-S(用于源代码)添加到objdump命令有所帮助,但仍然很复杂。我试图阻止编译器内联失败。

如果是我,我将切换到C并使用-fno-inline-functions进行编译,并检查是否仍然可以获得相同的效果。然后剖析chase()函数,直到你理解发生了什么。然后使用gcc -S输出组件,修改它直到它按正确的顺序排列,看看效果是否仍然存在。

值得注意的是,根据英特尔架构参考手册,clflush不是序列化指令。即使程序集符合您认为的顺序,处理器执行之前和之后的指令也是公平的。鉴于你追逐的方式,我不认为窗口足够宽,这是一个因素,但谁知道。您可以通过添加mfence来强制执行序列化。

另一种可能性是clflush在您的特定处理器上表现得很奇怪。您可以切换使用&#39; wbinvd&#39;使所有缓存无效。这是一项难以执行的指令,因为它是特权指令&#39;并且需要由内核执行。你必须写一个ioctl才能做到这一点。

祝你好运!