我想知道为什么没有编译器准备将相同值的连续写入合并到单个原子变量,例如:
#include <atomic>
std::atomic<int> y(0);
void f() {
auto order = std::memory_order_relaxed;
y.store(1, order);
y.store(1, order);
y.store(1, order);
}
Run Code Online (Sandbox Code Playgroud)
我尝试过的每个编译器都会发出三次上面的编写.什么合法的,无种族的观察者可以看到上述代码与具有单次写入的优化版本之间的差异(即,不是"假设"规则适用)?
如果变量是易变的,那么显然不适用优化.在我的情况下有什么阻止它?
这是编译器资源管理器中的代码.
在这种情况下,两个负载会合二为一吗?如果这是依赖于架构的,那么说英特尔的现代处理器会是什么情况?我相信原子负载相当于英特尔处理器中的正常负载。
void run1() {
auto a = atomic_var.load(std::memory_order_relaxed);
auto b = atomic_var.load(std::memory_order_relaxed);
// Some code using a and b;
}
void run2() {
if (atomic_var.load(std::memory_order_relaxed) == 2 && /*some conditions*/ ...) {
if (atomic_var.load(std::memory_order_relaxed) * somevar > 3) {
/*...*/
}
}
}
Run Code Online (Sandbox Code Playgroud)
run1()并且run2()只是使用相同原子变量的两个负载的两个场景。编译器能否将这种两种加载的场景合并为一种加载并重用它?
为什么GCC和Clang会为此代码(x86_64,-O3 -std = c ++ 17)生成如此不同的asm?
#include <atomic>
int global_var = 0;
int foo_seq_cst(int a)
{
std::atomic<int> ia;
ia.store(global_var + a, std::memory_order_seq_cst);
return ia.load(std::memory_order_seq_cst);
}
int foo_relaxed(int a)
{
std::atomic<int> ia;
ia.store(global_var + a, std::memory_order_relaxed);
return ia.load(std::memory_order_relaxed);
}
Run Code Online (Sandbox Code Playgroud)
GCC 9.1:
foo_seq_cst(int):
add edi, DWORD PTR global_var[rip]
mov DWORD PTR [rsp-4], edi
mfence
mov eax, DWORD PTR [rsp-4]
ret
foo_relaxed(int):
add edi, DWORD PTR global_var[rip]
mov DWORD PTR [rsp-4], edi
mov eax, DWORD PTR [rsp-4]
ret
Run Code Online (Sandbox Code Playgroud)
铛8.0:
foo_seq_cst(int): # @foo_seq_cst(int)
mov …Run Code Online (Sandbox Code Playgroud) 事实证明,所有(?)编译器都将其视为std::atomic::load(std::memory_order_relaxed)易失性负载(通过__iso_volatile_load64等)。
他们根本不优化或重新排序。即使丢弃加载的值仍然会生成加载指令,因为编译器将其视为可能产生副作用。
因此,松弛载荷不是最佳的。照这样说...
假设p指向共享内存中一个单调递增的 8 字节计数器,该计数器仅写入我的进程之外。我的程序只从这个地址读取。
我想以这样的方式读取这个计数器:
负载是原子的(无撕裂)
该计数器保留顺序(因此这x = *p; y = *p;意味着x <= y)
负载不被视为不透明/优化障碍(除了上面的#2)
特别是,这里的目的是编译器在正常内存访问时执行尽可能多的优化,例如:无用的加载(如(void)*p;)被丢弃,其他指令围绕此内存访问自由重新排序,等等。
除了使用易失性负载之外,还有什么方法可以在 MSVC 或 Clang 上实现此目的吗?
(特定于实现的黑客/内在函数/等都可以,只要这些特定的实现永远不会将其视为未定义的行为,因此不存在错误代码生成的风险。)