使用基本 seqlock的简化版本,gcc 在load(memory_order_seq_cst)
使用-O3
. 使用其他优化级别编译或使用 clang(甚至 on O3
)编译时,不会观察到这种重新排序。这种重新排序似乎违反了应该建立的同步关系,我很想知道为什么 gcc 会重新排序这个特定的负载,以及标准是否允许这样做。
考虑以下load
函数:
auto load()
{
std::size_t copy;
std::size_t seq0 = 0, seq1 = 0;
do
{
seq0 = seq_.load();
copy = value;
seq1 = seq_.load();
} while( seq0 & 1 || seq0 != seq1);
std::cout << "Observed: " << seq0 << '\n';
return copy;
}
在 seqlock 过程之后,此读取器旋转直到它能够加载 的两个实例seq_
,这两个实例被定义为 a std::atomic<std::size_t>
,它们是偶数(表示写入者当前未写入)和相等(表示写入者尚未写入value
在两个负载之间seq_
)。此外,由于这些负载被标记为memory_order_seq_cst
( 作为默认参数 ),我想该指令copy = value;
将在每次迭代时执行,因为它不能在初始负载中重新排序,也不能在后者之下重新排序。
但是,生成的程序集value
在第一次加载之前发出加载,seq_
甚至在循环之外执行。这可能导致不正确的同步或value
seqlock 算法无法解决的读取中断。此外,我注意到这只发生在 sizeof(value)
123 字节以下时。修改value
为某种类型 >= 123 字节会产生正确的程序集,并在每次循环迭代时在两次加载seq_
. 为什么这个看似任意的阈值决定了生成哪个程序集?
该测试工具 暴露了我的 Xeon E3-1505M 上的行为,其中将从阅读器打印“Observed: 2”并返回值 65535。观察到的值seq_
和返回的负载的组合value
似乎违反了应该由编写线程发布seq.store(2)
与memory_order_release
读取线程和读取线程建立的同步seq_
关系memory_order_seq_cst
。
gcc 对负载重新排序是否有效,如果是,为什么它只在sizeof(value)
< 123 时才这样做?clang,无论优化级别还是sizeof(value)
不会重新排序负载。我相信 Clang 的 codegen 是适当且正确的方法。