[英]Can a compiler invent multiple loads when compiling one std::atomic load using memory_order_relaxed?
Taking the following piece of code采取以下代码
struct SharedMemory
{
std::atomic<float> value;
bool shouldReturnTrue()
{
float tmp = value.load(std::memory_order_relaxed);
float a = tmp;
float b = tmp;
return a == b;
}
};
Is it legal for the compiler to optimize it as follow and so break the thread safety?编译器如下优化它并破坏线程安全是否合法?
struct SharedMemory
{
std::atomic<float> value;
bool shouldReturnTrue()
{
float a = value.load(std::memory_order_relaxed);
float b = value.load(std::memory_order_relaxed);
return a == b;
}
};
If so, should I force it to not optimize tmp
using volatile
keyword as follow?如果是这样,我是否应该强制它不使用
volatile
关键字优化tmp
,如下所示?
struct SharedMemory
{
std::atomic<float> value;
bool shouldReturnTrue()
{
volatile float tmp = value.load(std::memory_order_relaxed);
float a = tmp;
float b = tmp;
return a == b;
}
};
No, that wouldn't be a legal optimization.不,那不是合法的优化。 As you say, it could return false for cases other than
tmp
being NaN, if there was a writer.正如您所说,如果有作者,它可能会在
tmp
为 NaN 以外的情况下返回 false。 Therefore it wouldn't be valid per the as-if rule, because the C++ abstract machine does always return !isnan(tmp)
.因此,根据 as-if 规则,它是无效的,因为 C++ 抽象机确实总是返回
!isnan(tmp)
。
(If you'd picked a type like int
where self == self
was always true, it would be easier to talk about!) (如果你选择了一个像
int
这样的类型,其中self == self
总是为真,那么谈论它会更容易!)
The transformation you ask about is only legal for non-atomic variables, precisely because the value can be assumed not to be changing.您询问的转换仅对非原子变量合法,正是因为可以假定该值不会改变。 If it is, that would be data-race undefined behaviour, and compilers are allowed to assume no UB.
如果是,那将是数据竞争未定义行为,并且允许编译器假设没有 UB。 Thus they can invent another non-atomic non-volatile load if that's more convenient for register allocation.
因此,如果寄存器分配更方便,他们可以发明另一种非原子非易失性负载。
But an atomic
variable does have to be assumed to change value between any two reads (in this way similar to volatile
in terms of what compilers have to assume about it), because that is well-defined behaviour that compilers have to respect.但是必须假定
atomic
变量在任意两次读取之间更改值(在这种方式下,就编译器必须假设的内容而言,类似于volatile
),因为这是编译器必须遵守的明确定义的行为。
So the compiler will never invent reads of atomic<>
objects .所以编译器永远不会发明
atomic<>
objects 的读取。 (That linked article is about Linux kernel code, where they use volatile
for hand-rolled atomics on compilers that are known to work the way they want , for the same reasons we use std::atomic<>
in modern C++.) (那篇链接的文章是关于 Linux kernel 代码的,他们在编译器上使用
volatile
来手动滚动原子,这些编译器以他们想要的方式工作,原因与我们在现代 C++ 中使用std::atomic<>
的原因相同。)
memory_order_relaxed
means that the ordering wrt. memory_order_relaxed
意味着订购wrt。 surrounding memory accesses is relaxed, not the atomicity guarantees.周围的 memory 访问是宽松的,不是原子性保证。 In this respect,
relaxed
isn't different from acquire
or seq_cst
.在这方面,
relaxed
与acquire
或seq_cst
没有什么不同。 I guess you had some misconception that relaxed
might be relevant here, but it isn't.我猜你有一些误解,认为
relaxed
可能与此相关,但事实并非如此。
You're right that assigning the load result to volatile float tmp
would make it less likely for a compiler to want to invent loads, but if that was a real problem (it isn't), a better way would be to use volatile atomic<float> value;
你是对的,将加载结果分配给
volatile float tmp
会使编译器不太可能想要发明加载,但如果这是一个真正的问题(不是),更好的方法是使用volatile atomic<float> value;
. . Again, this is not necessary .
同样,这不是必需的。 Especially now, since compilers currently don't optimize atomics at all ( Why don't compilers merge redundant std::atomic writes? ) but even in future with compilers that only give you the bare minimum ISO C++ standard and do optimize atomics by eg coalescing redundant back-to-back reads into one, they won't be allowed to do the other thing and invent extra writes, or invent extra reads and assume the value is the same.
特别是现在,因为编译器目前根本不优化原子( 为什么编译器不合并冗余的 std::atomic 写入? )但即使在未来编译器只给你最低限度的 ISO C++ 标准并通过例如优化原子将冗余的背靠背读取合并为一个,他们将不允许做另一件事并发明额外的写入,或发明额外的读取并假设值相同。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.