【问题标题】:Purpose of _Compiler_barrier() on 32bit read32 位读取时 Compiler Barrier() 的用途
【发布时间】:2018-04-23 16:52:02
【问题描述】:

当我在 64 位项目的 VS2017 上分配 atomic_long 类型时,我一直在逐步完成所涉及的函数调用。我特别想看看当我将atomic_long 复制到一个非原子变量时会发生什么,以及它周围是否有任何锁定。

atomic_long ll = 10;
long t2 = ll;

最终以这个调用结束(我删除了一些 ifdefed out 的代码)

inline _Uint4_t _Load_seq_cst_4(volatile _Uint4_t *_Tgt)
    {   /* load from *_Tgt atomically with
            sequentially consistent memory order */
    _Uint4_t _Value;

    _Value = *_Tgt;
    _Compiler_barrier();

    return (_Value);
    }

现在,我读到 from MSDN 说 32 位值的普通读取将是原子的:

对正确对齐的 32 位变量的简单读写是 原子操作。

...这就解释了为什么没有Interlocked 函数用于阅读;只有那些用于更改/比较的。我想知道_Compiler_barrier() 位在做什么。这是#defined

__MACHINE(void _ReadWriteBarrier(void))

...我又在MSDN 上发现了这个

限制可以重新排序内存访问的编译器优化 跨越通话点。

但我不明白,因为除了return 调用之外没有其他内存访问;肯定编译器不会将赋值移到下面吗?

有人可以澄清这个障碍的目的吗?

【问题讨论】:

  • 也许它可以尝试return (*_Tgt);。只是猜测。
  • 你低估了优化器可以做什么。如果这不会改变程序的可观察行为,它肯定会被允许对内存访问进行重新排序,并且通常会这样做。从单线程的角度来看。屏障确保从另一个线程的角度来看禁止这样做。

标签: c++11 visual-c++ stdatomic


【解决方案1】:

_Load_seq_cst_4 是一个inline 函数。编译器屏障用于阻止与内联到的调用函数中的后续代码重新排序。

例如,考虑阅读SeqLock。 (过度简化自this actual implementation)。

#include <atomic>
atomic<unsigned> sequence;
atomic_long  value;

long seqlock_try_read() {
    // this would normally be the body of a retry-loop;
    unsigned seq1 = sequence;
    long tmpval = value;
    unsigned seq2 = sequence;

    if (seq1 == seq2 && (seq1 & 1 == 0)
        return tmpval;
    else
        // writer was modifying it, we should retry the loop
}

如果我们不阻止编译时重新排序,编译器可以将 sequence 的两个读取合并到一个访问中,就像这样

    long tmpval = value;
    unsigned seq1 = sequence;
    unsigned seq2 = sequence;

这会破坏锁定机制(作者在修改数据之前增加一次sequence,然后在完成后再次增加一次)。阅读器是完全无锁的,但它不是“无锁”算法,因为如果作者在更新过程中卡住,阅读器将无法阅读任何内容。

屏障每个load函数在内联后阻止与其他事物重新排序。

(C++11 内存模型很弱,但是 x86 内存模型很强大,只允许 StoreLoad 重新排序。阻塞编译时重新排序和稍后的加载/存储足以给你一个获取/顺序一致性加载在运行时。x86: Are memory barriers needed here?)


顺便说一句,一个更好的例子可能是在看到atomic 标志中的某个值后读取/写入一些非atomic 变量。 MSVC 可能已经避免了原子访问的重新排序或合并,并且在 seqlock 中,受保护的数据也必须是 atomic

Why don't compilers merge redundant std::atomic writes?

【讨论】:

  • 谢谢彼得。我不太清楚,我看到_Load_seq_cst_4 是内联的,但你是说_ReadWriteBarrier() 只是一个内联函数(什么都不做?)。今天早些时候,我读到了这项技术——blogs.oracle.com/d/compiler-memory-barriers——一个函数调用可以防止代码重新排序。这就是这里发生的一切吗?如果是这样,如链接文章所示,函数调用是一种非常昂贵的方式或防止重新排序......还是我误解了你?
  • @Wad:不,我是说_Load_seq_cst_4 本身是一个内联函数,所以你必须担心它的操作与它的父级中的其他东西重新排序。用一个例子更新了答案。
  • 好的,谢谢彼得回来。我已经多次阅读并重新阅读您的评论。你能澄清一下吗?如果这段代码是内联的,这意味着返回值可以直接写入局部变量。因此,如果没有障碍,我们最终可能会得到类似于some_local_variable = _Value; _Value = *_Tgt; **的代码,它在_Value 更新之前分配了局部变量_Value,对吗? **
  • @Wad:不,编译时重新排序不会等同于编写的源代码,因此“as-if”规则不允许这样做。 preshing.com/20120625/memory-ordering-at-compile-time
  • 好的。我实际上对 preshing 网站很熟悉,并且在很多场合都提到过它。如果没有编译器屏障,您能否举一个在_Load_seq_cst_4 的上下文中可能发生的编译器重新排序示例?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-04-11
  • 2013-02-22
  • 2018-11-15
  • 2017-12-17
  • 2010-11-01
相关资源
最近更新 更多