48

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

这听起来类似于 GCC 内置__sync_synchronize函数。

这两个相似吗?

如果不是,有什么区别,什么时候使用一个而不是另一个?

4

2 回答 2

56

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

第二个屏障(内置同步)将简单地转换为硬件屏障,如果您使用的是 x86,则可能是栅栏(mfence/sfence)操作,或者其他架构中的等效物。CPU 也可能在运行时做各种优化,最重要的是实际上是乱序执行操作——这条指令告诉它确保加载或存储不能通过这一点,必须在正确的一侧观察同步点。

这是另一个很好的解释:

内存屏障的类型

如上所述,编译器和处理器都可以以需要使用内存屏障的方式优化指令的执行。影响编译器和处理器的内存屏障是硬件内存屏障,仅影响编译器的内存屏障是软件内存屏障。

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

还有一类特定于多处理器环境的内存屏障。这些内存屏障的名称以“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 支持,就像现在大多数情况一样)。硬件围栏可以防止这种情况发生。

于 2013-11-13T22:05:34.130 回答
6

关于 SW-only 障碍有用性的评论:

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

于 2015-12-21T22:10:20.613 回答