asm,asm易失性和破坏性内存之间的区别


75

在实现无锁数据结构和时序代码时,通常有必要抑制编译器的优化。通常情况下,人们会在“障碍物”列表中使用asm volatilewith进行此操作memory,但有时您会看到只是asm volatile或仅有一个简单的asm障碍物记忆。

这些不同的语句对代码生成有什么影响(尤其是在GCC中,因为它不太可能移植)?

仅供参考,以下是一些有趣的变化:


1
似乎有人在太接近金属的地方乱糟糟了:-)(在其他地方,@ Mysticial键入了一个荒谬的详细答案……)
Kerrek SB 2013年

Answers:


66

请参阅GCC文档中“扩展Asm”页面

您可以asm通过在volatile后面加上关键字来防止指令被删除asm。[...]volatile关键字表示该指令具有重要的副作用。volatile如果可以访问,GCC不会删除一个asm。

asm没有任何输出操作数的指令将与易失性asm指令相同。

您的示例均未指定输出操作数,因此asmandasm volatile表单的行为相同:它们在代码中创建了一个点,该点不可删除(除非被证明是不可访问的)。

这与不执行操作完全不同。有关更改代码生成的虚拟对象的示例,请参见此问题asm-在该示例中,将循环执行1000次的代码向量化为可立即计算循环16次迭代的代码;但是asm循环内部的存在会抑制优化(asm必须达到1000次)。

所述"memory"撞使得GCC假定任何存储器可以被任意地读取或写入asm块,所以会防止编译器通过它重新排序加载或存储:

这将导致GCC无法在汇编程序指令中将内存值缓存在寄存器中,也不会优化对该内存的存储或加载。

(不过,这不会阻止CPU对另一个CPU的装载和存储进行重新排序;为此,您需要使用真正的内存屏障指令。)


这实际上是非常有趣的,没有意识到gcc将asm没有输出的块视为易变是我所知的巨大空白。
jleahy

所以volatile=性能杀手,无论在什么上下文中使用它(变量或asm)。goto使用关键字归档-仅在绝对必要时使用。
以太网

“任何内存”是指内存中的任何对象?
curiousguy18年

3
"memory"撞仅经由任何指针输入到适用于全局可到达的存储器,或记忆可达asm语句。至于哪些C对象必须在内存中“同步”,哪些仍然可以在寄存器中,这就像一个非内联函数调用。因此,由于转义分析,从未将其地址传递到函数外部的本地变量(例如循环计数器)通常仍可以保留在寄存器中。
彼得·科德斯

1
这种优化是安全的,因为它尚不安全/不允许执行某些操作,例如asm("incl -16(%%rbp)" ::: "memory")访问gcc恰巧放置本地变量的堆栈空间(不使用"+m"操作数来使编译器生成寻址模式)。堆栈框架布局不是您可以做的任何假设;不同的编译器选项将对其进行更改。因此,无论如何,"memory"垃圾服务器会按照这个答案说的去做,但是性能下降并没有那么糟糕。
彼得·科德斯

12

asm ("") 什么都不做(或者至少不应该做任何事。

asm volatile ("") 也什么也没做。

asm ("" ::: "memory") 是一个简单的编译器栅栏。

asm volatile ("" ::: "memory")AFAIK与之前的相同。该volatile关键字告诉编译器,它不允许移动该组装块。例如,如果编译器确定每个调用中的输入值都相同,则可以将其吊起。我不确定在什么条件下编译器会决定对程序集了解得足够多以尝试优化其位置,但是volatile关键字完全抑制了这一点。就是说,如果编译器尝试移动asm没有声明的输入或输出的语句,我会感到非常惊讶。

顺便说一句,volatile如果编译器确定未使用输出值,还可以防止其删除表达式。不过,只有在有输出值的情况下才会发生这种情况,因此不适用于asm ("" ::: "memory")


11
Matthew Slattery的答案指出,这asm volatile ("")与不执行操作并不完全相同,因为它会对编译器优化产生巨大影响。同样的性能含义也适用于asm volatile ("" ::: "memory")用作编译器防护。
以太网

编译器不懂汇编语言!
curiousguy 2015年

3
@curiousguy否,但是它确实了解asm块何时声明了输入/输出,从而告诉编译器它依赖于哪个寄存器以及它将修改哪个寄存器,因此,如果编译器不影响某些计算,则它们可以将其周围的某些计算重新排序/输出。
莉莉·巴拉德

1
至少对于GCC,asm volatile禁止通用指令重新排序,它仅防止asm由于(明显)缺乏有意义的副作用而删除可访问的块(在较新的GCC上,即使将其从循环中吊起,编译器确定输入始终相同)。否则,仅通过声明的输入和输出(和伪输出,如"memory")禁止指令重新排序。在文档中阅读更多内容。
ShadowRanger

是的,如果asm语句volatile没有输出约束,则它们是隐式的。(gcc.gnu.org/onlinedocs/gcc/Extended-Asm.html)。所以asm("":::"memory")正好相同asm volatile("":::"memory")。如果从不使用结果,则可以删除非易失性asm语句,如果反复使用相同的输入运行,则可以将其提升(或以其他方式生成CSE d)。因此,您需要显式volatile地包装类似之类的内容,rdtsc或者rdrand因为您确实获得了具有相同(空)输入集的不同输出。
彼得·科德斯

3

只是为了完整性上百合巴拉德的回答,Visual Studio 2010的报价_ReadBarrier()_WriteBarrier()_ReadWriteBarrier()做相同的(VS2010不允许内联汇编的64位应用程序)。

这些不会生成任何指令,但会影响编译器的行为。一个很好的例子在这里

MemoryBarrier() 产生 lock or DWORD PTR [rsp], 0

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.