是否存在等于 C++11 中的 asm("" ::: "memory") 的编译器障碍?

2024-01-12

我的测试代码如下,我发现只有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(””:::“内存”)”级别太低。

还有更好的选择吗?


回复:您的编辑:

但我不想使用原子变量。

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

如果是因为其他原因,那么也许atomic_signal_fence将为您提供恰好在您的目标平台上运行的代码。我怀疑它的大多数实现确实订购了非atomic<>在实践中加载和存储,至少作为实现细节,并且如果可以访问,则可能有效地需要atomic<>变量。因此,在实践中可能有助于避免仍然存在的任何数据争用未定义行为的一些实际后果。 (例如,作为 SeqLock 实现的一部分,为了提高效率,您希望使用共享数据的非原子读/写,以便编译器可以使用 SIMD 向量副本。)

See 谁害怕一个糟糕的优化编译器? https://lwn.net/Articles/793253/在 LWN 上,了解如果您仅使用编译器屏障强制重新加载非atomic变量,而不是使用具有只读一次语义的东西。 (在那篇文章中,他们谈论的是 Linux 内核代码,因此他们使用volatile用于手动加载/存储原子。但一般情况下不要这样做:何时在多线程中使用 易失性? https://stackoverflow.com/questions/4557979/when-to-use-volatile-with-multi-threading/58535118#58535118- 几乎从来没有)


够什么用?

不管有什么障碍,如果两个线程同时运行这个函数,你的程序就会有未定义的行为,因为并发访问非atomic<>变量。因此,此代码唯一有用的方法是,如果您正在谈论与在同一线程中运行的信号处理程序同步。

这也与要求“编译器屏障”一致,仅防止在编译时重新排序,因为乱序执行和内存重新排序始终保留单个线程的行为。因此,您永远不需要额外的屏障指令来确保您按程序顺序看到自己的操作,您只需要在编译时停止编译器重新排序即可。请参阅 Jeff Preshing 的帖子:编译时的内存排序 http://preshing.com/20120625/memory-ordering-at-compile-time/

这是什么atomic_signal_fence http://en.cppreference.com/w/cpp/atomic/atomic_signal_fence is for。您可以将它与任何std::memory_order,就像 thread_fence 一样,获得不同强度的屏障并仅阻止您需要阻止的优化。


... atomic_thread_fence(memory_order_acq_rel)根本没有产生任何编译器障碍!

从几个方面来看,这是完全错误的。

atomic_thread_fence is编译器障碍plus任何运行时障碍都需要限制重新排序,以便我们的加载/存储对其他线程可见。

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

