使用内存屏障强制按顺序执行

Using memory barriers to force in-order execution

尝试继续我的想法,即使用软件和硬件内存屏障,我可以在使用编译器优化编译的代码中禁用特定函数的乱序优化,因此我可以实现软件使用 PetersonDeker 等不需要无序执行的算法的信号量,我测试了以下代码,其中包含 SW barrier asm volatile("": : :"memory") 和 gcc builtin HW barrier __sync_synchronize:

#include <stdio.h>
int main(int argc, char ** argv)
{
    int x=0;
    asm volatile("": : :"memory");
    __sync_synchronize();
    x=1;
    asm volatile("": : :"memory");
    __sync_synchronize();
    x=2;
    asm volatile("": : :"memory");
    __sync_synchronize();
    x=3;
    printf("%d",x);
    return 0;
}

但是编译输出文件是:

main:
.LFB24:
    .cfi_startproc
    subq    , %rsp
    .cfi_def_cfa_offset 16
    mfence
    mfence
    movl    , %edx
    movl    $.LC0, %esi
    movl    , %edi
    xorl    %eax, %eax
    mfence
    call    __printf_chk
    xorl    %eax, %eax
    addq    , %rsp

如果我移除障碍并再次编译,我得到:

main
.LFB24:
    .cfi_startproc
    subq    , %rsp
    .cfi_def_cfa_offset 16
    movl    , %edx
    movl    $.LC0, %esi
    movl    , %edi
    xorl    %eax, %eax
    call    __printf_chk
    xorl    %eax, %eax
    addq    , %rsp

在 Ubuntu 14.04.1 LTS,x86 中使用 gcc -Wall -O2 编译。

预期的结果是包含内存屏障的代码的输出文件将包含我在源代码中的所有值分配,它们之间有 mfence

根据相关的 Whosebug post -

gcc memory barrier __sync_synchronize vs asm volatile("": : :"memory")

When adding your inline assembly on each iteration, gcc is not permitted to change the order of the operations past the barrier

之后:

However, when the CPU performes this code, it's permitted to reorder the operations "under the hood", as long as it does not break memory ordering model. This means that performing the operations can be done out of order (if the CPU supports that, as most do these days). A HW fence would have prevented that.

但是正如您所看到的,带有内存屏障的代码与没有内存屏障的代码之间的唯一区别是前者包含 mfence 的方式是我没有预料到的,而且不是所有作业都包括在内。

为什么带有内存屏障的文件的输出文件与我预期的不一样 - 为什么 mfence 顺序被更改了?为什么编译器删除了一些赋值?即使应用了内存屏障并将每一行代码分开,编译器是否允许进行此类优化?

对内存屏障类型和用法的引用:

内存屏障告诉 compiler/CPU 指令不应该跨屏障重新排序,它们并不意味着无论如何都必须完成可以证明毫无意义的写入。

如果您将 x 定义为 volatile,编译器无法假设它是唯一关心 x 值并且必须遵循C抽象机的规则,这是为了内存写入实际发生。

在您的特定情况下,您可以跳过障碍,因为它已经保证易失性访问不会相互重新排序。

如果你有 C11 支持,你最好使用 _Atomics,它还可以保证正常分配不会根据你的 x 重新排序,并且访问是原子的。


编辑:GCC(以及 clang)在这方面似乎不一致,并不总是进行这种优化。 I opened a GCC bug report regarding this.