Arm 应该为互斥体获取交换循环(或队列获取加载循环)组合内存围栏还是应该避免?

Arm 应该为互斥体获取交换循环(或队列获取加载循环)组合内存围栏还是应该避免?,arm,cpu-architecture,micro-optimization,memory-barriers,Arm,Cpu Architecture,Micro Optimization,Memory Barriers,假设重复采集操作,尝试加载或交换值,直到观察到的值为所需值 让我们以以下为起点: void f(int n) { for (int cnt = 0; cnt < 100; ++cnt) { while (lock.test_and_set(std::memory_order_acquire)) // acquire lock ; // spin std::cout << "Output from thread

假设重复采集操作,尝试加载或交换值,直到观察到的值为所需值

让我们以以下为起点:

void f(int n)
{
    for (int cnt = 0; cnt < 100; ++cnt) {
        while (lock.test_and_set(std::memory_order_acquire))  // acquire lock
             ; // spin
        std::cout << "Output from thread " << n << '\n';
        lock.clear(std::memory_order_release);               // release lock
    }
}
我希望x86不会改变这一点

我想知道:

  • 在存在差异的平台(ARM)上,此更改是否有好处或缺点
  • 使用或不使用
    yield
    指令的决定是否受到干扰

我不仅对
atomic\u flag::clear
/
atomic\u flag::test\u和\u set
对感兴趣,我还对
atomic::store
/
atomic::load
对感兴趣


可能更改为松弛载荷是有意义的:

void f(int n)
{
    for (int cnt = 0; cnt < 100; ++cnt) {
        while (lock.test_and_set(std::memory_order_acquire))  // acquire lock
             while (lock.test(std::memory_order_relaxed))
                 YieldProcessor(); // spin
        std::cout << "Output from thread " << n << '\n';
        lock.clear(std::memory_order_release);               // release lock
    }
}
void f(int n)
{
对于(int cnt=0;cnt<100;++cnt){
while(lock.test_和_set(std::memory_order_acquire))//获取锁
while(锁测试(标准::内存\u顺序\u松弛))
YieldProcessor();//自旋

std::cout暂停指令只是N条NOP指令的替代品,其中N条指令因处理器而异。此外,它对无序执行处理器中指令的重新排序也有影响。原子线程围栏是否比“暂停”更有效取决于典型的周期数自旋等待循环等待。原子线程围栏的执行延迟比暂停指令高。如果自旋等待周期比其他机制(如在x86平台上使用MONITOR-MWAIT指令对)大,则性能更好,也更节能。否则暂停就足够了。

是的,避免捕获的一般想法是故障重试路径中的e屏障可能有用,但如果您只是在旋转,则故障情况下的性能几乎不相关。
pause
yield
节省电源。在x86上,
pause
还提高了SMT友好性,避免了在修改另一个内核后离开循环时内存顺序错误推测正在旋转的内存位置

但这就是为什么CAS对成功和失败有单独的
memory\u order
参数的原因。放松的失败只会让编译器上的障碍离开循环路径

atomic\u flag
test\u and\u set
没有此选项。手动执行此操作可能会损害像AArch64这样的ISA,因为它本可以执行获取RMW并避免显式围栏指令。(例如,使用
ldarb

:带
锁的原始循环。测试和设置(标准::内存\u顺序\u获取)

(是的,它只是用
tst
而不是
cbnz w1、.L6
测试低8位,这看起来像是错过了优化)

while(放松RMW)+
std::原子线程围栏(std::内存顺序获取);

32位ARMv8的情况更糟,其中
dmb ishld
不可用,或者编译器不使用它。您将获得一个
dmb ish
完整屏障。


或者使用
-march=armv8.1-a
vs


指令,例如pause或yield
,而我看到它只是对cpu的一个提示,没有
yield
指令。如果你的意思是,它会等待一个中断,如果你的意思是,那么你就不应该使用它-
sched_yield
是用于实时进程的,你的代码只会使用100%的cpu和调度程序blo检查其他进程。
yield
是ARM指令。我的意思是。在x86上,这是一个没有任何问题的问题,所以我提到了ARM指令。我的意思是CPU指令,没有操作系统或运行时库函数。是的,这是一个关于做出优化提示的问题,所以
pause
/
yield
确实是“嗨”是的,避免故障重试路径内的获取障碍的一般想法可能是有用的,尽管如果您只是在旋转,故障情况下的性能几乎不相关。但这就是为什么CAS有单独的
内存顺序
成功和失败参数的原因。放松的故障只会让编译器停止运行ier在离开循环路径。
atomic_flag
test_和_set
没有这个选项。是的,我是在问“仅仅旋转的性能”。我怀疑它背后有某种意义,否则就没有
暂停
/
产生
指令。感谢您提到了带有单独排序参数的CAS——我现在明白了为什么这些单独参数很有用。谢谢。我希望它也适用于
存储
-释放
加载
-获取循环(相同方式,但不包括法律顾问/资深大律师)我假设,当分离的
原子线程围栏
更糟糕时,这种情况的存在是反对在一般实现中这样做的有力论据,因为它是对快速路径的悲观,而潜在优化是对waiting@AlexGuteniev:是的,没错。ARMv8是一个有趣的案例,其中反垄断比收购围栏便宜得多。作为收购操作的一部分,许多其他弱有序的ISA都会有一个同等的围栏。但是现在ARM是一个非常相关/重要的ISA,这是关注其特殊情况的好理由。@AlexGuteniev:对,只读旋转,直到它看起来有机会采取行动为止锁更可取。我应该提到这一点,但我认为,
atomic_flag
是如此原始,它只是TAS和clear。但是的,当然也有一个测试。它确实是。
atomic_flag::test
是C++20的加法。
void f(int n)
{
    for (int cnt = 0; cnt < 100; ++cnt) {
        while (lock.test_and_set(std::memory_order_acquire))  // acquire lock
             while (lock.test(std::memory_order_relaxed))
                 YieldProcessor(); // spin
        std::cout << "Output from thread " << n << '\n';
        lock.clear(std::memory_order_release);               // release lock
    }
}
# AArch64 gcc8.2 -O3
.L6:                            # do{
    ldaxrb  w0, [x19]           # acquire load-exclusive
    stxrb   w1, w20, [x19]      # relaxed store-exclusive
    cbnz    w1, .L6            # LL/SC failure retry
    tst     w0, 255
    bne     .L6             # }while(old value was != 0)
  ... no barrier after this
.L14:                          # do {
    ldxrb   w0, [x19]             # relaxed load-exclusive
    stxrb   w1, w20, [x19]        # relaxed store-exclusive
    cbnz    w1, .L14             # LL/SC retry
    tst     w0, 255
    bne     .L14               # }while(old value was != 0)
    dmb     ishld         #### Acquire fence
   ...
.L2:
    swpab   w20, w0, [x19]
    tst     w0, 255
    bne     .L2
    mov     x2, 19
  ...
.L9:
    swpb    w20, w0, [x19]
    tst     w0, 255
    bne     .L9
    dmb     ishld                   # acquire barrier (load ordering)
    mov     x2, 19
...