سؤال

std::atomic functions such as store and load take an std::memory_order argument. The argument can be determined at runtime, like any other function argument. However, the actual value may effect the optimization of the code during compilation. Consider the following:

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?
}

If memOrd happens to be memory_order_relaxed, the second store could safely be moved in front of the first one. This will add some extra work between loading value and using it, which might prevent otherwise required stalls. However, if memOrd is memory_order_seq_cst, switching the stores should not be allowed, because some other thread might count on ai1 being already set to value if ai2 is set to 1.

What I'm wondering is why was the memory order defined as a runtime argument rather than compile time. Is there any reason for someone to examine the environment at runtime before deciding the best memory operations semantics?

هل كانت مفيدة؟

المحلول

The reason this is implemented as a runtime parameter rather than a compile-time parameter is to enable composition.

Suppose you are writing a function that uses the provided atomic operations to do the equivalent of a load operation, but operating on a higher level construct. By having the memory order specified as a runtime parameter the higher level load can then pass a memory order parameter supplied by the user to the low-level atomic operation that is required to provide the ordering without the higher level operation having to be a template.

Typically, the atomic instructions will be inline, and the compiler will eliminate the test of the memory order parameter in the case that it is actually a compile-time constant.

نصائح أخرى

It's just an interface specification that allows the memory_order to be specified at runtime. It doesn't require the implementation to use that allowance.

For example, on x86 hardware memory_order_seq_cst is probably what you get, whatever you specify. memory_order_relaxed just isn't available because of the hardware cache coherency protocol.

On other hardware, where you might be able to optimize for compile time known order, the implementation might offer additional overloads that take advantage of the default parameters.

The writers of C++ could have implemented memory_order as a compile time feature, rather than a runtime feature. However, they would not have gained anything for it. Any compiler capable of comprehending memory orders is going to easily optimize the obvious cases like x.load(memory_order_acq), so they would not benefit from it being a compile time feature.

Meanwhile, using a runtime feature means they did not have to introduce any new notation for memory orders. They're just function arguments. This means they gained the same benefits as the compile time version, with less complexity.

Meanwhile, it is very convenient for simpler compilers, because they can implement atomic as a normal class, without having to treat it specially.

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
}
مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top