asm,asm volatile和clobbering memory之间的区别

jle*_*ahy 62 c gcc inline-assembly

在实现无锁数据结构和时序代码时,通常需要抑制编译器的优化.通常人们在clobber列表中使用asm volatilewith memory,但你有时会看到asm volatile或只是一个简单的asm破坏性记忆.

这些不同的陈述对代码生成有什么影响(特别是在GCC中,因为它不太可能是可移植的)?

仅供参考,这些是有趣的变化:

asm ("");   // presumably this has no effect on code generation
asm volatile ("");
asm ("" ::: "memory");
asm volatile ("" ::: "memory");
Run Code Online (Sandbox Code Playgroud)

Mat*_*ery 57

请参阅GCC文档中"Extended Asm"页面.

您可以asm通过在volatile后面写入关键字来阻止删除指令asm.[...] volatile关键字表示该指令具有重要的副作用.volatile如果可以访问,GCC将不会删除asm.

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

您的示例都没有指定输出操作数,因此asmasm volatile表单的行为相同:它们在代码中创建一个可能不会被删除的点(除非它被证明是无法访问的).

这与什么都不做完全不一样.请参阅此问题以获取asm更改代码生成的虚拟示例- 在该示例中,围绕循环1000次的代码被矢量化为代码,该代码一次计算循环的16次迭代; 但是asm循环内部的存在会抑制优化(asm必须达到1000次).

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

这将导致GCC不保持跨汇编指令缓存在寄存器中的内存值,而不是优化存储器或加载到该内存.

(但这并不妨碍CPU相对于另一个CPU重新排序加载和存储;您需要真正的内存屏障指令.)

  • “内存”破坏符仅适用于全局可访问的内存,或者可通过对“ asm”语句的任何指针输入访问的内存。至于哪些C对象必须在内存中“同步”,哪些仍然可以在寄存器中,这就像一个非内联函数调用。因此,由于[转义分析](https://en.wikipedia.org/wiki/Escape_analysis),从未将其地址传递到函数外部的本地变量(例如循环计数器)通常仍可以保留在寄存器中。 (3认同)

Lil*_*ard 7

asm ("") 什么都不做(或者至少,它不应该做任何事情.

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

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

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

顺便提一下,volatile如果它确定输出值未被使用,也会阻止编译器删除表达式.这只有在有输出值时才会发生,所以它不适用于asm ("" ::: "memory").

  • Matthew Slattery的回答指出,"asm volatile("")与无所事事并不完全相同,因为它会对编译器优化产生巨大影响.使用`asm volatile("":::"memory")`作为编译器围栏也会产生相同的性能影响. (10认同)
  • @curiousguy没有,但它确实理解`asm`块何时声明了输入/输出,它告诉编译器它依赖于哪个寄存器以及它将修改哪些寄存器,因此编译器可以在某些计算周围进行混洗,如果它们没有影响输入/输出. (2认同)