在实现无锁数据结构和时序代码时,通常需要抑制编译器的优化。通常人们使用asm volatile
并使用memory
在封面列表中执行此操作,但有时您只会看到asm volatile
或只看到asm
简单的内存。
这些不同的陈述对代码生成有什么影响(特别是在GCC中,因为它不太可能是可移植的)?
仅供参考,这些是有趣的变化:
asm (""); // presumably this has no effect on code generation
asm volatile ("");
asm ("" ::: "memory");
asm volatile ("" ::: "memory");
答案 0 :(得分:52)
请参阅"Extended Asm" page in the GCC documentation。
您可以在
asm
之后写一个关键字volatile
来阻止asm
指令被删除。 [...]volatile
关键字表示该指令具有重要的副作用。如果可以访问,GCC将不会删除volatile
asm。
和
没有任何输出操作数的
asm
指令将被视为与易失性asm
指令完全相同。
您的示例都没有指定输出操作数,因此asm
和asm volatile
表单的行为相同:它们在代码中创建一个可能不会被删除的点(除非它被证明无法访问)
这与什么都不做完全不一样。有关更改代码生成的虚拟asm
的示例,请参阅this question - 在该示例中,循环1000次循环的代码被矢量化为代码,该代码一次计算循环的16次迭代;但是循环中存在asm
会抑制优化(asm
必须达到1000次)。
"memory"
clobber使GCC认为asm
块可以任意读取或写入任何内存,因此会阻止编译器重新排序加载或存储:
这将导致GCC不在汇编器指令的寄存器中保持缓存的内存值,也不会优化存储器或加载到该存储器。
(但这并不妨碍CPU对另一个CPU重新排序加载和存储;你需要真正的内存屏障指令。)
答案 1 :(得分:7)
asm ("")
什么都不做(或者至少,它不应该做任何事情。
asm volatile ("")
也什么也没做。
asm ("" ::: "memory")
是一个简单的编译器围栏。
asm volatile ("" ::: "memory")
AFAIK与之前相同。 volatile
关键字告诉编译器不允许移动此程序集块。例如,如果编译器确定每次调用中的输入值相同,则可以将其从循环中提升。我不确定编译器在什么条件下会决定对程序集有足够的了解以尝试优化其位置,但volatile
关键字完全抑制了这一点。也就是说,如果编译器试图移动没有声明输入或输出的asm
语句,我会感到非常惊讶。
顺便说一句,volatile
还会阻止编译器在确定输出值未使用时删除该表达式。只有在存在输出值时才会发生这种情况,因此它不适用于asm ("" ::: "memory")
。
答案 2 :(得分:1)
为了完整解决Kevin Ballard的问题,Visual Studio 2010提供了_ReadBarrier(),_ _ WriteBarrier()和_ReadWriteBarrier()来做同样的事情(VS2010不允许64位应用程序的内联汇编)。
这些不会生成任何指令,但会影响编译器的行为。一个很好的例子是here
MemoryBarrier()生成lock or DWORD PTR [rsp], 0