`std::memory_ordering`的值会影响原子对象上的编译器重新排序和硬件指令吗?

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

我想知道,

std::memory_ordering
类型的参数的值只是提示编译器如何重新排序代码,还是该值也影响操作原子对象的指令的选择?

https://en.cppreference.com/w/cpp/atomic/memory_order中所述,例如:

memory_order_acquire
:在此加载之前,当前线程中的任何读取或写入都不能重新排序。

这对编译器提出了如何对代码重新排序的要求。假设目标平台有两个原子指令:

W0 addr, eax
W1 addr, eax
。 IIUC,
memory_order
的值也会影响原子对象使用哪条指令的选择,对吧?


另一个问题是,如果

memory_order
类型的参数的值只能在运行时确定,那么编译器如何知道如何根据该值对代码进行重新排序?

c++ atomic
1个回答
0
投票

是的,两者都有。

C++内存模型要求原子操作遵循一定的语义,这取决于指定的内存排序参数。因此,编译器必须发出在执行时根据这些语义表现的代码。

例如,采用如下代码:

std::atomic<int> x;
int y, tmp;
if (x.load(std::memory_order_acquire) == 5) {
    tmp = y;
}

在典型的机器上,编译器需要:

  1. 不在编译时重新排序

    x
    y
    的加载。换句话说,它应该发出一条加载指令
    x
    和一条加载指令
    y
    ,以便按照程序顺序,第一个指令在第二个指令之前执行。

  2. 确保

    x
    y
    的负载按顺序变为visible。如果机器能够执行无序执行、推测性加载或任何其他可能导致两个加载不按程序顺序可见的功能,则编译器必须发出代码以防止这种情况在这种情况下发生。

    该代码的外观取决于相关机器。可能性包括:

    • 不需要什么特别的,因为机器不会进行这种特殊的重新排序。因此

      x
      y
      将仅由普通加载指令加载,没有任何额外的内容。例如,x86 上就是这种情况,其中“所有负载均已获取”。

    • 使用特殊形式的加载指令来禁止重新排序。例如,在 AArch64 上,

      x
      的加载将使用
      ldapr
      ldar
      指令而不是普通的
      ldr
      来完成。

    • 在两个负载之间插入特殊的内存屏障指令,就像ARM的

      dmb


在绝大多数代码中,内存排序参数被指定为编译时常量,因为程序员静态地知道需要什么排序,因此编译器可以发出适合该特定排序的指令。

在排序参数不是常量的特殊情况下,编译器必须发出无论指定什么值都将正常运行的代码。通常所做的是,编译器只是将排序参数视为

memory_order_seq_cst
,因为它比所有其他参数都更强:
seq_cst
操作满足较弱排序所需的所有语义(以及更多)。这节省了在运行时实际测试排序参数值并相应分支的成本,这可能超过使用较弱排序执行操作所节省的潜在成本。

但是,如果编译器确实选择测试和分支,那么为了优化周围代码,它通常必须假设“最坏情况”。例如,在 AArch64 上,对于

x.load(order)
,它可能会发出如下代码块:

int t;
if (order == std::memory_order_relaxed)
    LDR t, [x]
else if (order == std::memory_order_acquire)
    LDAPR t, [x]
else if (order == std::memory_order_seq_cst)
    LDAR t, [x]
else
    abort();
if (t == 5)
    LDR tmp, [y]

但是,需要确保

y
的负载保持在该代码块的末尾(按程序顺序)。如果
order
等于
std::memory_order_relaxed
,那么可以在
y
的加载之前执行
x
的加载,但如果它是
std::memory_order_acquire
或更强,则不行。

另一方面,它可能会发射

int t, t2;
if (order == std::memory_order_relaxed) {
    LDR t2, [y]
    LDR t, [x]
} else if (order == std::memory_order_acquire) {
    LDAPR t, [x]
    LDR t2, [y]
} else if (order == std::memory_order_seq_cst) {
    LDAR t, [x]
    LDR t2, [y]
else
    abort();
if (t == 5)
    tmp = t2;

但我们现在远远超出了现实世界编译器实际执行的转换范围。

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