为什么将 memory_order 作为 std::atomic 函数的运行时参数给出

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

std::atomic
函数(例如
store
load
)采用
std::memory_order
参数。该参数可以在运行时确定,就像任何其他函数参数一样。然而,实际值可能会影响编译期间代码的优化。考虑以下几点:

std::atomic<int> ai1, ai2;
int value = whatever;

void foo() {
    std::memory_order memOrd = getMemoryOrder();
    register int v = value; // load value from memory
    ai1.store(v, memOrd);   // dependency on v's value
    ai2.store(1, memOrd);   // no dependency. could this be move up?
}

如果

memOrd
恰好是
memory_order_relaxed
,则第二家商店可以安全地移到第一家商店的前面。这将在加载
value
和使用它之间增加一些额外的工作,这可能会阻止其他需要的停顿。但是,如果
memOrd
memory_order_seq_cst
,则不应允许切换存储,因为如果
ai1
设置为 1,则某些其他线程可能会依赖于
value
已设置为
ai2

我想知道为什么内存顺序被定义为运行时参数而不是编译时。在决定最佳内存操作语义之前,有人有理由在运行时检查环境吗?

c++ c++11 atomic stdatomic memory-model
3个回答
9
投票

将其实现为运行时参数而不是编译时参数的原因是为了启用组合。

假设您正在编写一个函数,该函数使用提供的原子操作来执行与 load 操作等效的操作,但在更高级别的构造上进行操作。通过将内存顺序指定为运行时参数,较高级别的加载可以将用户提供的内存顺序参数传递给提供排序所需的低级别原子操作,而无需较高级别的操作必须是模板。

通常,原子指令将是内联的,并且在内存顺序参数实际上是编译时常量的情况下,编译器将消除对内存顺序参数的测试。


4
投票

它只是一个接口规范,允许在运行时指定

memory_order
。它不需要实施来使用该津贴。

例如,在 x86 硬件上

memory_order_seq_cst
可能就是您所得到的,无论您指定什么。
memory_order_relaxed
由于硬件缓存一致性协议而不可用。

在其他硬件上,您可能能够针对已知顺序的编译时间进行优化,该实现可能会提供利用默认参数的额外重载。


0
投票

C++ 的编写者可以将 memory_order 实现为编译时功能,而不是运行时功能。然而,他们不会因此得到任何好处。任何能够理解内存顺序的编译器都将轻松优化 x.load(memory_order_acq) 等明显的情况,因此它们不会从它作为编译时功能中受益。

同时,使用运行时功能意味着他们不必为内存顺序引入任何新的符号。它们只是函数参数。这意味着它们获得了与编译时版本相同的好处,但复杂性更低。

同时,对于更简单的编译器来说非常方便,因为它们可以将atomic实现为普通类,而不必对其进行特殊对待。

T atomic<T>::compare_exchange_strong(T& compare, T exchange, memory_order order)
{
    lockBeforeUsing(order); // handle the acquire part of the memory order
    if (mValue == compare) {
        compare = mValue;
        mValue = compare;
    } else {
        compare = mValue;
    }
    lockAfterUsing(order); // handle the release part of the memory order
}
© www.soinside.com 2019 - 2024. All rights reserved.