标准 C++11 是否保证 memory_order_seq_cst 防止 StoreLoad 围绕原子对非原子重新排序?
Does standard C++11 guarantee that memory_order_seq_cst prevents StoreLoad reordering of non-atomic around an atomic?
标准 C++11 是否保证 memory_order_seq_cst
防止 StoreLoad 围绕非原子内存访问的原子操作重新排序?
众所周知,C++11 中有 6 个 std::memory_order
,它指定 如何定期、非原子 内存访问围绕一个原子操作 - 工作草案,C++ 编程语言标准 2016-07-12:http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2016/n4606.pdf
§ 29.3 Order and consistency
§ 29.3 / 1
The enumeration memory_order specifies the detailed regular
(non-atomic) memory synchronization order as defined in 1.10 and may
provide for operation ordering. Its enumerated values and their
meanings are as follows:
另外,这 6 个 memory_orders 阻止了其中一些重新排序:
但是,memory_order_seq_cst
是否会阻止 StoreLoad 针对 常规非原子 内存访问的原子操作重新排序,或者仅针对具有相同 [=14= 的其他原子操作]?
即为了防止这种 StoreLoad 重新排序,我们应该对 STORE 和 LOAD 都使用 std::memory_order_seq_cst
,还是只对其中之一使用?
std::atomic<int> a, b;
b.store(1, std::memory_order_seq_cst); // Sequential Consistency
a.load(std::memory_order_seq_cst); // Sequential Consistency
关于 Acquire-Release 语义很清楚,它指定跨原子操作的非原子内存访问重新排序:http://en.cppreference.com/w/cpp/atomic/memory_order
为了防止 StoreLoad 重新排序,我们应该使用 std::memory_order_seq_cst
。
两个例子:
std::memory_order_seq_cst
对于 STORE 和 LOAD:有 MFENCE
StoreLoad 无法重新排序 - GCC 6.1.0 x86_64:https://godbolt.org/g/mVZJs0
std::atomic<int> a, b;
b.store(1, std::memory_order_seq_cst); // can't be executed after LOAD
a.load(std::memory_order_seq_cst); // can't be executed before STORE
std::memory_order_seq_cst
仅用于 LOAD:没有 MFENCE
StoreLoad 可以重新排序 - GCC 6.1.0 x86_64:https://godbolt.org/g/2NLy12
std::atomic<int> a, b;
b.store(1, std::memory_order_release); // can be executed after LOAD
a.load(std::memory_order_seq_cst); // can be executed before STORE
此外,如果 C/C++-编译器使用 C/C++11 到 x86 的替代映射,它会在加载之前刷新存储缓冲区:MFENCE,MOV (from memory)
,因此我们必须使用std::memory_order_seq_cst
也用于 LOAD:http://www.cl.cam.ac.uk/~pes20/cpp/cpp0xmappings.html As this example is discussed in another question as approach (3): Does it make any sense instruction LFENCE in processors x86/x86_64?
即我们应该对 STORE 和 LOAD 使用 std::memory_order_seq_cst
以生成 MFENCE
保证,以防止 StoreLoad 重新排序。
对于原子加载或存储,memory_order_seq_cst
是真的吗:
specifi Acquire-Release 语义 - 防止:LoadLoad、LoadStore、StoreStore 围绕原子操作重新排序 常规、非原子 内存访问,
但阻止 StoreLoad 围绕原子操作重新排序仅适用于具有相同 memory_order_seq_cst
?
[=87 的其他原子 操作=]
std::memory_order_seq_cst
gua运行tee 没有被编译器和 cpu 重新排序。在这种情况下,相同的内存顺序就好像一次只执行一条指令。
但是编译器优化混淆了问题,如果关闭-O3 则围栏是there。
编译器可以看到在你的带有 -O3 的测试程序中没有 mfence 的结果,因为程序太简单了。
如果你 运行 它在另一方面像 this 一样的手臂上,你可以看到障碍物 dmb ish
.
因此,如果您的程序更复杂,您可能会在这部分代码中看到 mfence
,但如果编译器可以分析并推断不需要它,则不会。
不,标准 C++11 不 保证 memory_order_seq_cst
防止 StoreLoad 重新排序 non-atomic
围绕 atomic(seq_cst)
.
即使是标准的 C++11 也不能 保证 memory_order_seq_cst
会阻止 StoreLoad 对 atomic(non-seq_cst)
的重新排序围绕 atomic(seq_cst)
.
工作草案,C++ 编程语言标准 2016-07-12:http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2016/n4606.pdf
- 所有
memory_order_seq_cst
操作都应有一个总顺序 S - C++11 标准:
§ 29.3
3
There shall be a single total order S on all memory_order_seq_cst
operations, consistent with the “happens before” order and
modification orders for all affected locations, such that each
memory_order_seq_cst operation B that loads a value from an atomic
object M observes one of the following values: ...
- 但是,任何排序弱于
memory_order_seq_cst
的原子操作都没有顺序一致性,也没有单一的总顺序,即非 memory_order_seq_cst
操作可以用 memory_order_seq_cst
重新排序允许方向上的操作 - C++11 标准:
§ 29.3
8 [ Note: memory_order_seq_cst ensures sequential consistency
only for a program that is free of data races and uses exclusively
memory_order_seq_cst operations. Any use of weaker ordering will
invalidate this guarantee unless extreme care is used. In particular,
memory_order_seq_cst fences ensure a total order only for the fences
themselves. Fences cannot, in general, be used to restore sequential
consistency for atomic operations with weaker ordering specifications.
— end note ]
C++ 编译器也允许这样的重新排序:
- 开x86_64
通常 - 如果在编译器中 seq_cst 在存储后实现为屏障,则:
STORE-C(relaxed);
LOAD-B(seq_cst);
可以重新排序为 LOAD-B(seq_cst);
STORE-C(relaxed);
GCC 7.0生成的Asm截图x86_64:https://godbolt.org/g/4yyeby
此外,理论上可行 - 如果在编译器中 seq_cst 在加载前实现为屏障,则:
STORE-A(seq_cst);
LOAD-C(acq_rel);
可以重新排序为 LOAD-C(acq_rel);
STORE-A(seq_cst);
- 在 PowerPC 上
STORE-A(seq_cst);
LOAD-C(relaxed);
可以重新排序为 LOAD-C(relaxed);
STORE-A(seq_cst);
在 PowerPC 上也可以这样重新排序:
STORE-A(seq_cst);
STORE-C(relaxed);
可以重新排序为 STORE-C(relaxed);
STORE-A(seq_cst);
如果连原子变量都可以跨原子(seq_cst)重新排序,那么非原子变量也可以跨原子(seq_cst)重新排序。
GCC 4.8 PowerPC 生成的 Asm 截图:https://godbolt.org/g/BTQBr8
更多详情:
- 开x86_64
STORE-C(release);
LOAD-B(seq_cst);
可以重新排序为 LOAD-B(seq_cst);
STORE-C(release);
Intel® 64 and IA-32 Architectures
8.2.3.4 Loads May Be Reordered with Earlier Stores to Different Locations
即x86_64代码:
STORE-A(seq_cst);
STORE-C(release);
LOAD-B(seq_cst);
可以重新订购为:
STORE-A(seq_cst);
LOAD-B(seq_cst);
STORE-C(release);
这可能发生,因为 c.store
和 b.load
之间不是 mfence
:
x86_64 - GCC 7.0:https://godbolt.org/g/dRGTaO
C++ 和 asm - 代码:
#include <atomic>
// Atomic load-store
void test() {
std::atomic<int> a, b, c;
a.store(2, std::memory_order_seq_cst); // movl 2,[a]; mfence;
c.store(4, std::memory_order_release); // movl 4,[c];
int tmp = b.load(std::memory_order_seq_cst); // movl [b],[tmp];
}
可以重新排序为:
#include <atomic>
// Atomic load-store
void test() {
std::atomic<int> a, b, c;
a.store(2, std::memory_order_seq_cst); // movl 2,[a]; mfence;
int tmp = b.load(std::memory_order_seq_cst); // movl [b],[tmp];
c.store(4, std::memory_order_release); // movl 4,[c];
}
此外,x86/x86_64中的顺序一致性可以通过四种方式实现:http://www.cl.cam.ac.uk/~pes20/cpp/cpp0xmappings.html
LOAD
(without fence) and STORE
+ MFENCE
LOAD
(without fence) and LOCK XCHG
MFENCE
+ LOAD
and STORE
(without fence)
LOCK XADD
( 0 ) and STORE
(without fence)
- 1 和 2 种方式:
LOAD
和 (STORE
+MFENCE
)/(LOCK XCHG
) - 我们在上面回顾过
- 3 和 4 种方式:(
MFENCE
+LOAD
)/LOCK XADD
和 STORE
- 允许下一次重新排序:
STORE-A(seq_cst);
LOAD-C(acq_rel);
可以重新排序为 LOAD-C(acq_rel);
STORE-A(seq_cst);
- 在 PowerPC 上
STORE-A(seq_cst);
LOAD-C(relaxed);
可以重新排序为 LOAD-C(relaxed);
STORE-A(seq_cst);
允许存储加载重新排序(Table 5 - PowerPC):http://www.rdrop.com/users/paulmck/scalability/paper/whymb.2010.06.07c.pdf
Stores Reordered After Loads
即PowerPC 代码:
STORE-A(seq_cst);
STORE-C(relaxed);
LOAD-C(relaxed);
LOAD-B(seq_cst);
可以重新订购为:
LOAD-C(relaxed);
STORE-A(seq_cst);
STORE-C(relaxed);
LOAD-B(seq_cst);
PowerPC - GCC 4.8:https://godbolt.org/g/xowFD3
C++ 和 asm - 代码:
#include <atomic>
// Atomic load-store
void test() {
std::atomic<int> a, b, c; // addr: 20, 24, 28
a.store(2, std::memory_order_seq_cst); // li r9<-2; sync; stw r9->[a];
c.store(4, std::memory_order_relaxed); // li r9<-4; stw r9->[c];
c.load(std::memory_order_relaxed); // lwz r9<-[c];
int tmp = b.load(std::memory_order_seq_cst); // sync; lwz r9<-[b]; ... isync;
}
通过将 a.store
分成两部分 - 它可以重新排序为:
#include <atomic>
// Atomic load-store
void test() {
std::atomic<int> a, b, c; // addr: 20, 24, 28
//a.store(2, std::memory_order_seq_cst); // part-1: li r9<-2; sync;
c.load(std::memory_order_relaxed); // lwz r9<-[c];
a.store(2, std::memory_order_seq_cst); // part-2: stw r9->[a];
c.store(4, std::memory_order_relaxed); // li r9<-4; stw r9->[c];
int tmp = b.load(std::memory_order_seq_cst); // sync; lwz r9<-[b]; ... isync;
}
从内存加载 lwz r9<-[c];
比存储到内存 stw r9->[a];
更早执行。
在 PowerPC 上也可以这样重新排序:
STORE-A(seq_cst);
STORE-C(relaxed);
可以重新排序为 STORE-C(relaxed);
STORE-A(seq_cst);
因为 PowerPC 的内存排序模型较弱 - 允许 Store-Store 重新排序 (Table 5 - PowerPC): http://www.rdrop.com/users/paulmck/scalability/paper/whymb.2010.06.07c.pdf
Stores Reordered After Stores
即在 PowerPC 操作上,Store 可以与其他 Store 一起重新排序,那么前面的示例可以重新排序,例如:
#include <atomic>
// Atomic load-store
void test() {
std::atomic<int> a, b, c; // addr: 20, 24, 28
//a.store(2, std::memory_order_seq_cst); // part-1: li r9<-2; sync;
c.load(std::memory_order_relaxed); // lwz r9<-[c];
c.store(4, std::memory_order_relaxed); // li r9<-4; stw r9->[c];
a.store(2, std::memory_order_seq_cst); // part-2: stw r9->[a];
int tmp = b.load(std::memory_order_seq_cst); // sync; lwz r9<-[b]; ... isync;
}
其中存储到内存 stw r9->[c];
比存储到内存 stw r9->[a];
执行得早。
标准 C++11 是否保证 memory_order_seq_cst
防止 StoreLoad 围绕非原子内存访问的原子操作重新排序?
众所周知,C++11 中有 6 个 std::memory_order
,它指定 如何定期、非原子 内存访问围绕一个原子操作 - 工作草案,C++ 编程语言标准 2016-07-12:http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2016/n4606.pdf
§ 29.3 Order and consistency
§ 29.3 / 1
The enumeration memory_order specifies the detailed regular (non-atomic) memory synchronization order as defined in 1.10 and may provide for operation ordering. Its enumerated values and their meanings are as follows:
另外,这 6 个 memory_orders 阻止了其中一些重新排序:
但是,memory_order_seq_cst
是否会阻止 StoreLoad 针对 常规非原子 内存访问的原子操作重新排序,或者仅针对具有相同 [=14= 的其他原子操作]?
即为了防止这种 StoreLoad 重新排序,我们应该对 STORE 和 LOAD 都使用 std::memory_order_seq_cst
,还是只对其中之一使用?
std::atomic<int> a, b;
b.store(1, std::memory_order_seq_cst); // Sequential Consistency
a.load(std::memory_order_seq_cst); // Sequential Consistency
关于 Acquire-Release 语义很清楚,它指定跨原子操作的非原子内存访问重新排序:http://en.cppreference.com/w/cpp/atomic/memory_order
为了防止 StoreLoad 重新排序,我们应该使用 std::memory_order_seq_cst
。
两个例子:
std::memory_order_seq_cst
对于 STORE 和 LOAD:有MFENCE
StoreLoad 无法重新排序 - GCC 6.1.0 x86_64:https://godbolt.org/g/mVZJs0
std::atomic<int> a, b;
b.store(1, std::memory_order_seq_cst); // can't be executed after LOAD
a.load(std::memory_order_seq_cst); // can't be executed before STORE
std::memory_order_seq_cst
仅用于 LOAD:没有MFENCE
StoreLoad 可以重新排序 - GCC 6.1.0 x86_64:https://godbolt.org/g/2NLy12
std::atomic<int> a, b;
b.store(1, std::memory_order_release); // can be executed after LOAD
a.load(std::memory_order_seq_cst); // can be executed before STORE
此外,如果 C/C++-编译器使用 C/C++11 到 x86 的替代映射,它会在加载之前刷新存储缓冲区:MFENCE,MOV (from memory)
,因此我们必须使用std::memory_order_seq_cst
也用于 LOAD:http://www.cl.cam.ac.uk/~pes20/cpp/cpp0xmappings.html As this example is discussed in another question as approach (3): Does it make any sense instruction LFENCE in processors x86/x86_64?
即我们应该对 STORE 和 LOAD 使用 std::memory_order_seq_cst
以生成 MFENCE
保证,以防止 StoreLoad 重新排序。
对于原子加载或存储,memory_order_seq_cst
是真的吗:
specifi Acquire-Release 语义 - 防止:LoadLoad、LoadStore、StoreStore 围绕原子操作重新排序 常规、非原子 内存访问,
但阻止 StoreLoad 围绕原子操作重新排序仅适用于具有相同
[=87 的其他原子 操作=]memory_order_seq_cst
?
std::memory_order_seq_cst
gua运行tee 没有被编译器和 cpu 重新排序。在这种情况下,相同的内存顺序就好像一次只执行一条指令。
但是编译器优化混淆了问题,如果关闭-O3 则围栏是there。
编译器可以看到在你的带有 -O3 的测试程序中没有 mfence 的结果,因为程序太简单了。
如果你 运行 它在另一方面像 this 一样的手臂上,你可以看到障碍物 dmb ish
.
因此,如果您的程序更复杂,您可能会在这部分代码中看到 mfence
,但如果编译器可以分析并推断不需要它,则不会。
不,标准 C++11 不 保证 memory_order_seq_cst
防止 StoreLoad 重新排序 non-atomic
围绕 atomic(seq_cst)
.
即使是标准的 C++11 也不能 保证 memory_order_seq_cst
会阻止 StoreLoad 对 atomic(non-seq_cst)
的重新排序围绕 atomic(seq_cst)
.
工作草案,C++ 编程语言标准 2016-07-12:http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2016/n4606.pdf
- 所有
memory_order_seq_cst
操作都应有一个总顺序 S - C++11 标准:
§ 29.3
3
There shall be a single total order S on all memory_order_seq_cst operations, consistent with the “happens before” order and modification orders for all affected locations, such that each memory_order_seq_cst operation B that loads a value from an atomic object M observes one of the following values: ...
- 但是,任何排序弱于
memory_order_seq_cst
的原子操作都没有顺序一致性,也没有单一的总顺序,即非memory_order_seq_cst
操作可以用memory_order_seq_cst
重新排序允许方向上的操作 - C++11 标准:
§ 29.3
8 [ Note: memory_order_seq_cst ensures sequential consistency only for a program that is free of data races and uses exclusively memory_order_seq_cst operations. Any use of weaker ordering will invalidate this guarantee unless extreme care is used. In particular, memory_order_seq_cst fences ensure a total order only for the fences themselves. Fences cannot, in general, be used to restore sequential consistency for atomic operations with weaker ordering specifications. — end note ]
C++ 编译器也允许这样的重新排序:
- 开x86_64
通常 - 如果在编译器中 seq_cst 在存储后实现为屏障,则:
STORE-C(relaxed);
LOAD-B(seq_cst);
可以重新排序为 LOAD-B(seq_cst);
STORE-C(relaxed);
GCC 7.0生成的Asm截图x86_64:https://godbolt.org/g/4yyeby
此外,理论上可行 - 如果在编译器中 seq_cst 在加载前实现为屏障,则:
STORE-A(seq_cst);
LOAD-C(acq_rel);
可以重新排序为 LOAD-C(acq_rel);
STORE-A(seq_cst);
- 在 PowerPC 上
STORE-A(seq_cst);
LOAD-C(relaxed);
可以重新排序为 LOAD-C(relaxed);
STORE-A(seq_cst);
在 PowerPC 上也可以这样重新排序:
STORE-A(seq_cst);
STORE-C(relaxed);
可以重新排序为 STORE-C(relaxed);
STORE-A(seq_cst);
如果连原子变量都可以跨原子(seq_cst)重新排序,那么非原子变量也可以跨原子(seq_cst)重新排序。
GCC 4.8 PowerPC 生成的 Asm 截图:https://godbolt.org/g/BTQBr8
更多详情:
- 开x86_64
STORE-C(release);
LOAD-B(seq_cst);
可以重新排序为 LOAD-B(seq_cst);
STORE-C(release);
Intel® 64 and IA-32 Architectures
8.2.3.4 Loads May Be Reordered with Earlier Stores to Different Locations
即x86_64代码:
STORE-A(seq_cst);
STORE-C(release);
LOAD-B(seq_cst);
可以重新订购为:
STORE-A(seq_cst);
LOAD-B(seq_cst);
STORE-C(release);
这可能发生,因为 c.store
和 b.load
之间不是 mfence
:
x86_64 - GCC 7.0:https://godbolt.org/g/dRGTaO
C++ 和 asm - 代码:
#include <atomic>
// Atomic load-store
void test() {
std::atomic<int> a, b, c;
a.store(2, std::memory_order_seq_cst); // movl 2,[a]; mfence;
c.store(4, std::memory_order_release); // movl 4,[c];
int tmp = b.load(std::memory_order_seq_cst); // movl [b],[tmp];
}
可以重新排序为:
#include <atomic>
// Atomic load-store
void test() {
std::atomic<int> a, b, c;
a.store(2, std::memory_order_seq_cst); // movl 2,[a]; mfence;
int tmp = b.load(std::memory_order_seq_cst); // movl [b],[tmp];
c.store(4, std::memory_order_release); // movl 4,[c];
}
此外,x86/x86_64中的顺序一致性可以通过四种方式实现:http://www.cl.cam.ac.uk/~pes20/cpp/cpp0xmappings.html
LOAD
(without fence) andSTORE
+MFENCE
LOAD
(without fence) andLOCK XCHG
MFENCE
+LOAD
andSTORE
(without fence)LOCK XADD
( 0 ) andSTORE
(without fence)
- 1 和 2 种方式:
LOAD
和 (STORE
+MFENCE
)/(LOCK XCHG
) - 我们在上面回顾过 - 3 和 4 种方式:(
MFENCE
+LOAD
)/LOCK XADD
和STORE
- 允许下一次重新排序:
STORE-A(seq_cst);
LOAD-C(acq_rel);
可以重新排序为 LOAD-C(acq_rel);
STORE-A(seq_cst);
- 在 PowerPC 上
STORE-A(seq_cst);
LOAD-C(relaxed);
可以重新排序为 LOAD-C(relaxed);
STORE-A(seq_cst);
允许存储加载重新排序(Table 5 - PowerPC):http://www.rdrop.com/users/paulmck/scalability/paper/whymb.2010.06.07c.pdf
Stores Reordered After Loads
即PowerPC 代码:
STORE-A(seq_cst);
STORE-C(relaxed);
LOAD-C(relaxed);
LOAD-B(seq_cst);
可以重新订购为:
LOAD-C(relaxed);
STORE-A(seq_cst);
STORE-C(relaxed);
LOAD-B(seq_cst);
PowerPC - GCC 4.8:https://godbolt.org/g/xowFD3
C++ 和 asm - 代码:
#include <atomic>
// Atomic load-store
void test() {
std::atomic<int> a, b, c; // addr: 20, 24, 28
a.store(2, std::memory_order_seq_cst); // li r9<-2; sync; stw r9->[a];
c.store(4, std::memory_order_relaxed); // li r9<-4; stw r9->[c];
c.load(std::memory_order_relaxed); // lwz r9<-[c];
int tmp = b.load(std::memory_order_seq_cst); // sync; lwz r9<-[b]; ... isync;
}
通过将 a.store
分成两部分 - 它可以重新排序为:
#include <atomic>
// Atomic load-store
void test() {
std::atomic<int> a, b, c; // addr: 20, 24, 28
//a.store(2, std::memory_order_seq_cst); // part-1: li r9<-2; sync;
c.load(std::memory_order_relaxed); // lwz r9<-[c];
a.store(2, std::memory_order_seq_cst); // part-2: stw r9->[a];
c.store(4, std::memory_order_relaxed); // li r9<-4; stw r9->[c];
int tmp = b.load(std::memory_order_seq_cst); // sync; lwz r9<-[b]; ... isync;
}
从内存加载 lwz r9<-[c];
比存储到内存 stw r9->[a];
更早执行。
在 PowerPC 上也可以这样重新排序:
STORE-A(seq_cst);
STORE-C(relaxed);
可以重新排序为 STORE-C(relaxed);
STORE-A(seq_cst);
因为 PowerPC 的内存排序模型较弱 - 允许 Store-Store 重新排序 (Table 5 - PowerPC): http://www.rdrop.com/users/paulmck/scalability/paper/whymb.2010.06.07c.pdf
Stores Reordered After Stores
即在 PowerPC 操作上,Store 可以与其他 Store 一起重新排序,那么前面的示例可以重新排序,例如:
#include <atomic>
// Atomic load-store
void test() {
std::atomic<int> a, b, c; // addr: 20, 24, 28
//a.store(2, std::memory_order_seq_cst); // part-1: li r9<-2; sync;
c.load(std::memory_order_relaxed); // lwz r9<-[c];
c.store(4, std::memory_order_relaxed); // li r9<-4; stw r9->[c];
a.store(2, std::memory_order_seq_cst); // part-2: stw r9->[a];
int tmp = b.load(std::memory_order_seq_cst); // sync; lwz r9<-[b]; ... isync;
}
其中存储到内存 stw r9->[c];
比存储到内存 stw r9->[a];
执行得早。