ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

在C 11中是否有任何编译器屏障等于asm(“”:::“memory”)?

2019-08-24 00:07:43  阅读:266  来源: 互联网

标签:lock-free memory-barriers c c11 atomic


我的测试代码如下,我发现只有memory_order_seq_cst禁止编译器重新排序.

#include <atomic>

using namespace std;

int A, B = 1;

void func(void) {
    A = B + 1;
    atomic_thread_fence(memory_order_seq_cst);
    B = 0;
}

而诸如memory_order_release,memory_order_acq_rel等其他选择根本不会产生任何编译器障碍.

我认为他们必须使用原子变量,如下所示.

#include <atomic>

using namespace std;

atomic<int> A(0);
int B = 1;

void func(void) {
    A.store(B+1, memory_order_release);
    B = 0;
}

但我不想使用原子变量.与此同时,我认为“asm(”“:::”记忆“)”太低了.

还有更好的选择吗?

解决方法:

re:你的编辑:

But I do not want to use atomic variable.

为什么不?如果出于性能原因,将它们与memory_order_relaxed和atomic_signal_fence(mo_whatever)一起使用来阻止编译器重新排序,除了编译器屏障可能阻止某些编译时优化之外没有任何运行时开销,具体取决于周围的代码.

如果出于其他原因,那么atomic_signal_fence可能会为您提供恰好在目标平台上运行的代码.我怀疑它确实订购了非原子<>加载和/或存储,因此它甚至可以帮助避免C中的数据争用未定义行为.

什么足够?

无论有任何障碍,如果两个线程同时运行此函数,则由于对非原子<>的并发访问,您的程序具有未定义的行为.变量.因此,如果您正在讨论与在同一线程中运行的信号处理程序同步,则此代码有用的唯一方法.

这也与要求“编译器障碍”一致,只是为了防止在编译时重新排序,因为无序执行和内存重新排序总是保留单个线程的行为.因此,您永远不需要额外的屏障指令来确保按程序顺序查看自己的操作,您只需要在编译时停止编译器重新排序.见Jeff Preshing的帖子:Memory Ordering at Compile Time

这就是atomic_signal_fence的用途.您可以将它与任何std :: memory_order一起使用,就像thread_fence一样,以获得不同的屏障优势,并且只能阻止您需要防止的优化.

atomic_thread_fence(memory_order_acq_rel) did not generate any compiler barrier at all!

在几个方面完全错了.

atomic_thread_fence是一个编译器障碍,加上我们的加载/存储对其他线程可见的顺序限制重新排序所需的任何运行时障碍.

我猜你的意思是当你查看x86的asm输出时它没有发出任何障碍指令.像x86的MFENCE这样的指令不是“编译器障碍”,它们是运行时内存屏障,甚至阻止StoreLoad在运行时重新排序. (这是x86允许的唯一重新排序.只有在使用弱排序(NT)存储时才需要SFENCE和LFENCE,如MOVNTPS (_mm_stream_ps).)

在像ARM这样弱有序的ISA上,thread_fence(mo_acq_rel)不是免费的,而是编译成一条指令. gcc5.4使用dmb ish. (见Godbolt compiler explorer).

编译器屏障只是在编译时阻止重新排序,而不必阻止运行时重新排序.因此,即使在ARM上,atomic_signal_fence(mo_seq_cst)也会编译为无指令.

一个足够弱的屏障允许编译器在商店之前将商店存储到A(如果需要),但gcc碰巧决定仍然按源顺序执行它们,即使使用thread_fence(mo_acquire)(不应该与其他商店订购商店)商店).

所以这个例子并没有真正测试某些东西是否是编译器障碍.

来自gcc的奇怪编译器行为与编译器障碍不同的示例:

See this source+asm on Godbolt.

#include <atomic>
using namespace std;
int A,B;

void foo() {
  A = 0;
  atomic_thread_fence(memory_order_release);
  B = 1;
  //asm volatile(""::: "memory");
  //atomic_signal_fence(memory_order_release);
  atomic_thread_fence(memory_order_release);
  A = 2;
}

这会以你期望的方式编译clang:thread_fence是StoreStore屏障,因此A = 0必须在B = 1之前发生,并且不能与A = 2合并.

    # clang3.9 -O3
    mov     dword ptr [rip + A], 0
    mov     dword ptr [rip + B], 1
    mov     dword ptr [rip + A], 2
    ret

但是对于gcc,屏障没有效果,只有A的最终存储在asm输出中.

    # gcc6.2 -O3
    mov     DWORD PTR B[rip], 1
    mov     DWORD PTR A[rip], 2
    ret

但是使用atomic_signal_fence(memory_order_release),gcc的输出匹配clang.因此atomic_signal_fence(mo_release)具有我们期望的屏障效果,但是任何弱于seq_cst的atomic_thread_fence都不会充当编译屏障.

这里的一个理论是gcc知道它是多个线程写入非原子<>的官方未定义行为.变量.这并没有多少水,因为如果用于与信号处理程序同步,atomic_thread_fence仍然可以工作,它只是比必要的强.

BTW,使用atomic_thread_fence(memory_order_seq_cst),我们得到了预期的结果

    # gcc6.2 -O3, with a mo_seq_cst barrier
    mov     DWORD PTR A[rip], 0
    mov     DWORD PTR B[rip], 1
    mfence
    mov     DWORD PTR A[rip], 2
    ret

即使只有一个屏障,我们也可以得到这个,这仍然允许A = 0和A = 2存储一个接一个地发生,因此允许编译器跨屏障合并它们. (观察者未能看到单独的A = 0和A = 2值是可能的排序,因此编译器可以决定总是会发生什么).但是,当前的编译器通常不会进行这种优化.请参阅我在Can num++ be atomic for ‘int num’?的答案结尾处的讨论.

标签:lock-free,memory-barriers,c,c11,atomic
来源: https://codeday.me/bug/20190823/1702448.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有