【发布时间】:2016-11-10 02:08:42
【问题描述】:
Chandler Carruth 在他的CppCon2015 talk 中引入了两个函数,可用于对优化器进行一些细粒度的抑制。它们对于编写优化器不会简单地陷入无意义的微基准非常有用。
void clobber() {
asm volatile("" : : : "memory");
}
void escape(void* p) {
asm volatile("" : : "g"(p) : "memory");
}
这些使用内联汇编语句来改变优化器的假设。
clobber 中的汇编语句声明其中的汇编代码可以读写内存中的任何位置。实际的汇编代码是空的,但优化器不会查看它,因为它是asm volatile。当我们告诉它代码可以在内存中的任何地方读写时,它就会相信它。这有效地防止优化器在调用 clobber 之前重新排序或丢弃内存写入,并在调用 clobber† 之后强制读取内存。
escape 中的那个还使指针p 对汇编块可见。同样,因为优化器不会查看代码可能为空的实际内联汇编代码,并且优化器仍将假定该块使用指针p 指向的地址。这有效地强制 p 指向的任何内容在内存中而不是在寄存器中,因为汇编块可能会从该地址执行读取。
(这很重要,因为clobber 函数不会强制对编译器决定放入寄存器的任何内容进行读取或写入,因为clobber 中的汇编语句并没有说明必须有任何特别的内容对程序集可见。)
所有这些都是在没有任何额外代码直接由这些“障碍”生成的情况下发生的。它们是纯粹的编译时工件。
不过,它们使用 GCC 和 Clang 中支持的语言扩展。有没有办法在使用 MSVC 时有类似的行为?
† 要理解为什么优化器必须这样想,想象一下汇编块是否是一个循环,将内存中的每个字节加 1。
【问题讨论】:
-
看起来like
_ReadWriteBarrier可能是clobber的答案。我不知道escape。也许_ReadWriteBarrier加上将指针传递给一些外部定义的函数。 -
哦,我忘了提到它们的另一个特性:它们不会生成任何代码。优化器完成后,它们的任何效果都会消失。在运行时之前没有任何问题。它们纯粹是编译时的。
-
就像@user786653 所说,
_ReadWriteBarrier(或者可能只是_ReadBarrier/_WriteBarrier,如果这就是所有需要的话)在MSVC 中的效果与clobber相同。对于escape,我分析汇编输出的经验是,如果你只标记变量volatile,MSVC 会做正确的事情。当然,这会产生一些运行时开销,因为生成的代码会始终在内存中保持变量的更新。这不是一个完美的解决方案,但我没有找到更好的解决方案。 -
@R.MartinhoFernandes ...只要您使用此机制进行基准测试,但不依赖它来防止线程代码的读/写迁移,我quiesce。
-
你研究过 std::atomic_signal_fence 和 atomic_thread_fence 吗?
标签: c++ visual-c++ optimization benchmarking