我尝试使用共享索引来指示数据已写入共享循环缓冲区。有没有一种有效的方法可以在 ARM(arm gcc 9.3.1 for cortex M4 with -O3)上执行此操作,而不使用 discouraged
volatile
关键字?
以下 C 函数在 x86 上运行良好:
void Test1(int volatile* x) { *x = 5; }
void Test2(int* x) { __atomic_store_n(x, 5, __ATOMIC_RELEASE); }
两者在 x86 上的编译效率相同且相同:
0000000000000000 <Test1>:
0: c7 07 05 00 00 00 movl $0x5,(%rdi)
6: c3 retq
7: 66 0f 1f 84 00 00 00 nopw 0x0(%rax,%rax,1)
e: 00 00
0000000000000010 <Test2>:
10: c7 07 05 00 00 00 movl $0x5,(%rdi)
16: c3 retq
但是,在 ARM 上,
__atomic
内置函数会生成 数据内存屏障,而 volatile
则不会:
00000000 <Test1>:
0: 2305 movs r3, #5
2: 6003 str r3, [r0, #0]
4: 4770 bx lr
6: bf00 nop
00000000 <Test2>:
0: 2305 movs r3, #5
2: f3bf 8f5b dmb ish
6: 6003 str r3, [r0, #0]
8: 4770 bx lr
a: bf00 nop
如何避免内存障碍(或类似的低效率),同时避免
volatile
?
volatile
分配不是发布存储,甚至不为您提供 StoreStore 排序,这可能是您在这里所需要的全部。
volatile
基本上等同于 __ATOMIC_RELAXED
排序,只不过它会阻止其他 volatile
访问的编译时重新排序。它“不会”做任何事情来阻止运行时重新排序,而 x86 以外的 CPU 内存模型确实允许这种情况。 (至于实际的原子性,对于足够窄的类型,您确实可以通过某些编译器(例如 GCC 和 Clang)获得原子性,因为 Linux 内核使用 volatile
这种方式来滚动自己的原子性,以及用于栅栏的内联汇编。)另请参阅何时在多线程中使用 易失性? - 从不,volatile
不会为您提供任何在多线程中使用原子无法获得的东西
。如果您需要对程序其他部分中的变量进行非原子访问,请使用 GNU C 内置函数或 C++20
std::atomic_ref
和 memory_order_relaxed
而不是 volatile
。或者更简单地使用 C11 stdatomic.h
_Atomic int
或 C++11 std::atomic<>
(如果您不需要将普通的 int*
指向它)。
dmb ISHST
至少是一个 StoreStore 屏障,所以在 asm 中你可以获得发布语义。较早的商店,但不较早的装载。这对于
std::memory_order_release
又名 __ATOMIC_RELEASE
来说还不够(还需要 LoadStore ordering),因此无法让编译器为您使用它。 (https://www.cl.cam.ac.uk/~pes20/cpp/cpp0xmappings.html中的操作或栅栏都没有映射到此)。 因此不幸的是,在 ARMv7 及更早版本上,对于除
dmb ish
之外的任何标准 C / C++ 内存顺序,您都需要一个完整的屏障 (
relaxed
)。 ARMv8 解决了这个问题。
对于 -mcpu=cortex-a53
或其他
ARMv8CPU,即使在 AArch32 状态下,
stl
也可用作发布存储。因此,用它来避免发布商店或获取负载的昂贵的dmb ish
完全障碍:https://godbolt.org/z/1hzvGMbon
# GCC -O2 -mcpu=cortex-a53 (or -march=armv8-a)
Test2(int*):
movs r3, #5
stl r3, [r0] // release store
bx lr
单核系统您可以使用
atomic_signal_fence
滚动您自己的 same-core-acquire / same-core-release 来进行
relaxed
加载/存储。 // writer
buffer[idx] = xyz;
atomic_signal_fence(memory_order_release); // prevent compile-time reordering, no run-time cost
atomic_store_explicit(&shared_idx, idx, memory_order_relaxed);
// reader
int idx = atomic_load_explicit(&shared_idx, memory_order_relaxed);
atomic_signal_fence(memory_order_acquire); // prevent compile-time reordering, no run-time cost
int tmp = buffer[idx];
通过将
atomic_signal_fence
更改为
atomic_thread_fence
将此类代码移植到多核是安全的,但在某些 ISA 上性能会更差,特别是 ARMv8,其中单独的屏障指令非常昂贵,但发布存储操作可以只使用 stl