C++ 为什么内存顺序作为std::atomic函数的运行时参数给出

C++ 为什么内存顺序作为std::atomic函数的运行时参数给出,c++,c++11,atomic,memory-model,C++,C++11,Atomic,Memory Model,std::atomic函数,如store和load采用std::memory\u顺序参数。与任何其他函数参数一样,该参数可以在运行时确定。但是,实际值可能会影响编译期间代码的优化。考虑以下事项: std::atomic<int> ai1, ai2; int value = whatever; void foo() { std::memory_order memOrd = getMemoryOrder(); register int v = value; // loa

std::atomic
函数,如
store
load
采用
std::memory\u顺序
参数。与任何其他函数参数一样,该参数可以在运行时确定。但是,实际值可能会影响编译期间代码的优化。考虑以下事项:

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?
}
std::原子ai1,ai2;
int值=任何值;
void foo(){
std::memory_order memOrd=getMemoryOrder();
寄存器int v=value;//从内存加载值
ai1.store(v,memOrd);//依赖于v的值
ai2.store(1,memOrd);//没有依赖项。这可以上移吗?
}
如果
memOrd
恰好是
memory\u order\u released
,第二个存储可以安全地移动到第一个存储前面。这将在加载
和使用它之间增加一些额外的工作,这可能会防止其他需要的暂停。但是,如果
memOrd
memory\u order\u seq\u cst
,则不允许切换存储,因为如果
ai2
设置为1,则其他一些线程可能会指望
ai1
已设置为


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

这只是一个允许在运行时指定
内存顺序的接口规范。它不要求实现使用该余量

例如,在x86硬件上,无论您指定什么,都可能得到
内存\u顺序\u seq\u cst
<代码>内存\u顺序\u松弛
由于硬件缓存一致性协议而不可用

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

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

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


典型的是,原子指令将是内联的,编译器将在实际编译时间常数的情况下消除内存顺序参数的测试。

< P> C++的编写者可以将MexyOyLoad实现为编译时特性,而不是运行时特性。然而,他们不会因此得到任何好处。任何能够理解内存顺序的编译器都会很容易地优化明显的情况,比如x.load(memory\u order\u 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
}
T原子::比较\u交换\u强(T&compare,T交换,内存\u顺序)
{
lockBeforeUsing(order);//处理内存顺序的获取部分
if(mValue==比较){
比较=价值;
M值=比较;
}否则{
比较=价值;
}
lockAfterUsing(order);//处理内存顺序的释放部分
}

这只是一个提示。如果该值实际上不是一个编译时常量,那么实现很可能只是使用顺序一致性,并且永远不会再三考虑它。任何敢于围绕原子进行优化的编译器在值得优化的情况下跟踪参数都不会有问题。内存排序的参数比模板参数更容易使用,所以既然可以做简单的事情,为什么还要做困难的事情呢!(还需要考虑的是,这些函数对应于C等价函数……这些函数最容易用内存顺序参数实现)硬件一致性协议只是故事的一半。除非我遗漏了什么,否则即使是针对x86,您也应该受益于宽松的顺序(给定正确的算法),因为它允许像我在问题中描述的那样进行编译器优化。如果接口没有向编译器提供足够的信息,那么最终只能依赖硬件,从而失去了潜在的编译时间增益。这似乎是一个奇怪的设计选择,因为您在编译时确实知道所需的一切。编译器将知道该值是否是编译时常量(或者使用默认参数值),并可以根据该值进行优化。我相信,在这种情况下,他们会这么做。@Bo Persson:你确定x86内存模型等同于
内存\u顺序\u seq\u cst
?我一直认为它只能保证
内存\u顺序\u acq\u rel
。关于优化:编写标准库的人不一定就是编写编译器的人。我当然希望所有相关的编译器都能做到这一点,但我不敢打赌;)无论如何,他们不需要引入新的符号。他们可以把它变成一个模板参数。