标准C ++ 11是否保证memory_order_seq_cst阻止StoreLoad在原子周围重新排序非原子?

时间:2016-08-20 11:28:12

标签: c++ multithreading c++11 concurrency standards

标准C ++ 11是否保证memory_order_seq_cst阻止StoreLoad重新排序原子操作以进行非原子内存访问?

众所周知,C ++ 11中有6个std::memory_order,它指定如何在原子操作周围排序常规,非原子内存访问 - 工作草案,C ++编程语言标准2016-07-12:http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2016/n4606.pdf

  

§29.3顺序和一致性

     

§29.3/ 1

     

枚举 memory_order 指定详细的常规   (非原子)内存同步顺序,如1.10和5中所定义   提供操作订购。它列举的价值和他们的   含义如下:

众所周知,这6个memory_orders会阻止其中一些重新排序:

enter image description here

但是,memory_order_seq_cst是否阻止StoreLoad围绕常规,非原子内存访问的原子操作重新排序,或仅针对具有相同memory_order_seq_cst的其他原子进行重新排序?

即。如果我们对STORE和LOAD都使用std::memory_order_seq_cst,或者仅对其中一个使用{Store},那么要阻止这个StoreLoad重新排序吗?

std::atomic<int> a, b;
b.store(1, std::memory_order_seq_cst); // Sequential Consistency
a.load(std::memory_order_seq_cst); // Sequential Consistency

关于Acquire-Release语义是清楚的,它完全指定了原子操作中的非原子内存访问重新排序:http://en.cppreference.com/w/cpp/atomic/memory_order

为了防止StoreLoad重新排序,我们应该使用std::memory_order_seq_cst

两个例子:

    对于STORE和LOAD,
  1. std::memory_order_seq_cstMFENCE
  2. StoreLoad无法重新排序 - GCC 6.1.0 x86_64:https://godbolt.org/g/mVZJs0

    std::atomic<int> a, b;
    b.store(1, std::memory_order_seq_cst); // can't be executed after LOAD
    a.load(std::memory_order_seq_cst); // can't be executed before STORE
    
    1. std::memory_order_seq_cst仅适用于LOAD:没有MFENCE
    2. StoreLoad可以重新排序 - GCC 6.1.0 x86_64:https://godbolt.org/g/2NLy12

      std::atomic<int> a, b;
      b.store(1, std::memory_order_release); // can be executed after LOAD
      a.load(std::memory_order_seq_cst); // can be executed before STORE
      

      此外,如果C / C ++ - 编译器使用C / C ++ 11的替代映射到x86,它在LOAD之前刷新存储缓冲区:MFENCE,MOV (from memory),所以我们也必须使用std::memory_order_seq_cst来加载LOAD :http://www.cl.cam.ac.uk/~pes20/cpp/cpp0xmappings.html正如此示例在另一个问题中讨论为方法(3):Does it make any sense instruction LFENCE in processors x86/x86_64?

      即。我们应该使用std::memory_order_seq_cst对STORE和LOAD生成MFENCE保证,以防止StoreLoad重新排序。

      是真的,memory_order_seq_cst用于原子加载或存储:

      • specifici获取 - 释放语义 - 阻止:LoadLoad,LoadStore,StoreStore重新排序原子操作以进行常规,非原子内存访问,

      • 但是阻止StoreLoad重新排序原子操作只针对具有相同memory_order_seq_cst的其他原子操作?

2 个答案:

答案 0 :(得分:4)

不,标准C ++ 11 保证memory_order_seq_cst阻止 StoreLoad non-atomic周围重新排序atomic(seq_cst)

即使标准C ++ 11 也不能保证memory_order_seq_cst阻止<{1}}周围的atomic(non-seq_cst) StoreLoad 重新排序。

工作草案,编程语言C ++标准2016-07-12:http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2016/n4606.pdf

  • 所有atomic(seq_cst)操作都应该有一个总订单S - C ++ 11 Standard:
  

§29.3

     

3

     

