有一个显着的区别 - 第一个选项(内联 asm)实际上在运行时不执行任何操作,那里没有执行任何命令,并且 CPU 不知道它。它仅在编译时起作用,告诉编译器不要将加载或存储移动到此点之外(向任何方向),作为其优化的一部分。它被称为 SW 屏障。
第二个屏障(内置同步)将简单地转换为硬件屏障,如果您使用的是 x86 或其他架构中的等效项,则可能是栅栏(mfence/sfence)操作。 CPU 还可能在运行时进行各种优化,最重要的一个实际上是无序执行操作 - 该指令告诉它确保加载或存储不能通过这一点,并且必须在正确的一侧进行观察同步点。
Here's http://bruceblinn.com/linuxinfo/MemoryBarriers.html另一个很好的解释:
内存屏障的类型
如上所述,编译器和处理器都可以优化指令的执行,从而需要使用
记忆障碍。影响编译器和
处理器是一个硬件内存屏障,并且是一个内存屏障
唯一影响编译器的是软件内存屏障。
除了硬件和软件内存屏障之外,还有内存屏障
可以限制为内存读取、内存写入或两者。一段回忆
影响读取和写入的屏障是完整内存屏障。
还有一类特定于的内存屏障
多处理器环境。这些内存屏障的名称是
前缀为“smp”。在多处理器系统上,这些障碍是
硬件内存屏障和单处理器系统上,它们是
软件内存屏障。
Barrier() 宏是唯一的软件内存屏障,它是一个
完整的内存屏障。 Linux 内核中的所有其他内存屏障都是
硬件障碍。硬件内存屏障是隐含的软件
障碍。
SW 屏障何时有用的示例:考虑以下代码 -
for (i = 0; i < N; ++i) {
a[i]++;
}
这个经过优化编译的简单循环很可能会展开并矢量化。
这是 gcc 4.8.0 -O3 生成的打包(向量)操作的汇编代码:
400420: 66 0f 6f 00 movdqa (%rax),%xmm0
400424: 48 83 c0 10 add $0x10,%rax
400428: 66 0f fe c1 paddd %xmm1,%xmm0
40042c: 66 0f 7f 40 f0 movdqa %xmm0,0xfffffffffffffff0(%rax)
400431: 48 39 d0 cmp %rdx,%rax
400434: 75 ea jne 400420 <main+0x30>
但是,当在每次迭代中添加内联程序集时,gcc 不允许更改超过屏障的操作顺序,因此它无法对它们进行分组,并且程序集成为循环的标量版本:
400418: 83 00 01 addl $0x1,(%rax)
40041b: 48 83 c0 04 add $0x4,%rax
40041f: 48 39 d0 cmp %rdx,%rax
400422: 75 f4 jne 400418 <main+0x28>
但是,当 CPU 执行此代码时,只要不破坏内存排序模型,就允许“在幕后”对操作进行重新排序。这意味着执行操作可能会乱序(如果 CPU 支持的话,就像现在大多数 CPU 一样)。硬件围栏可以防止这种情况发生。