GCC内存屏障__sync_synchronize vs asm volatile(“”:::“memory”)

时间:2013-11-13 21:48:07

标签: c gcc

asm volatile("": : :"memory")通常用作内存屏障(例如,在Linux内核barrier宏中看到)。

这听起来类似于GCC内置的__sync_synchronize

这两个是否相似?

如果没有,那么差异是什么,何时使用另一个?

2 个答案:

答案 0 :(得分:46)

有一个显着的区别 - 第一个选项(内联asm)在运行时实际上什么都没做,那里没有执行任何命令,CPU也不知道它。它只在编译时服务,告诉编译器不要在此点(任何方向)上移动加载或存储作为其优化的一部分。它被称为SW屏障。

第二个障碍(内置同步),只是转换为硬屏障,如果你在x86上,可能是围栏(mfence / sfence)操作,或者它在其他架构中的等价物。 CPU也可以在运行时进行各种优化,最重要的是实际执行无序操作 - 该指令告诉它确保加载或存储不能通过这一点,并且必须在正确的一侧观察同步点。

Here's另一个很好的解释:

  

内存障碍的类型

     

如上所述,编译器和处理器都可以以必须使用a的方式优化指令的执行   记忆障碍。影响编译器和内存的内存屏障   处理器是硬件内存屏障,以及内存屏障   只影响编译器是软件内存的障碍。

     

除硬件和软件内存障碍外,还有内存屏障   可以限制为内存读取,内存写入或两者兼而有之。记忆   影响读写的屏障是一个完整的内存屏障。

     

还有一类特定的内存屏障   多处理器环境。这些记忆障碍的名称是   前缀为“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支持这种操作,那么这些日子大多数都是这样做的)。 HW围栏会阻止它。

答案 1 :(得分:3)

评论仅限SW的障碍的有用性:

在某些微控制器和其他嵌入式平台上,您可能具有多任务处理,但没有缓存系统或缓存延迟,因此没有硬件屏障指令。所以你需要做SW旋转锁之类的事情。 SW屏障可防止这些算法中的编译器优化(读/写组合和重新排序)。