Гарантирует ли стандарт C ++ 11, что memory_order_seq_cst предотвращает переупорядочение StoreLoad неатомных вокруг атомарных?

c++ multithreading c++11 concurrency standards

833 просмотра

2 ответа

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

Как известно, std::memory_orderв C ++ 11 есть 6 с, и он определяет порядок упорядочения регулярных неатомарных обращений к памяти вокруг атомарной операции - рабочий проект, стандарт языка программирования C ++ 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.

Два примера:

  1. 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только для ЗАГРУЗКИ: нет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 ++ - компилятор использовал альтернативное отображение 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для атомной нагрузки или магазина:

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

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

Автор: Alex Источник Размещён: 08.11.2019 11:14

Ответы (2)


4 плюса

Решение

Нет, стандарт C ++ 11 не гарантирует, что memory_order_seq_cstпредотвращает переупорядочение StoreLoadnon-atomic вокруг atomic(seq_cst).

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

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

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

§ 29,3

3

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

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

§ 29,3

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


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

  1. На 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


Больше деталей:

  1. На 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

C ++ & asm-код:

#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.

  1. LOAD(без забора) и STORE+MFENCE
  2. LOAD (без забора) и LOCK XCHG
  3. MFENCE+ LOADи STORE(без забора)
  4. 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

C ++ & asm-код:

#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;
}

Где загрузка из памяти 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 Store может быть переупорядочен с другим 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];
    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];.

Автор: Alex Размещён: 17.03.2017 12:00

0 плюса

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

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

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

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

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

Автор: Surt Размещён: 20.08.2016 12:00
Вопросы из категории :
32x32