在像 ARM 这样的弱有序 ISA 上,thread_fence(mo_acq_rel) 不是免费的,并且会编译为指令。 gcc5.4使用dmb ish。 (请参阅Godbolt 编译器浏览器 http://gcc.godbolt.org/#g:!((g:!((g:!((h:codeEditor,i:(j:1,options:(colouriseAsm:%270%27,compileOnChange:%270%27),source:%27%23include+%3Catomic%3E%0A%0Ausing+namespace+std%3B%0A%0Aint+A,+B+%3D+1%3B%0A%0A//const+std::memory_order+mo+%3D+std::memory_order_seq_cst%3B%0Aconst+std::memory_order+mo+%3D+std::memory_order_acq_rel%3B%0A%0Avoid+func_threadfence(void)+%7B%0A++++A+%3D+B+%2B+1%3B%0A++++atomic_thread_fence(mo)%3B%0A++++B+%3D+0%3B%0A%7D%0A%0A%0Avoid+func_signalfence(void)+%7B%0A++++A+%3D+B+%2B+1%3B%0A++++atomic_signal_fence(mo)%3B%0A++++B+%3D+0%3B%0A%7D%27),l:%275%27,n:%271%27,o:%27C%2B%2B+source+%231%27,t:%270%27)),k:33.92739273927394,l:%274%27,n:%270%27,o:%27%27,s:0,t:%270%27),(g:!((h:compiler,i:(compiler:armhfg54,filters:(b:%270%27,commentOnly:%270%27,directives:%270%27),options:%27-O3+-std%3Dgnu%2B%2B11+-Wall+-Wextra+-fno-verbose-asm%27),l:%275%27,n:%270%27,o:%27%231+with+ARM+gcc+5.4%27,t:%270%27)),k:32.739273927392745,l:%274%27,n:%270%27,o:%27%27,s:0,t:%270%27),(g:!((h:compiler,i:(compiler:g62,filters:(b:%270%27,commentOnly:%270%27,directives:%270%27,intel:%270%27),options:%27-O3+-std%3Dgnu%2B%2B11+-Wall+-Wextra+-fno-verbose-asm%27),l:%275%27,n:%270%27,o:%27%231+with+x86-64+gcc+6.2%27,t:%270%27)),k:33.33333333333333,l:%274%27,n:%270%27,o:%27%27,s:0,t:%270%27)),l:%272%27,n:%270%27,o:%27%27,t:%270%27)),version:4).

编译器屏障只是阻止编译时重新排序,而不一定阻止运行时重新排序。所以即使在 ARM 上,atomic_signal_fence(mo_seq_cst)编译为无指令。

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

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


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

请参阅 Godbolt 上的源代码+asm http://gcc.godbolt.org/#g:!((g:!((g:!((h:codeEditor,i:(j:1,options:(colouriseAsm:%270%27,compileOnChange:%270%27),source:%27%23include+%3Catomic%3E%0Ausing+namespace+std%3B%0Aint+A,B%3B%0A%0Avoid+foo()+%7B%0A++A+%3D+0%3B%0A++atomic_thread_fence(memory_order_release)%3B%0A++B+%3D+1%3B%0A++//asm+volatile(%22%22:::+%22memory%22)%3B%0A++//atomic_signal_fence(memory_order_release)%3B%0A++atomic_thread_fence(memory_order_release)%3B%0A++A+%3D+2%3B%0A%7D%27),l:%275%27,n:%271%27,o:%27C%2B%2B+source+%231%27,t:%270%27)),k:33.69797478330771,l:%274%27,n:%270%27,o:%27%27,s:0,t:%270%27),(g:!((h:compiler,i:(compiler:g62,filters:(b:%270%27,commentOnly:%270%27,directives:%270%27,intel:%270%27),options:%27-O3+-std%3Dc%2B%2B11+-fno-verbose-asm+-Wall+-Wextra%27),l:%275%27,n:%270%27,o:%27%231+with+x86-64+gcc+6.2%27,t:%270%27)),k:32.96869188335898,l:%274%27,n:%270%27,o:%27%27,s:0,t:%270%27),(g:!((h:compiler,i:(compiler:clang390,filters:(b:%270%27,commentOnly:%270%27,directives:%270%27,intel:%270%27),options:%27-O3+-std%3Dc%2B%2B11+-fno-verbose-asm+-Wall+-Wextra%27),l:%275%27,n:%270%27,o:%27%231+with+x86-64+clang+3.9.0%27,t:%270%27)),k:33.33333333333333,l:%274%27,n:%270%27,o:%27%27,s:0,t:%270%27)),l:%272%27,n:%270%27,o:%27%27,t:%270%27)),version:4.

#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 匹配。So atomic_signal_fence(mo_release)具有我们预期的屏障效应,但是atomic_thread_fence任何比 seq_cst 弱的东西根本不会充当编译器障碍。

这里的一种理论是 gcc 知道多线程写入非官方行为是未定义的行为atomic<>变量。这并不能容纳太多水,因为atomic_thread_fence如果用于与信号处理程序同步,应该仍然可以工作,它只是比必要的更强大。

顺便说一句,与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 值是一种可能的排序,因此编译器可以决定这总是发生的情况)。不过,当前的编译器通常不会进行这种优化。请参阅我的答案末尾的讨论num++ 对于“int num”可以是原子的吗? https://stackoverflow.com/questions/39393850/can-num-be-atomic-for-int-num/39396999#39396999.

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

是否存在等于 C++11 中的 asm("" ::: "memory") 的编译器障碍? 的相关文章

