Поддерживает ли стандарт С++ 11, что memory_order_seq_cst предотвращает переупорядочение StoreLoad неатомного атома?

Предоставляет ли стандарт С++ 11, что memory_order_seq_cst предотвращает переупорядочение StoreLoad вокруг атомной операции для доступа к неатомной памяти?

Как известно, в С++ 11 есть 6 std::memory_order, и в нем указано , как регулярные, неатомные обращения к памяти должны быть упорядочены вокруг атомной операции - Рабочий проект, Стандарт для Язык программирования С++ 2016-07-12: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2016/n4606.pdf

§ 29.3 Порядок и согласованность

§ 29.3/1

В перечислении memory_order указан подробный регулярный (неатомной) памяти, как определено в 1.10, и может обеспечить порядок работы. Его перечисляемые ценности и их значения следующие:

Также известно, что эти 6 memory_orders предотвращают некоторые из этих переупорядочений:

введите описание изображения здесь

Но, memory_order_seq_cst предотвращает переупорядочение StoreLoad вокруг атомарной операции для регулярных, неатомных регулярных, неатомических запросов к памяти или только для других атомов с тем же memory_order_seq_cst?

т.е. чтобы предотвратить переупорядочение StoreLoad, мы должны использовать std::memory_order_seq_cst для STORE и LOAD или только для одного из них?

std::atomic<int> a, b;
b.store(1, std::memory_order_seq_cst); // Sequential Consistency
a.load(std::memory_order_seq_cst); // Sequential Consistency

О семантике Acquire-Release все ясно, он указывает точно неатомное переупорядочение доступа к памяти в рамках атомных операций: http://en.cppreference.com/w/cpp/atomic/memory_order


Чтобы предотвратить переупорядочение StoreLoad, мы должны использовать std::memory_order_seq_cst.

Два примера:

  • std::memory_order_seq_cst для STORE и LOAD: есть MFENCE

StoreLoad не может быть переупорядочен - GCC 6.1.0 x86_64: https://godbolt.org/g/mVZJs0

std::atomic<int> a, b;
b.store(1, std::memory_order_seq_cst); // can't be executed after LOAD
a.load(std::memory_order_seq_cst); // can't be executed before STORE
  1. std::memory_order_seq_cst только для LOAD: нет MFENCE

StoreLoad можно переупорядочить - GCC 6.1.0 x86_64: https://godbolt.org/g/2NLy12

std::atomic<int> a, b;
b.store(1, std::memory_order_release); // can be executed after LOAD
a.load(std::memory_order_seq_cst); // can be executed before STORE

Также, если C/С++ - компилятор использовал альтернативное отображение C/С++ 11 на x86, которое сбрасывает буфер хранения до LOAD: MFENCE,MOV (from memory), поэтому мы также должны использовать std::memory_order_seq_cst для LOAD: http://www.cl.cam.ac.uk/~pes20/cpp/cpp0xmappings.html Поскольку этот пример обсуждается в другом вопросе как подход (3): Имеет ли смысл какую-либо команду LFENCE в процессорах x86/x86_64?

т.е. мы должны использовать std::memory_order_seq_cst как для STORE, так и для LOAD для создания MFENCE гарантированного, что предотвращает переупорядочение StoreLoad.

Это правда, что memory_order_seq_cst для атомной загрузки или сохранения:

  • speci Приобретать-освобождать семантику - предотвращать: LoadLoad, LoadStore, StoreStore переупорядочивает вокруг атомной операции для регулярных, неатомических доступа к памяти,

  • но не препятствуйте StoreLoad переупорядочивать вокруг атомной операции только для других атомных операций с тем же memory_order_seq_cst?

Ответ 1

Нет, стандартный С++ 11 не гарантирует, что memory_order_seq_cst предотвращает переупорядочение хранилища non-atomic вокруг atomic(seq_cst).

Даже стандартный С++ 11 не гарантирует, что memory_order_seq_cst предотвращает переупорядочивание atomic(non-seq_cst) вокруг atomic(seq_cst).

Рабочий проект, стандарт для языка программирования С++ 2016-07-12: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2016/n4606.pdf

  • На всех операциях memory_order_seq_cst должен быть один полный порядок S - С++ 11 Стандарт:

§ 29.3

3

