为什么这个“std::atomic_thread_fence”起作用

2024-05-01

首先我想谈一下我对此的一些理解,如有错误请指正。

  1. a MFENCE在x86中可以保证全屏障
  2. 顺序一致性可防止 STORE-STORE、STORE-LOAD、LOAD-STORE 和 LOAD-LOAD 重新排序

    这是根据维基百科 https://en.wikipedia.org/wiki/Memory_ordering.

  3. std::memory_order_seq_cst不保证防止 STORE-LOAD 重新排序。

    这是根据亚历克斯的回答 https://stackoverflow.com/questions/39053600/does-standard-c11-guarantee-that-memory-order-seq-cst-prevents-storeload-reord?rq=1、“负载可能会与早期存储一起重新排序到不同位置”(对于 x86)并且 mfence 不会始终被添加。

    无论是std::memory_order_seq_cst表示顺序一致性?根据第2/3点,我认为这似乎不正确。std::memory_order_seq_cst仅当以下情况时才表示顺序一致性

    1. 至少一个明确的MFENCE添加到任一LOAD or STORE
    2. LOAD(无栅栏)和 LOCK XCHG
    3. LOCK XADD ( 0 ) 和 STORE (无栅栏)

    否则仍有可能重新订购。

    根据@LWimsey 的评论,我在这里犯了一个错误,如果两者都LOAD and STORE are memory_order_seq_cst,没有重新排序。 Alex 可能指出使用非原子或非 SC 的情况。

  4. std::atomic_thread_fence(memory_order_seq_cst)总是产生一个完整的屏障

    这是根据亚历克斯的回答 https://stackoverflow.com/questions/25478029/does-atomic-thread-fencememory-order-seq-cst-have-the-semantics-of-a-full-memo。所以我可以随时更换asm volatile("mfence" ::: "memory") with std::atomic_thread_fence(memory_order_seq_cst)

    这对我来说很奇怪,因为memory_order_seq_cst原子函数和栅栏函数之间的用法似乎有很大不同。

现在我在MSVC 2015的标准库的头文件中找到这段代码,它实现了std::atomic_thread_fence

inline void _Atomic_thread_fence(memory_order _Order)
    {   /* force memory visibility and inhibit compiler reordering */
 #if defined(_M_ARM) || defined(_M_ARM64)
    if (_Order != memory_order_relaxed)
        {
        _Memory_barrier();
        }

 #else
    _Compiler_barrier();
    if (_Order == memory_order_seq_cst)
        {   /* force visibility */
        static _Uint4_t _Guard;
        _Atomic_exchange_4(&_Guard, 0, memory_order_seq_cst);
        _Compiler_barrier();
        }
 #endif
    }

所以我的主要问题是如何_Atomic_exchange_4(&_Guard, 0, memory_order_seq_cst);打造全面屏障MFENCE,或者实际上做了什么来启用类似的等效机制MFENCE,因为一个_Compiler_barrier()显然这里对于完整的内存屏障来说是不够的,或者这个语句的工作原理有点类似于第 3 点?


所以我的主要问题是如何_Atomic_exchange_4(&_Guard, 0, memory_order_seq_cst);创建一个完整的屏障 MFENCE

This compiles to an xchg instruction with a memory destination. This is a full memory barrier (draining the store buffer) exactly1 like mfence.

在此之前和之后存在编译器障碍,也可以防止围绕它的编译时重新排序。因此,阻止了任一方向上的所有重新排序(原子和非原子 C++ 对象上的操作),使其足够强大,可以执行 ISO C++ 的所有操作atomic_thread_fence(mo_seq_cst)承诺。


对于弱于 seq_cst 的指令,只需要编译器屏障。 x86 的硬件内存排序模型是程序顺序 + 具有存储转发的存储缓冲区。这足够强大了acq_rel编译器不会发出任何特殊的 asm 指令,只是阻止编译时重新排序。https://preshing.com/20120930/weak-vs-strong-memory-models/ https://preshing.com/20120930/weak-vs-strong-memory-models/


