C++ 指针追踪基准:读取+;写入(CLFLUSH)比读取(CLFLUSH)快

C++ 指针追踪基准:读取+;写入(CLFLUSH)比读取(CLFLUSH)快,c++,performance,caching,cpu,C++,Performance,Caching,Cpu,我试图理解使用CLFLUSH对性能的影响。为此,我编写了一个小型指针跟踪基准测试。我取一个std::vector,其中第一个元素是下一个条目的偏移量,第二个元素是有效负载。我从条目0转到下一个条目,依此类推,直到到达开头。在路上,我计算所有有效载荷的总和 另外,我有两个参数:如果write==1,则在读取有效负载后修改它(从而使缓存线无效)。如果clflush==1,则在转到下一个元素之前执行clflush 向量的大小等于一级缓存的大小(32 KiB) 以下是我的结果: write clfl

我试图理解使用CLFLUSH对性能的影响。为此,我编写了一个小型指针跟踪基准测试。我取一个
std::vector
,其中第一个元素是下一个条目的偏移量,第二个元素是有效负载。我从条目0转到下一个条目,依此类推,直到到达开头。在路上,我计算所有有效载荷的总和

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

向量的大小等于一级缓存的大小(32 KiB)

以下是我的结果:

write   clflush runtime
0       0       5324060
0       1       298751237
1       0       4366570
1       1       180303091
我确实理解为什么使用clflush的运行比不使用clflush的运行慢。但是为什么读写比写快,为什么脏缓存线比干净缓存线看起来更快


作为参考,您可以找到我的基准测试,我使用
g++-4.8-std=c++11-lrt-O3编译了它
这可能不是一个答案,但我认为您看到的效果不是真实的。以下是我在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
他们之间有很多不同,但没有一个和你看到的相符。我还在一个沙桥E5-1620上运行,发现了与这些类似的结果(与您的结果不匹配),尽管那台机器上较旧版本的clang++没有在无写无刷新的情况下崩溃

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

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

我试图查看生成的程序集(objdump-d-C chase),但在获取方向时遇到了问题。所有内容都直接内联到main中,因此它不像查看chase()函数来查看发生了什么那样简单。使用-g编译(用于调试)并将-S(用于源代码)添加到objdump命令有帮助,但仍然很复杂。我试图阻止编译器内联失败

如果是我,我会切换到C并使用-fno内联函数编译,然后检查是否仍然得到相同的效果。然后剖析chase()函数,直到了解发生了什么。然后使用gcc-S输出程序集,修改它,直到其顺序正确,然后查看效果是否仍然存在

还值得注意的是,根据《英特尔体系结构参考手册》,clflush不是串行化指令。即使程序集是按照您认为应该的顺序进行的,处理器也可以公平地执行前后的指令。考虑到你追逐的方式,我认为窗口不够宽,不足以成为一个因素,但谁知道呢。可以通过添加mfence来强制序列化

另一种可能是clflush在特定处理器上的行为异常。您可以切换使用“wbinvd”使所有缓存无效的核心选项。这是一条很难执行的指令,因为它是“私有的”,需要由内核执行。你必须写一个ioctl来做这件事


祝你好运

嗨,内森,谢谢你详细的回答,很抱歉我的回复迟了。重新排序的CLFLUSH是一个重要的问题-我错过了一个SFENCE。尽管如此,理解性能影响仍然相当困难。对于我们的项目,我们已经切换到不同的基准,以实现我们所需要的。