Должен быть единый общий заказ S на все memory_order_seq_cst в соответствии с порядком "происходит до" и порядок модификации для всех затронутых местоположений, так что каждый memory_order_seq_cst, которая загружает значение из атомарного объект M наблюдает одно из следующих значений:...

  • Но любые атомные операции с упорядочением слабее, чем memory_order_seq_cst, не имеют последовательной согласованности и не имеют единого общего порядка, то есть операции без memory_order_seq_cst могут быть переупорядочены с помощью операций memory_order_seq_cst в разрешенных направлениях - С++ 11 Стандарт:

§ 29.3

8 [Примечание: memory_order_seq_cst обеспечивает последовательную согласованность только для программы, в которой нет данных, а используется исключительно Операции memory_order_seq_cst. Любое использование более слабых заказов будет аннулировать эту гарантию, если не использовать чрезмерную осторожность. В частности, memory_order_seq_cst заборы обеспечивают общий порядок только для заборов самих себя. Заборы не могут, как правило, использоваться для восстановления последовательных согласованность для атомных операций с более слабыми параметрами упорядочения. - конечная нота]


Также С++ - компиляторы допускают такие переупорядочивания:

  • В x86_64

Обычно - если в компиляторах seq_cst реализован как барьер после хранения, то:

STORE-C(relaxed); LOAD-B(seq_cst); можно изменить на LOAD-B(seq_cst); STORE-C(relaxed);

Снимок экрана с Asm, созданный GCC 7.0 x86_64: https://godbolt.org/g/4yyeby

Кроме того, теоретически возможно - если в компиляторах seq_cst реализованы как барьер перед загрузкой, то:

STORE-A(seq_cst); LOAD-C(acq_rel); можно переупорядочить до LOAD-C(acq_rel); STORE-A(seq_cst);

  1. На PowerPC

STORE-A(seq_cst); LOAD-C(relaxed); можно переупорядочить до LOAD-C(relaxed); STORE-A(seq_cst);

Также на PowerPC может быть такое переупорядочение:

STORE-A(seq_cst); STORE-C(relaxed); может быть изменен на STORE-C(relaxed); STORE-A(seq_cst);

Если даже атомные переменные разрешены для упорядочения по атомам (seq_cst), то неатомные переменные также могут быть переупорядочены по атомам (seq_cst).

Снимок экрана с Asm, созданный GCC 4.8 PowerPC: https://godbolt.org/g/BTQBr8


Подробнее:

  • В x86_64

STORE-C(release); LOAD-B(seq_cst); можно переупорядочить до LOAD-B(seq_cst); STORE-C(release);

Архитектуры Intel® 64 и IA-32

8.2.3.4 Нагрузки могут быть переупорядочены с более ранними магазинами в разных местах

т.е. Код x86_64:

STORE-A(seq_cst);
STORE-C(release); 
LOAD-B(seq_cst);

Можно изменить порядок:

STORE-A(seq_cst);
LOAD-B(seq_cst);
STORE-C(release); 

Это может произойти, потому что между c.store и b.load не mfence:

x86_64 - GCC 7.0: https://godbolt.org/g/dRGTaO

С++ и asm-code:

#include <atomic>

// Atomic load-store
void test() {
    std::atomic<int> a, b, c;
    a.store(2, std::memory_order_seq_cst);          // movl 2,[a]; mfence;
    c.store(4, std::memory_order_release);          // movl 4,[c];
    int tmp = b.load(std::memory_order_seq_cst);    // movl [b],[tmp];
}

Его можно изменить следующим образом:

#include <atomic>

// Atomic load-store
void test() {
    std::atomic<int> a, b, c;
    a.store(2, std::memory_order_seq_cst);          // movl 2,[a]; mfence;
    int tmp = b.load(std::memory_order_seq_cst);    // movl [b],[tmp];
    c.store(4, std::memory_order_release);          // movl 4,[c];
}

Кроме того, последовательная согласованность в x86/x86_64 может быть реализована четырьмя способами: http://www.cl.cam.ac.uk/~pes20/cpp/cpp0xmappings.html

  • LOAD (без забора) и STORE + mfence
  • LOAD (без забора) и LOCK XCHG
  • mfence + LOAD и STORE (без забора)
  • LOCK XADD (0) и STORE (без забора)
  • 1 и 2 пути: LOAD и (STORE + mfence)/(LOCK XCHG) - мы рассмотрели выше
  • 3 и 4 пути: (mfence + LOAD)/LOCK XADD и STORE - разрешить следующее переупорядочение:

