gpt4 book ai didi

c++ - GCC 使用 `memory_order_seq_cst` 跨负载重新排序。这是允许的吗?

转载 作者:可可西里 更新时间:2023-11-01 16:14:27 25 4
gpt4 key购买 nike

使用基本的简化版本 seqlock , gcc 将非原子负载重新排序到原子 load(memory_order_seq_cst)使用 -O3 编译代码时.当使用其他优化级别编译或使用 clang 编译时(即使在 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_ 的两个实例。 , 定义为 std::atomic<std::size_t> ,它们是偶数(表示作者当前未写入)和相等(表示作者尚未在 value 的两次加载之间写入 seq_)。此外,因为这些负载被标记为 memory_order_seq_cst (作为默认参数),我会想象指令 copy = value;将在每次迭代中执行,因为它不能在初始加载期间重新排序,也不能在后者之下重新排序。

然而,generated assemblyvalue 发出负载在第一次从 seq_ 加载之前甚至在循环之外执行。这可能导致不正确的同步或撕裂读取 value seqlock 算法无法解决的问题。此外,我注意到这仅在 sizeof(value) 时发生。小于 123 字节。修改 value某种类型 >= 123 字节会产生正确的程序集,并在 seq_ 的两次加载之间的每次循环迭代中加载.这个看似任意的阈值决定生成哪个程序集有什么原因吗?

This test harness暴露了我的 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 的代码生成是合适且正确的方法。

最佳答案

恭喜,我认为您在 gcc 中遇到了错误!

现在我认为你可以做出合理的论证,因为 other answer是的,您显示的原始代码可能可能已经被 gcc 以这种方式正确优化了通过依赖一个关于无条件访问 value 的相当模糊的论点: 本质上你不能一直依赖负载之间的同步关系 seq0 = seq_.load();以及随后读取的 value ,所以在“其他地方”阅读它不应该改变无竞争程序的语义。我实际上不确定这个论点,但这是我从减少代码中得到的一个“更简单”的案例:

#include <atomic>
#include <iostream>

std::atomic<std::size_t> seq_;
std::size_t value;

auto load()
{
std::size_t copy;
std::size_t seq0;
do
{
seq0 = seq_.load();
if (!seq0) continue;
copy = value;
seq0 = seq_.load();
} while (!seq0);

return copy;
}

这不是 seqlock或任何东西 - 它只是等待 seq0从零变为非零,然后读取 value . seq_的二读和while一样是多余的条件,但如果没有它们,错误就会消失。

现在这是众所周知的习语的读取端,它确实有效并且没有竞争:一个线程写入 value , 然后设置 seq0发布商店非零。线程调用 load查看非零存储,并与之同步,这样就可以安全地读取 value .当然,你不能一直写信给value ,这是“一次性”初始化,但这是一种常见模式。

通过上面的代码,gcc是still hoisting the read of value :

load():
mov rax, QWORD PTR value[rip]
.L2:
mov rdx, QWORD PTR seq_[rip]
test rdx, rdx
je .L2
mov rdx, QWORD PTR seq_[rip]
test rdx, rdx
je .L2
rep ret

糟糕!

此行为出现在 gcc 7.3 之前,但不会出现在 8.1 中。您的代码也可以在 8.1 中按照您的意愿进行编译:

    mov     rbx, QWORD PTR seq_[rip]
mov rbp, QWORD PTR value[rip]
mov rax, QWORD PTR seq_[rip]

关于c++ - GCC 使用 `memory_order_seq_cst` 跨负载重新排序。这是允许的吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/36958372/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com