脚注1:完全足以满足 std::atomic 的目的。来自 WC 内存的弱有序 MOVNTDQA 加载可能不会严格排序lockMFENCE 的编辑说明。

  • x86 上哪个写屏障更好:lock+addl 或 xchgl? https://stackoverflow.com/questions/4232660/which-is-a-better-write-barrier-on-x86-lockaddl-or-xchgl/52910647#52910647
  • lock xchg 与 mfence 具有相同的行为吗? https://stackoverflow.com/questions/40409297/does-lock-xchg-have-the-same-behavior-as-mfence- 与 std::atomic 目的相同,但对于使用 WC 内存区域的设备驱动程序可能存在一些细微的差异。以及性能差异。尤其在 Skylake 上mfence阻止 OoO exec 类似lfence https://stackoverflow.com/questions/50494658/are-loads-and-stores-the-only-instructions-that-gets-reordered
  • 为什么 LOCK 在 x86 上是完全屏障? https://stackoverflow.com/questions/60332591/why-is-lock-a-full-barrier-on-x86

x86 上的原子读-修改-写 (RMW) 操作只能通过lock前缀,或xchg有记忆 https://www.felixcloutier.com/x86/xchg即使机器代码中没有锁定前缀,情况也是如此。带锁前缀的指令(或带有 mem 的 xchg)始终是完整的内存屏障。

使用类似的指令lock add dword [esp], 0作为替代品mfence是一项众所周知的技术。 (并且在某些 CPU 上性能更好。)这个 MSVC 代码是相同的想法,但它不是对堆栈指针指向的任何内容执行无操作,而是执行xchg在虚拟变量上。实际上它在哪里并不重要,但是仅由当前核心访问并且在缓存中已经很热的缓存线是性能的最佳选择。