STORE-A(seq_cst); LOAD-C(acq_rel); можно переупорядочить до LOAD-C(acq_rel); STORE-A(seq_cst);


  1. На PowerPC

STORE-A(seq_cst); LOAD-C(relaxed); можно переупорядочить до LOAD-C(relaxed); STORE-A(seq_cst);

Позволяет переупорядочить хранилище (Таблица 5 - PowerPC): http://www.rdrop.com/users/paulmck/scalability/paper/whymb.2010.06.07c.pdf

Сохраненные после загрузки нагрузки

т.е. Код PowerPC:

STORE-A(seq_cst);
STORE-C(relaxed); 
LOAD-C(relaxed); 
LOAD-B(seq_cst);

Можно изменить порядок:

LOAD-C(relaxed);
STORE-A(seq_cst);
STORE-C(relaxed); 
LOAD-B(seq_cst);

PowerPC - GCC 4.8: https://godbolt.org/g/xowFD3

С++ и asm-code:

#include <atomic>

// Atomic load-store
void test() {
    std::atomic<int> a, b, c;       // addr: 20, 24, 28
    a.store(2, std::memory_order_seq_cst);          // li r9<-2; sync; stw r9->[a];
    c.store(4, std::memory_order_relaxed);          // li r9<-4; stw r9->[c];
    c.load(std::memory_order_relaxed);              // lwz r9<-[c];
    int tmp = b.load(std::memory_order_seq_cst);    // sync; lwz r9<-[b]; ... isync;
}

Разделив a.store на две части - его можно изменить следующим образом:

#include <atomic>

// Atomic load-store
void test() {
    std::atomic<int> a, b, c;       // addr: 20, 24, 28
    //a.store(2, std::memory_order_seq_cst);            // part-1: li r9<-2; sync;
    c.load(std::memory_order_relaxed);              // lwz r9<-[c];
    a.store(2, std::memory_order_seq_cst);          // part-2: stw r9->[a];
    c.store(4, std::memory_order_relaxed);          // li r9<-4; stw r9->[c];
    int tmp = b.load(std::memory_order_seq_cst);    // sync; lwz r9<-[b]; ... isync;
}

Где load-from-memory lwz r9<-[c]; выполняется раньше, чем память-память stw r9->[a];.


Также на PowerPC может быть такое переупорядочение:

STORE-A(seq_cst); STORE-C(relaxed); может быть изменен на STORE-C(relaxed); STORE-A(seq_cst);

Поскольку PowerPC имеет слабую модель упорядочения памяти - позволяет переупорядочить хранилище Store Store (Таблица 5 - PowerPC): http://www.rdrop.com/users/paulmck/scalability/paper/whymb.2010.06.07c.pdf

Магазины, упорядоченные после магазинов

т.е. на операциях PowerPC. Магазин можно переупорядочить с помощью другого Хранилища, тогда предыдущий пример может быть переупорядочен, например:

#include <atomic>

// Atomic load-store
void test() {
    std::atomic<int> a, b, c;       // addr: 20, 24, 28
    //a.store(2, std::memory_order_seq_cst);            // part-1: li r9<-2; sync;
    c.load(std::memory_order_relaxed);              // lwz r9<-[c];
    c.store(4, std::memory_order_relaxed);          // li r9<-4; stw r9->[c];
    a.store(2, std::memory_order_seq_cst);          // part-2: stw r9->[a];
    int tmp = b.load(std::memory_order_seq_cst);    // sync; lwz r9<-[b]; ... isync;
}

Где хранится память stw r9->[c]; выполняется раньше, чем память-память stw r9->[a];.

Ответ 2

std::memory_order_seq_cst гарантирует, что не будет переупорядочиваться ни компилятором, ни процессором. В этом случае тот же порядок памяти, что и только одна команда, выполняемая за раз.

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

Компилятор может видеть, что в вашей тестовой программе с -O3 нет никакого следствия mfence, поскольку программа слишком проста.

Если вы запустили его на руке с другой стороны, например this, вы можете увидеть барьеры dmb ish.

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