arm gcc:没有易失性的商店排序?

问题描述 投票:0回答:1

我尝试使用共享索引来指示数据已写入共享循环缓冲区。有没有一种有效的方法可以在 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

gcc arm atomic volatile memory-barriers
1个回答
1
投票

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

或其他

ARMv8
CPU,即使在 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

单核系统

在单核 Cortex M4 上,所有“线程”都将在同一核心上运行,因此运行时内存重新排序是不可能的。导致上下文切换的中断相当于 C11 / C++11 内存模型中的信号处理程序。

您可以使用

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
    

© www.soinside.com 2019 - 2024. All rights reserved.