我试图了解使用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
进行编译。
答案 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才能做到这一点。
祝你好运!