gpt4 book ai didi

c++ - 在C++ 11中是否有任何等于asm (“”::: “memory”)的编译器障碍?

转载 作者:太空狗 更新时间:2023-10-29 20:21:55 27 4
gpt4 key购买 nike

我的测试代码如下,我发现只有memory_order_seq_cst禁止编译器重新排序。

#include <atomic>

using namespace std;

int A, B = 1;

void func(void) {
A = B + 1;
atomic_thread_fence(memory_order_seq_cst);
B = 0;
}

其他选择,例如 memory_order_releasememory_order_acq_rel根本不会产生任何编译器障碍。

我认为它们必须使用原子变量,如下所示。
#include <atomic>

using namespace std;

atomic<int> A(0);
int B = 1;

void func(void) {
A.store(B+1, memory_order_release);
B = 0;
}

但是我不想使用原子变量。同时,我认为“asm(”“:::” memory“)”的级别太低。

还有什么更好的选择吗?

最佳答案

回复:您的编辑:

But I do not want to use atomic variable.



为什么不?如果出于性能原因,请将它们与 memory_order_relaxedatomic_signal_fence(mo_whatever)一起使用,以阻止编译器重新排序,而不会产生任何运行时开销,除了编译器障碍可能会阻止某些编译时优化外,具体取决于周围的代码。

如果是由于其他原因,那么 atomic_signal_fence可能会为您提供恰好在目标平台上工作的代码。我怀疑它确实对非 atomic<>加载和/或存储进行排序,因此它甚至可能有助于避免C++中的数据争用未定义行为。

足够做什么?

不管有什么障碍,如果两个线程同时运行此函数,则由于并发访问non- atomic<>变量,您的程序将具有“未定义行为”。因此,此代码唯一有用的方法是,如果您要与在同一线程中运行的信号处理程序进行同步。

这也与要求“编译器屏障”相一致,仅防止在编译时进行重新排序,因为乱序执行和内存重新排序始终会保留单个线程的行为。因此,您永远不需要额外的障碍说明来确保按程序顺序看到自己的操作,只需要在编译时停止编译器的重新排序即可。参见Jeff Preshing的文章: Memory Ordering at Compile Time

这就是 atomic_signal_fence 代表。您可以将其与任何 std::memory_order一起使用,就像thread_fence一样,以获得不同的屏障强度,并且仅阻止需要阻止的优化。

... atomic_thread_fence(memory_order_acq_rel) did not generate any compiler barrier at all!



完全错误,有几种方式。
atomic_thread_fence是编译器障碍,加上任何必需的运行时障碍,以限制加载/存储对其他线程可见的顺序来限制重新排序。

我猜您的意思是,当您查看x86的asm输出时,它没有发出任何屏障指令。诸如x86的MFENCE之类的指令不是“编译器壁垒”,它们是运行时内存壁垒,甚至可以防止在运行时对StoreLoad进行重新排序。 (这是x86允许的唯一重新排序。只有在使用弱序(NT)存储(例如 MOVNTPS ( _mm_stream_ps ))时才需要SFENCE和LFENCE。)

在像ARM这样的弱排序ISA上,thread_fence(mo_acq_rel)不是免费的,而是编译为指令。 gcc5.4使用 dmb ish。 (请参阅 Godbolt compiler explorer)。

编译器屏障仅阻止编译时重新排序,而不必阻止运行时重新排序。因此,即使在ARM上, atomic_signal_fence(mo_seq_cst)也不会编译为任何指令。

足够弱的屏障允许编译器在需要时先将存储区中的 B进行存储,然后再存储在 A中,但是gcc碰巧决定仍然以源顺序进行存储,即使使用thread_fence(mo_acquire)(也不应该与其他存储区一起对存储进行排序)商店)。

因此,此示例并未真正测试某些东西是否成为编译器障碍。

来自gcc的奇怪编译器行为,例如与编译器障碍不同的示例:

See this source+asm on Godbolt
#include <atomic>
using namespace std;
int A,B;

void foo() {
A = 0;
atomic_thread_fence(memory_order_release);
B = 1;
//asm volatile(""::: "memory");
//atomic_signal_fence(memory_order_release);
atomic_thread_fence(memory_order_release);
A = 2;
}

这可以按照您期望的方式用clang编译:thread_fence是StoreStore的障碍,因此A = 0必须在B = 1之前发生,并且不能与A = 2合并。
    # clang3.9 -O3
mov dword ptr [rip + A], 0
mov dword ptr [rip + B], 1
mov dword ptr [rip + A], 2
ret

但是使用gcc时,屏障不会起作用,并且asm输出中仅存在A的最终存储。
    # gcc6.2 -O3
mov DWORD PTR B[rip], 1
mov DWORD PTR A[rip], 2
ret

但是使用 atomic_signal_fence(memory_order_release),gcc的输出与clang匹配。 因此,atomic_signal_fence(mo_release)具有我们所期望的屏障效果,但是比seq_cst弱的atomic_thread_fence根本不充当编译器屏障。

这里的一种理论是,gcc知道多个线程写入非 atomic<>变量是正式的Undefined Behavior。这并没有太多用处,因为如果 atomic_thread_fence用于与信号处理程序同步,它仍然可以工作,它比必要的要强大。

顺便说一句,使用 atomic_thread_fence(memory_order_seq_cst),我们得到了预期的结果
    # gcc6.2 -O3, with a mo_seq_cst barrier
mov DWORD PTR A[rip], 0
mov DWORD PTR B[rip], 1
mfence
mov DWORD PTR A[rip], 2
ret

即使只有一个障碍,我们也能做到这一点,这仍然允许A = 0和A = 2存储一个接一个地发生,因此允许编译器将它们合并成一个障碍。 (观察者可能看不到单独的A = 0和A = 2值,这是可能的顺序,因此编译器可以确定总是发生这种情况)。不过,当前的编译器通常不会进行这种优化。请参阅我对 Can num++ be atomic for 'int num'?的回答末尾的讨论。

关于c++ - 在C++ 11中是否有任何等于asm (“”::: “memory”)的编译器障碍?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/40579342/

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