所有memory_order_seq_cst上应该有一个总订单S.   操作,符合“之前发生”的顺序​​和   所有受影响地点的修改订单,例如每个   memory_order_seq_cst操作B从原子加载值   对象M观察到以下值之一:...

  • 但是,排序弱于memory_order_seq_cst的任何原子操作都没有顺序一致性,并且没有单个总顺序,即非memory_order_seq_cst操作可以使用memory_order_seq_cst操作重新排序允许的方向 - C ++ 11标准:
  

§29.3

     

8 [注意: memory_order_seq_cst可确保顺序一致性   仅适用于无数据竞赛且仅使用的程序   memory_order_seq_cst操作。任何使用较弱的订购都会   除非使用极度谨慎,否则此保证无效。特别是,   memory_order_seq_cst围栏确保仅针对围栏的总订单   他们自己。通常,挡板不能用于恢复顺序   具有较弱订购规范的原子操作的一致性。    - 结束说明]

此外,C ++ - 编译器允许这样的重新排序:

  1. 在x86_64
  2. 通常 - 如果在编译器中seq_cst在存储后实现为屏障,那么:

    memory_order_seq_cst STORE-C(relaxed);可以重新排序到LOAD-B(seq_cst); LOAD-B(seq_cst);

    GCC 7.0 x86_64生成的Asm的屏幕截图:https://godbolt.org/g/4yyeby

    另外,理论上可行 - 如果在编译器中seq_cst在加载之前实现为屏障,那么:

    STORE-C(relaxed); STORE-A(seq_cst);可以重新排序到LOAD-C(acq_rel); LOAD-C(acq_rel);

    1. 在PowerPC上
    2. STORE-A(seq_cst); STORE-A(seq_cst);可以重新排序到LOAD-C(relaxed); LOAD-C(relaxed);

      同样在PowerPC上也可以重新排序:

      STORE-A(seq_cst); STORE-A(seq_cst);可以重新排序为STORE-C(relaxed); STORE-C(relaxed);

      如果允许原子变量跨原子(seq_cst)重新排序,那么非原子变量也可以在原子(seq_cst)上重新排序。

      GCC 4.8 PowerPC生成的Asm截图:https://godbolt.org/g/BTQBr8

      更多详情:

      1. 在x86_64
      2. STORE-A(seq_cst); STORE-C(release);可以重新排序到LOAD-B(seq_cst); LOAD-B(seq_cst);

        Intel® 64 and IA-32 Architectures

          

        8.2.3.4负载可能会与较早的商店重新排序到不同的位置

        即。 x86_64代码:

        STORE-C(release);

        可以重新订购:

        STORE-A(seq_cst);
        STORE-C(release); 
        LOAD-B(seq_cst);
        

        这可能发生,因为STORE-A(seq_cst); LOAD-B(seq_cst); STORE-C(release); c.store之间不是b.load

        x86_64 - GCC 7.0 https://godbolt.org/g/dRGTaO

        C ++&amp; asm - 代码:

        mfence

        可以重新订购:

        #include <atomic>
        
        // Atomic load-store
        void test() {
            std::atomic<int> a, b, c;
            a.store(2, std::memory_order_seq_cst);          // movl 2,[a]; mfence;
            c.store(4, std::memory_order_release);          // movl 4,[c];
            int tmp = b.load(std::memory_order_seq_cst);    // movl [b],[tmp];
        }
        

        此外,x86 / x86_64中的顺序一致性可以通过四种方式实现:http://www.cl.cam.ac.uk/~pes20/cpp/cpp0xmappings.html

          
            
        1. #include <atomic> // Atomic load-store void test() { std::atomic<int> a, b, c; a.store(2, std::memory_order_seq_cst); // movl 2,[a]; mfence; int tmp = b.load(std::memory_order_seq_cst); // movl [b],[tmp]; c.store(4, std::memory_order_release); // movl 4,[c]; } (没有围栏)和LOAD + STORE
        2.   
        3. MFENCE(没有围栏)和LOAD
        4.   
        5. LOCK XCHG + MFENCELOAD(没有围栏)
        6.   
        7. STORE(0)和LOCK XADD(没有围栏)
        8.   
        • 1和2种方式:STORE和(LOAD + STORE)/(MFENCE) - 我们在上面进行了审核
        • 3种和4种方式:(LOCK XCHG + MFENCE)/ LOADLOCK XADD - 允许下一次重新排序:

        STORE STORE-A(seq_cst);可以重新排序到LOAD-C(acq_rel); LOAD-C(acq_rel);

        1. 在PowerPC上
        2. STORE-A(seq_cst); STORE-A(seq_cst);可以重新排序到LOAD-C(relaxed); LOAD-C(relaxed);

          允许存储负载重新排序(表5 - PowerPC ):http://www.rdrop.com/users/paulmck/scalability/paper/whymb.2010.06.07c.pdf

            

          加载后重新排序的商店

          即。 PowerPC代码:

          STORE-A(seq_cst);

          可以重新订购:

          STORE-A(seq_cst);
          STORE-C(relaxed); 
          LOAD-C(relaxed); 
          LOAD-B(seq_cst);
          

          PowerPC - GCC 4.8 https://godbolt.org/g/xowFD3

          C ++&amp; asm - 代码:

          LOAD-C(relaxed);
          STORE-A(seq_cst);
          STORE-C(relaxed); 
          LOAD-B(seq_cst);
          

          #include <atomic> // Atomic load-store void test() { std::atomic<int> a, b, c; // addr: 20, 24, 28 a.store(2, std::memory_order_seq_cst); // li r9<-2; sync; stw r9->[a]; c.store(4, std::memory_order_relaxed); // li r9<-4; stw r9->[c]; c.load(std::memory_order_relaxed); // lwz r9<-[c]; int tmp = b.load(std::memory_order_seq_cst); // sync; lwz r9<-[b]; ... isync; } 分成两部分 - 可以重新排序为:

          a.store

          从内存加载#include <atomic> // Atomic load-store void test() { std::atomic<int> a, b, c; // addr: 20, 24, 28 //a.store(2, std::memory_order_seq_cst); // part-1: li r9<-2; sync; c.load(std::memory_order_relaxed); // lwz r9<-[c]; a.store(2, std::memory_order_seq_cst); // part-2: stw r9->[a]; c.store(4, std::memory_order_relaxed); // li r9<-4; stw r9->[c]; int tmp = b.load(std::memory_order_seq_cst); // sync; lwz r9<-[b]; ... isync; } 执行的时间早于存储到内存lwz r9<-[c];

          同样在PowerPC上也可以重新排序:

          stw r9->[a]; STORE-A(seq_cst);可以重新排序为STORE-C(relaxed); STORE-C(relaxed);

          因为PowerPC具有弱内存排序模型 - 允许Store-Store重新排序(表5 - PowerPC ):http://www.rdrop.com/users/paulmck/scalability/paper/whymb.2010.06.07c.pdf

            

          商店后重新订购的商店

          即。在PowerPC操作上,Store可以与其他Store重新排序,然后可以重新排序前面的示例,例如:

          STORE-A(seq_cst);

          存储到内存#include <atomic> // Atomic load-store void test() { std::atomic<int> a, b, c; // addr: 20, 24, 28 //a.store(2, std::memory_order_seq_cst); // part-1: li r9<-2; sync; c.load(std::memory_order_relaxed); // lwz r9<-[c]; c.store(4, std::memory_order_relaxed); // li r9<-4; stw r9->[c]; a.store(2, std::memory_order_seq_cst); // part-2: stw r9->[a]; int tmp = b.load(std::memory_order_seq_cst); // sync; lwz r9<-[b]; ... isync; } 的执行时间早于存储到内存stw r9->[c];

答案 1 :(得分:0)

std::memory_order_seq_cst保证编译器和cpu都没有重新排序。在这种情况下,相同的内存顺序就好像只有一条指令一次执行一样。

但是编译器优化会混淆问题,如果关闭-O3,那么栅栏就是there

编译器可以在你的测试程序中看到-O3没有mfence的后果,因为程序太简单了。

如果你在另一方面在一个手臂上运行它,如this,你可以看到障碍dmb ish

因此,如果您的程序更复杂,您可能会在代码的这一部分看到mfence,但如果编译器可以分析并推断它不需要则不会。