【发布时间】:2016-02-05 06:11:24
【问题描述】:
我在查看 compiler output of rmw atomics from gcc 时发现了一些奇怪的东西 - 在 Aarch64 上,诸如 fetch_add 之类的 rmw 操作可以通过宽松的负载进行部分重新排序。
在 Aarch64 上,可能会为value.fetch_add(1, seq_cst) 生成以下代码
.L1:
ldaxr x1, [x0]
add x1, x1, 1
stlxr w2, x1, [x0]
cbnz L1
但是,发生在 ldaxr 之前的加载和存储可能会被重新排序,而不是发生在 stlxr 之后的加载和加载/存储(请参阅here)。 GCC 没有添加栅栏来防止这种情况 - 这是一小段代码展示了这一点:
void partial_reorder(std::atomic<uint64_t> loader, std::atomic<uint64_t> adder) {
loader.load(std::memory_order_relaxed); // can be reordered past the ldaxr
adder.fetch_add(1, std::memory_order_seq_cst);
loader.load(std::memory_order_relaxed); // can be reordered past the stlxr
}
生成
partial_reorder(std::atomic<int>, std::atomic<int>):
ldr w2, [x0] @ reordered down
.L2:
ldaxr w2, [x1]
add w2, w2, 1
stlxr w3, w2, [x1]
cbnz w3, .L2
ldr w0, [x0] @ reordered up
ret
实际上,可以使用 RMW 操作对负载进行部分重新排序 - 它们发生在中间。
那么,有什么大不了的?我在问什么?
原子操作本身可以被整除似乎很奇怪。我在标准中找不到任何阻止这一点的东西,但我相信存在隐含操作不可分割的规则组合。
这似乎不尊重获取顺序。如果我在此操作之后直接执行加载,我可以看到 fetch_add 和后面的操作之间的存储加载或存储存储重新排序,这意味着后面的内存访问至少部分在获取操作之后重新排序。同样,我在标准中找不到任何明确说明不允许的内容,并且获取是加载顺序,但我的理解是获取操作适用于整个操作,而不仅仅是部分操作。类似的情况也适用于通过 ldaxr 重新排序的发布。
这可能会进一步扩展排序定义,但 seq_cst 操作之前和之后的两个操作可以相互重新排序似乎是无效的。如果边界操作都重新排序到操作的中间,然后相互超越,这可能(?)发生。
【问题讨论】:
-
可能相关:bigflake.com/seq_cst.cpp 显示用于基本原子加载/存储的 x64/arm32/aarch64 的编译器输出。
store(val, std::memory_order_seq_cst)似乎缺少障碍。这是 Android 上的 gcc 4.9。
标签: c++ multithreading c++11 concurrency atomic