首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >应该将互斥对象获取-交换循环(或队列获取-负载循环)的内存栅栏结合起来,还是应该避免?

应该将互斥对象获取-交换循环(或队列获取-负载循环)的内存栅栏结合起来,还是应该避免?
EN

Stack Overflow用户
提问于 2020-06-11 06:50:09
回答 2查看 1.7K关注 0票数 2

假设重复的获取操作,尝试加载或交换一个值,直到观察到的值是所需的值。

让我们以Cp偏好原子标志示例为起点:

代码语言:javascript
复制
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
    }
}

现在,让我们考虑一下对这种旋转的增强。两个著名的例子是:

  • 不要永远旋转,相反,去操作系统等待某一时刻;
  • 使用指令,如pauseyield,而不是非操作旋转.

我能想到第三个,我想知道这是否有意义。我们可以使用std::atomic_thread_fence获取语义:

代码语言:javascript
复制
void f(int n)
{
    for (int cnt = 0; cnt < 100; ++cnt) {
        while (lock.test_and_set(std::memory_order_relaxed))  // acquire lock
             ; // spin
        std::atomic_thread_fence(std::memory_order_acquire);  // acquire fence
        std::cout << "Output from thread " << n << '\n';
        lock.clear(std::memory_order_release);               // release lock
    }
}

我希望这对x86来说没有什么变化。

我想知道:

  • 在存在差异的平台(ARM)上,这种更改是否有好处或缺点?
  • 是否对使用或不使用yield指令的决定有任何干扰?

我不仅对atomic_flag::clear / atomic_flag::test_and_set对感兴趣,还对atomic<uint32_t>::store / atomic<uint32_t>::load对感兴趣。

可能改变为放松负荷可能是有意义的:

代码语言:javascript
复制
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
    }
}
EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2020-06-11 11:40:10

是的,避免失败重试路径中的获取障碍的一般想法可能是有用的,尽管在失败情况下的性能几乎不相关,如果您只是旋转的话。pauseyield省电。在x86上,pause还提高了SMT的友好性,并且在另一个核心修改内存位置之后离开循环时避免了内存顺序错误的猜测。

但这就是为什么中国科学院有独立的memory_order参数来衡量成功和失败。松弛的失败可能会让编译器在离开循环路径时遇到唯一的障碍.

不过,atomic_flag test_and_set没有这个选择。手动操作可能会对AArch64这样的ISAs造成伤害,后者可能已经完成了RMW的获取,并避免了明确的隔离指令。(例如使用ldarb**)** )

哥德波特:使用lock.test_and_set(std::memory_order_acquire)的原始循环

代码语言:javascript
复制
# 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

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

同时(放松的RMW) + std::atomic_thread_fence(std::memory_order_acquire);

代码语言:javascript
复制
.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
   ...

更糟糕的是32位ARMv8没有dmb ishld,或者编译器不使用它。dmb ish,,,,。

或使用-march=armv8.1-a

代码语言:javascript
复制
.L2:
    swpab   w20, w0, [x19]
    tst     w0, 255
    bne     .L2
    mov     x2, 19
  ...

代码语言:javascript
复制
.L9:
    swpb    w20, w0, [x19]
    tst     w0, 255
    bne     .L9
    dmb     ishld                   # acquire barrier (load ordering)
    mov     x2, 19
...
票数 1
EN

Stack Overflow用户

发布于 2020-06-11 11:13:44

暂停指令只是N个NOP指令的替换,其中N个处理器不同。此外,它对无序执行能力处理器中指令的重新排序有影响.atomic_thread_fence是否会比“暂停”提供一些好处取决于典型的循环自旋等待次数。atomic_thread_fence比暂停指令具有更高的执行延迟。如果旋转等待周期大于其他机制,如在x86平台上使用监控器-MWAIT指令对,则具有更好的性能和能源效率。否则暂停就足够了。

票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/62318642

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档