随机推荐

  • 如何检查当前线程的单元状态?

    我有一个函数需要在 STA 公寓状态下运行 我想检查它是否作为 STA 运行 如果没有生成一个在 STA 中运行的新线程 如何检查当前线程正在哪个单元状态运行 System Threading Thread CurrentThread Ge
  • NSString 发布

    我有这段字符串代码 在尝试释放内存时遇到问题 我知道只有那些释放它的人才会初始化 而不是自动释放 但我在字符串 end 和 nSum 释放方面遇到了问题 NSString urlBase NSString alloc initWithFor
  • 多处理炸弹

    我正在研究以下示例道格赫尔曼 http www doughellmann com PyMOTW multiprocessing basics html多处理教程 import multiprocessing def worker worke
  • 应用程序未指定API级别

    我刚刚开始使用 Eclipse 开发 Android 应用程序 我已经安装了 Eclipse 3 5 2 和 Java 5 AVD 是 Android 2 1 API 7 我最初的 Hello Android 程序运行良好 但无法再次运行
  • 使用 Gradle (Android Studio) 构建极长的版本

    现在 对于非常简单的更改 我们的构建时间为 2 分 30 秒 这 与 ANT 相比 速度慢得惊人 并且正在降低整个团队的生产力 我正在使用 Android Studio 并使用 使用本地 gradle 发行版 我尝试为 gradle 提供更
  • 脚本如何区分 Docker Toolbox 和 Docker for Windows?

    在我目前的团队中 我们仍在从Docker工具箱 to 适用于 Windows 的 Docker 桌面 我们的许多脚本仍然假设您在 VirtualBox 上运行 Docker Toolbox 例如如何挂载驱动器 斜杠或驱动器名称如何用于这些挂
  • Heroku ssl:具有 GlobalSign ExtendedSSL 的端点

    我已注册 GlobalSign ExtendedSSL 我只是不知道如何将其添加到ssl endpoint addon 当我注册 ExtendedSSL 时 我必须生成 CSR 证书 其中包含以下文件 私钥 key www domain c
  • 是否有一致的方式链接到 Google“我手气不错”结果? [关闭]

    Closed 这个问题不符合堆栈溢出指南 help closed questions 目前不接受答案 在我的应用程序中 我试图创建一种简单的方法来根据 NFL 球员的名字链接到 NFL com 上的 NFL 球员的个人资料 由于 NFL c
  • 为 Git 克隆设置密码?

    我想为我的存储库设置一个密码 以便在从我的存储库克隆时询问密码 要限制对存储库克隆的访问 您需要使用 ssh 协议 并仅向您想要访问的用户提供 ssh 访问权限 您可能应该做的是查找各种 git 工作流程 问题和答案 它们描述了各个团队成员
  • C# Linq to SQL:如何表达“CONVERT([...] AS INT)”?

    在 MSSQL 中 您可以将字符串转换为整数 如下所示 CONVERT INT table column 是否有 Linq to SQL 可以将其转换为任何 C 表达式 在 C 中 您通常可以使用以下方法执行相同操作int Parse 但不
  • 更改 pandas 中的列类型

    我从列表列表创建了一个 DataFrame table a 1 2 4 2 b 70 0 03 x 5 0 df pd DataFrame table 如何将列转换为特定类型 在本例中 我想将第 2 列和第 3 列转换为浮点数 有没有办法在
  • forkjoin后无法订阅

    我想在角度循环内执行一些 firebase 操作 当我向 firebase 发送 HTTP 请求时 它返回一个可观察值数组 因此使用 forkjoin 我将此可观察值数组转换为单个可观察值 现在的问题是 当我订阅这些新的可观察量时 我没有得
  • 在 HijrahChronology 中配置自定义变体以进行日期校正 jdk 8

    我在 javafx JDK 8 中使用了 DatePicker 并使用了 HijrahChronology INSTANCE 这样日期选择器就显示了日历 一切都工作得很好 但我在公历和回历之间有 1 天的差异 回历推迟 1 天 我正在尝试按
  • 从 NFC mifare 标签读取 UID iOS 13

    我正在尝试读取 mifare 标签的 UID 查看示例 我经常看到以下方法 func tagReaderSession session NFCTagReaderSession didDetect tags NFCTag if case le
  • 在 WPF 中将画布转换为 writeablebitmap 的最快方法?

    我目前有一个 writeablebitmap 图像和带有绘图的画布 我想将图像发送给对等方 为了减少带宽 我想将 canvas 转换为 writeablebitmap 这样我就可以将这两个图像 blit 到一个新的 writeablebit
  • 我可以将步骤定义放在不是“步骤”的文件夹中吗?

    我正在尝试使用 Behave on Python 进行工作 我想知道是否有办法将我的 py 文件放在其他地方 而不是被迫将它们全部放在 steps 文件夹中 我当前的结构如下所示 tests features steps all code
  • 什么是最小正常运行时间

    我在用forever https github com nodejitsu forever和我的项目 这些选项有什么作用 minUptime spinSleepTime 我从 GitHub 页面不明白 永远的文档简要解释每一项 https
  • Grails,如何通过外键查找记录

    我有两个域 它们是一对多关系的一部分 我想知道如何查询孩 子的父母FK 下面是父 子的伪代码 Parent class AlumProfile String firstName String lastName static hasMany
  • .NET Core 中的 WCF 参考

    如何将 WCF 引用到我的 NET Core 客户端 我下载并安装 WCF 服务预览 插件 但是当我尝试添加引用时出现错误 错误 找不到与 Net Core 应用程序兼容的端点 工具中出现错误 无法生成服务参考 当我在浏览器中尝试服务时 工
  • 是否存在等于 C++11 中的 asm("" ::: "memory") 的编译器障碍?

    我的测试代码如下 我发现只有memory order seq cst禁止编译器重新排序 include