Почему memory_order задан как аргумент runtime для std:: atomic functions - программирование
Подтвердить что ты не робот

Почему memory_order задан как аргумент runtime для std:: atomic functions

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 value
    ai2.store(1, memOrd);   // no dependency. could this be move up?
}

Если memOrd оказывается memory_order_relaxed, второй магазин можно смело перемещать перед первым. Это добавит некоторую дополнительную работу между загрузкой value и ее использованием, что может помешать ненужным киоскам. Однако, если memOrd равно memory_order_seq_cst, переключение хранилищ не должно быть разрешено, потому что какой-то другой поток может рассчитывать на ai1, уже установленный на value, если ai2 установлен в 1.

Что мне интересно, почему порядок памяти определяется как параметр времени выполнения, а не время компиляции. Есть ли какая-либо причина для того, чтобы кто-то исследовал среду во время выполнения, прежде чем выбрать лучшую семантику операций с памятью?

4b9b3361

Ответ 1

Причина, по которой это реализовано как параметр времени выполнения, а не параметр времени компиляции, заключается в том, чтобы включить композицию.

Предположим, вы пишете функцию, которая использует предоставленные атомарные операции, чтобы выполнить эквивалент операции загрузки, но работая над конструкцией более высокого уровня. Имея порядок памяти, указанный в качестве параметра времени выполнения, загрузка более высокого уровня может затем передать параметр порядка памяти, предоставленный пользователем, для атомарной операции низкого уровня, которая требуется для обеспечения заказа без операции более высокого уровня, которая должна быть шаблоном.

Как правило, атомарные инструкции будут встроенными, и компилятор устранит проверку параметра порядка памяти в том случае, если он фактически является константой времени компиляции.

Ответ 2

Это просто спецификация интерфейса, которая позволяет указать memory_order во время выполнения. Это не требует, чтобы реализация использовала это пособие.

Например, на оборудовании x86 memory_order_seq_cst, вероятно, вы получаете то, что вы указываете. memory_order_relaxed просто недоступен из-за протокола согласования аппаратного кэша.

На другом оборудовании, где вы можете оптимизировать для известного порядка времени компиляции, реализация может предложить дополнительные перегрузки, которые используют параметры по умолчанию.

Ответ 3

Авторы С++ могли бы реализовать memory_order как функцию времени компиляции, а не функцию времени исполнения. Однако они ничего не выиграли бы для этого. Любой компилятор, способный осмысливать заказы памяти, будет легко оптимизировать очевидные случаи, такие как x.load(memory_order_acq), поэтому они не выиграют от того, что это функция времени компиляции.

Между тем использование функции времени исполнения означает, что им не нужно вводить какие-либо новые обозначения для заказов памяти. Это просто функциональные аргументы. Это означает, что они получили те же преимущества, что и версия времени компиляции, с меньшей сложностью.

Между тем, это очень удобно для более простых компиляторов, потому что они могут реализовать атомный как обычный класс, не требуя особого рассмотрения.

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
}