Using a static所有核心都将争夺访问权限的共享变量是最糟糕的选择;这段代码太糟糕了!无需与其他核心相同的高速缓存行交互来控制该核心对其自己的 L1d 高速缓存的操作顺序。这完全是疯了。 MSVC 显然仍然在其实现中使用这个可怕的代码std::atomic_thread_fence(),即使对于 x86-64,其中mfence保证可用。 (Godbolt 与 MSVC 19.14 https://godbolt.org/#z:OYLghAFBqd5QCxAYwPYBMCmBRdBLAF1QCcAaPECAM1QDsCBlZAQwBtMQBGAFlICsupVs1qhkAUgBMAISnTSAZ0ztkBPHUqZa6AMKpWAVwC2tLgFZSW9ABk8tTADljAI0zEQADlIAHVAsLqtHqGJuY%2BfgF0tvZORq7uXkoqanQMBMzEBMHGppwWSZiqgWkZBNGOLm6eiumZ2aF5NaXlsfGeAJSKqAbEyBwA5FIAzHbIhlgA1OJDOsxERngSQ9jiAAwAgmvrAG6oeOgTzhnEeG4Q7VMA7LIbE3cTCgToICBzqAvIAPoECMSYzOhPlQtH0II9niAjJgjCQAJ6fEhYYifJQAR0%2ByEe7WmN02lwAIlstrt9g8iH8UcgwU8Xm8PtMdHYCNNsFNJAA2bYZC7ia5be4TLnEKZDfETTiSIY4okE/qdVggfpmfqkUz9VYq1CKnRyOQPbq9TBsoacFUERUa9qdBD/JGUToAaxAQ24ADoAJwednu7hmTjey4SrwK/rcFVqjWkLX9FUKECrUjm9Vy0hwWBINBGbx4dhkCgQTPZ3MoVifewAd0%2B7N4VBzBDccYgzgtKucdgysMVptImah9AA8rRWJ3k6QsEYRMB2C2x3g/kVtpg46PMAAPQoGetdlVM5Qz1h4ZzEDt6LDbxMnIzbzo0ehMNgcHj8QTCUQoXUyISHuOQTqobwpLQy4ALT9pIsbKIUgGaNo9S5JY2gtJU7icOE/iAXBgi%2BOhgRIXEVSoQURSpLUWT6DkghEYBJSZHhbSEaRmEMc0dgVPhKGdAoBp9Fw8qKsqqoztGq7VhMRgKNsyCCpw7qujwEwQAA6gAkg42AXBAuCECQxqoRMehZjmbi6RcOoyHIZotp0EAZu8RZuOQlCFkZ7iLMgMmobWrD1sQjbNqOba0B2569loBCDsOM7jpO06jvg85qIuy6RmuG5bv03a7iGkYHkeJ4YAM3YEJe16kLejAsNOT4CEMQiTu%2B5mfrlP7nFGAGBCB/ZDBBySBDBujkQ0CE2KxrQEWhkRBIN8HYZNdHjVRxSMdNlGQcRtA0WUo3IZRy0hPBjwsTEO2cJx3GPnxSrhkJiqrl6wGiW54qyasckKVp5K6aQ%2Bl2S5xpDKZH7SJZyZWqQTpDJIrpmKs7JmJIlzcHkPBDOyQiKmGgmjtGsbxomVmpjAiAoL9uaOQWpNVMA7q1V5Pl%2BTOgXBRlKqhQOQ4jpG0WiLFXNzlBeBJTOqXIJuhU7vQe6jrlx7ELCp7ixeeBXizN50BVD5cLwAiSHVb4SI18jNfAf7tXQnXdYoa3QRAVhMcN80oRNGErahs2AY7q29SRpT24tPu0dt7G7b7rtNIHx3B6dXQ9Dx0chgJEaard92icAyBSbJQzvfgn3DHpBn2cKwySIDhsg5a1q2lUrUQ1DMNwwjSN%2BtwqPo6G13Y4quMJkmlft%2BBWORjj%2BOg50i6%2BX13BAA)

如果你正在做 seq_cststore,你的选择是mov+mfence(海湾合作委员会这样做)或做商店and障碍与单一xchg(clang 和 MSVC 这样做,所以代码生成很好,没有共享虚拟变量)。


这个问题的大部分早期部分(陈述“事实”)似乎都是错误的,并且包含一些误解或被误导的事情,甚至没有错。

std::memory_order_seq_cst不保证防止 STORE-LOAD 重新排序。

C++ 使用完全不同的模型来保证顺序,其中获取加载从发布存储中看到的值与其“同步”,并且 C++ 源代码中的后续操作保证可以看到发布存储之前代码中的所有存储。

它还保证总订单为allseq_cst 操作甚至可以跨不同的对象。 (较弱的顺序允许线程在其自己的存储变得全局可见之前重新加载,即存储转发。这就是为什么只有 seq_cst 必须耗尽存储缓冲区。它们还允许 IRIW 重新排序。其他线程是否总是以相同的顺序看到对不同线程中不同位置的两个原子写入? https://stackoverflow.com/questions/27807118/will-two-atomic-writes-to-different-locations-in-different-threads-always-be-see/50679223#50679223)

StoreLoad 重新排序等概念基于以下模型:

  • 所有核心间通信都是通过将存储提交到缓存一致的共享内存
  • 重新排序发生在一个核心内部对缓存的访问之间。例如通过存储缓冲区延迟存储可见性,直到稍后加载(如 x86 允许)。 (除非核心可以通过商店转发尽早看到自己的商店。)

就该模型而言,seq_cst 确实需要在 seq_cst 存储和稍后的 seq_cst 加载之间的某个时刻耗尽存储缓冲区。实现这一目标的有效方法是设置全面的屏障afterseq_cst 存储。 (而不是在每次 seq_cst 加载之前。便宜的加载比便宜的存储更重要。)

在像 AArch64 这样的 ISA 上,有加载获取和存储释放指令,它们实际上具有顺序释放语义,这与 x86 加载/存储“仅”常规释放不同。 (因此 AArch64 seq_cst 不需要单独的屏障;微体系结构可以延迟耗尽存储缓冲区,除非/直到执行加载获取,同时仍然有存储释放未提交到 L1d 缓存。)其他 ISA 通常需要完整的屏障在 seq_cst 存储之后耗尽存储缓冲区的指令。

当然,即使是 AArch64 也需要完整的屏障指令seq_cst fence,不同于seq_cst加载或存储手术.


std::atomic_thread_fence(memory_order_seq_cst)总是产生一个完整的屏障

实际上是的。

所以我可以随时更换asm volatile("mfence" ::: "memory") with std::atomic_thread_fence(memory_order_seq_cst)

实际上是的,但理论上,实现可能允许对非原子操作进行一些重新排序std::atomic_thread_fence并且仍然符合标准。Always这是一个非常强烈的词。

ISO C++ 仅在以下情况下保证任何内容std::atomic涉及加载或存储操作。 GNU C++ 可以让你推出自己的原子操作asm("" ::: "memory")编译器障碍 (acq_rel) 和asm("mfence" ::: "memory")全面壁垒。将其转换为 ISO C++ signal_fence 和 thread_fence 将留下一个具有数据争用 UB 的“可移植”ISO C++ 程序,因此无法保证任何内容。

(尽管请注意,滚动你自己的原子应该使用至少volatile https://stackoverflow.com/questions/4557979/when-to-use-volatile-with-multi-threading/58535118#58535118,而不仅仅是障碍,以确保编译器不会发明多个加载,即使您避免了将加载提升到循环之外的明显问题。谁害怕一个糟糕的优化编译器? https://lwn.net/Articles/793253/).


永远记住,实现所做的事情必须是at least与 ISO C++ 所保证的一样强大。这往往最终会变得更强大。

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

为什么这个“std::atomic_thread_fence”起作用 的相关文章

随机推荐

  • C# 中类实例的内存使用情况[重复]

    这个问题在这里已经有答案了 可能的重复 C NET 对象使用多少内存 https stackoverflow com questions 426396 how much memory does a c net object use 就像标题
  • 将文件读入 Perl 中的变量 [重复]

    这个问题在这里已经有答案了 可能的重复 在 Perl 中将文件转换为字符串的最佳方法是什么 https stackoverflow com questions 206661 what is the best way to slurp a f
  • jquery是如何实现$(document).ready()的?

    jquery是如何实现的 document ready 当然我可以阅读代码 但我正在寻找这个概念 概念 jQuery ready http api jquery com ready 虽然 JavaScript 提供了在渲染页面时执行代码的
  • C# '+=' 运算符

    我有一些包含运算符 的代码 具体来说 代码如下 foreach KeyValuePair
  • 在 Google Analytics 中跟踪添加到主屏幕 Web 应用程序

    我有一个移动网络应用程序 其 添加到主屏幕 功能运行良好 我正在尝试确定如何使用 Google Analytics 来最好地跟踪已将应用程序安装到主屏幕的用户的应用程序使用情况 似乎没有默认方法可以在分析中查看应用程序是否处于独立模式 Go
  • css 中的嵌套边框布局?

    我这个工作正常 http jsfiddle net uwcEw http jsfiddle net uwcEw CSS 上的传统边框布局 我想要做的是在边框布局的 中间 内有另一个 5 区域边框布局 重新应用上面相同的模式我得到这个 htt
  • 使用 Angular 将井号作为 URL 查询字符串中的值传递

    我有一个输入标签 在输入时它将根据输入值过滤表中的数据列表 该值通过请求 URL 中的查询字符串传递 通常我会得到返回的数据并且表会被适当更新 但是 当搜索井号 时 我收到 500 内部服务器错误 我的问题是 在查询字符串中传递井号时 An
  • Bigtable数据库设计理论

    我非常精通关系数据库设计的理论和实践 我知道什么有效 什么无效 什么是高性能的 什么是可维护的 几乎 当您开始拥有真实数据时 总是有需要调整的地方 看来我找不到关于分布式可扩展数据库的大量知识 例如 Google 的 Bigtable 用于
  • MATLAB 图像锐化 - 使用(1-高斯低通滤波器)的高斯高通滤波器

    我试图通过设计高斯高通滤波器来锐化图像 我想利用高通滤波器相当于单位矩阵减去低通滤波器的事实来做到这一点 所以我执行了以下操作 image imread Question3 Data Cats jpg read image H 1 fspe
  • 在 sendmultiple 上使用 dropzone.js 发送 formData

    dropzone js 文档 wiki 没有说明如何发送表单字段 我刚刚读到表单数据对象 https developer mozilla org en US docs Web API FormData FormData它说明了如何使用表单字
  • 不同元素类型的名称属性的 jQuery 选择器

    我有一个行表 在其中一列中 某些行具有包含静态文本的跨度 某些行具有可供选择的值的选择 该一列中的所有元素都具有相同的名称属性 在我的表单提交中 我遍历行并希望获取所有列的值 我希望有一个 jQuery 选择器语句来从该元素获取值 span
  • Django ModelForm 不保存数据

    我已经尝试过以下帖子中的解决方案 从 ModelForm 保存数据 https stackoverflow com questions 13046488 saving data from modelform 没起作用 ModelForm数据
  • 识别 IR 中的阵列类型

    我一直在尝试使用以下代码来识别 IR 中的数组访问 for BasicBlock iterator ii BB gt begin ii2 ii BB gt end ii Instruction I ii if GetElementPtrIn
  • iPhone 中的应用程序堆大小是多少?

    HI iPhone 中的应用程序堆大小是多少 我的意思是应用程序可以使用 iPhone 中的内存大小 对于 ipod 来说是一样的还是对于 iPhone 或 iPod 来说是不同的 我们可以使用 NSdata 在我们的应用程序中下载的 iP
  • NGINX 与 Tomcat 配置

    我是 Nginx 新手 我需要你的帮助 根据很多论坛我了解到我们所有的静态页面都存储在Nginx中 当有请求到来时 我必须将该请求传递给 tomcat 获取数据 并在 tomcat 生成响应后生成响应 目前 我刚刚做到了 我将请求直接传递给
  • 编码标准维基

    在我的工作地点 我负责创建编码标准文档 一般来说 我们在某种程度上遵循 FxCop 和 StyleCop 工具报告的内容 但我们真正需要的是解释何时使用约定的文档 为什么使用约定 甚至可能是一个简单的示例 将来也可以将其扩展用于其他目的 我
  • pipx 如何知道要使用哪个 Python 版本?

    我是一个坚强的pyenv and poetry开始使用的用户pipx并希望了解更多有关其工作原理的信息 具体来说 我想了解它如何确定安装时使用哪个Python版本 我注意到它似乎在搜索PATH现有的应用程序参考 当您pipx install
  • ASP.NET Core [要求] 不可为 null 的类型

    Here https stackoverflow com questions 6662976 required attribute for an integer value 提出了如何验证不可为空的必需类型的问题 在我的情况下 提供的使字段
  • fbtorch:cmake 找不到 torch 目录

    我正在尝试构建并安装 fbtorch 但是当我使用 cmake 时出现以下错误 CMake Error at CMakeLists txt 9 FIND PACKAGE By not providing FindTorch cmake in
  • 为什么这个“std::atomic_thread_fence”起作用

    首先我想谈一下我对此的一些理解 如有错误请指正 a MFENCE在x86中可以保证全屏障 顺序一致性可防止 STORE STORE STORE LOAD LOAD STORE 和 LOAD LOAD 重新排序 这是根据维基百科 https