SlideShare a Scribd company logo
1 of 226
Download to read offline
Лекция 7. Атомарные операции.
Внеочередное выполнение инструкций.
Барьеры памяти. Семантика захвата-
освобождения. Модель памяти C++
Пазников Алексей Александрович
Кафедра вычислительных систем СибГУТИ
Сайт курса: http://cpct.sibsutis.ru/~apaznikov/teaching/
Вопросы: https://piazza.com/sibsutis.ru/fall2014/pct14/home
Параллельные вычислительные технологии
Осень 2014 (Parallel Computing Technologies, PCT 14)
Атомарные
переменные
2
Атомарные операции
▪ Операция над разделяемой переменной
атомарная, если она выполняется потоком
за один неделимый шаг. Ни один из других
потоков не может обнаружить эту переменную
в промежуточном состоянии.
▪ Если операции, которые совершают потоки над
раздялемыми переменными, не атомарны, то это
приведёт к гонкам данных.
▪ Гонки данных являются причиной неопредлённого
поведения, поскольку они приводят к частичным
(фрагментированным, “разорванным”)
чтениям и записям переменных.
3
Фрагментированные чтения и записи
uint64_t shared;
int main() {
shared = 0x100000002;
...
4
Фрагментированные чтения и записи
mov DWORD PTR shared, 2
mov DWORD PTR shared+4, 1
uint64_t shared;
int main() {
shared = 0x100000002;
...
gcc -m32 -S -masm=intel -O1 prog.c
запись младших 32 бит
запись старших 32 бит
▪ Выполнение присваивания 64-битного целого shared = 42
на 32-разрядной архитектуре выполняется
за 2 инструкции.
▪ Операция записи не атомарная.
5
Фрагментированные чтения и записи
mov DWORD PTR shared, 2
mov DWORD PTR shared+4, 1
uint64_t shared;
int main() {
shared = 0x100000002;
...
gcc -m32 -S -masm=intel -O1 prog.c
1 поток
запись старших 32 бит
▪ Вытеснение потока после записи младших бит приведёт к
тому, что эти биты останутся в памяти и будут
использованы другими потоками.
▪ На многоядерных системах даже не требуется
вытеснения. 6
Фрагментированные чтения и записи
mov DWORD PTR shared, 2
mov DWORD PTR shared+4, 1
uint64_t shared;
int main() {
shared = 0x100000002;
...
gcc -m32 -S -masm=intel -O1 prog.c
1 поток
2 поток
▪ Вытеснение потока после записи младших бит приведёт к
тому, что эти биты останутся в памяти, а старшие будут
записаны другим потоком.
▪ На многоядерных системах даже не требуется
вытеснения. 7
Фрагментированные чтения и записи
mov eax, DWORD PTR shared
mov edx, DWORD PTR shared+4
ret
uint64_t shared;
uint64_t getShared() {
return shared;
}
gcc -m32 -S -masm=intel -O1 prog.c
чтение младших 32 бит
чтение старших 32 бит
▪ Выполнение чтения 64-битного целого shared на 32-
разрядной архитектуре выполняется
за 2 инструкции.
▪ Операция чтения не атомарная.
8
Фрагментированные чтения и записи
strd r0, r1, [r2]
▪ Инструкции могут быть неатомарными, даже если
выполняются одной процессорной инструкцией.
Например, в ARMv7 инструкция для помещения содержимого
двух 32-битных регистров в один 64-битный:
На некоторых процессорах эта инструкция реализуются двумя
отдельными операциями сохранения.
32-битная операция mov атомарна только для выравненных
данных. В остальных случаях операция неатомарная
▪ Вытеснение потоков, выполняющих данные операции, или
выполнение операций в двугих потоках в многоядерных
системах приводит к неопределённому поведению.
9
Атомарность в С и С++
▪ В языках С и С++ предполагается, что все
операции неатомарны.
▪ Операции могут быть атомарными в
большинстве случаев, например, операция
присваивания 32-битного целого значения.
▪ Тем не менее, все инструкции должны
рассматриваться как неатомарные.
▪ К счастью, в С и С++ есть набор шаблонов
атомарных типов данных.
10
Атомарность в С и С++
▪ Атомарные операции в С++ неделимы. Из
любого потока нельзя обнаружить эту
операцию выполненной частично - она
либо выполнена, либо невыполнена.
▪ Это позволяет избежать гонок данных.
▪ Для атомарных типов определён метод
is_lock_free, позволяющий определить,
являются ли операции над ним напрямую
с помощью атомарных инструкций, или они
эмулируются.
11
Атомарные типы в С и С++
▪ std::atomic_flag - единственный тип,
который не имеет функции is_lock_free. Он
предельно простой, всецело атомарный и
поддерживает одну операцию: test_and_set -
проверить и установить.
▪ Остальные типы определяются
специализацией шаблона std::atomic<>,
например std::atomic<int> и std::
atomic<void*>
12
Атомарные типы в С и С++
Атомарные тип Соответствующая специализация
std::atomic_bool std::atomic<bool>
std::atomic_char std::atomic<char>
std::atomic_schar std::atomic<signed char>
std::atomic_uchar std::atomic<unsigned char>
std::atomic_short std::atomic<short>
std::atomic_ushort std::atomic<unsigned short>
std::atomic_int std::atomic<int>
std::atomic_uint std::atomic<unsigned int>
std::atomic_long std::atomic<long>
...
+ пользовательские типы 13
Операции над атомарными типами
▪ Операции сохранения: store, clear, etc.
Упорядочение memory_order_relaxed,
memory_order_release, memory_order_seq_cst.
▪ Операции загрузки: load, etc.
Упорядочение memory_order_relaxed,
memory_order_consume, memory_order_acquire,
memory_order_seq_cst.
▪ Операции чтения-модификации-записи:
compare_exchange, fetch_add, test_and_set, etc.
Упорядочение memory_order_relaxed,
memory_order_consume, memory_order_acquire,
memory_order_release, memory_order_seq_cst,
memory_order_acq_rel.
14
Атомарный флаг std::atomic_flag
std::atomic_flag должен быть проиниализирован:
std::atomic_flag flag = ATOMIC_FLAG_INIT
Очистить флаг (операция сохранения): установить значение
false:
flag.clear(std::memory_order_release);
Установить значение флага в true и вернуть предыдущее
значение:
bool x = flag.test_and_set();
Для атомарного флага запрещены операции копирования и
присваивания.
15
Реализация спинлока на основе атомарного флага
class spinlock_mutex
{
std::atomic_flag flag;
public:
spinlock_mutex():
flag{ATOMIC_FLAG_INIT} { }
void lock() {
while (flag.test_and_set(
std::memory_order_acquire));
}
void unlock() {
flag.clear(std::memory_order_release);
}
};
n.b. spinlock_mutex можно использовать с lock_guard и unique_guard! 16
Записать в flag 1,
вернуть то, что было
Записать 0 в flag
Операции сохранения и загрузки атомарных типов
// Объявить переменную и проициализировать true
std::atomic<bool> b(true);
// Загрузить значение в переменной в неатомарную
// переменную x
bool x = b.load(std::memory_order_acquire);
// Записать в переменную b значение true
b.store(true);
// Обменять значение переменной b со значением false,
// вернуть предыдущее значение b в переменную x.
x = b.exchange(false, std::memory_order_acq_rel);
17
Операция “сравнить и обменять”
Операция compare_exchange (“сравнить и обменять”):
1. Сравнить текущее значение атомарной переменной с
ожидаемым expected.
2. Если значения совпали, сохранить новое значение
desired и вернуть true.
3. Если значения не совпадают, то ожидаемое значение
expected заменяется фактическим значением
переменной, функция возвращает false.
bool compare_exchange_weak(T& expected, T desired,
std::memory_order order =
std::memory_order_seq_cst);
bool compare_exchange_strong(T& expected, T desired,
std::memory_order order =
std::memory_order_seq_cst);
18
Операция “сравнить и обменять”
compare_exchange_weak() - сохранение может не
произойти, даже если текущее значение совпадает с
ожидаемым. Значение переменной не изменится, функция
возвращает false.
Последнее возможно при отсутствии аппаратной
поддержки команды сравнить-и-обменять, из-за того, что
поток может быть вытеснен в середине требуемой
последовательности команд (ложный отказ).
Из-за возможного ложного отказа функцию
compare_exchange_weak() обычно вызывают в цикле:
bool expected = false;
extern atomic<bool> b;
...
while (!b.compare_exchange_weak(expected, true);
19
Операция “сравнить и обменять”
compare_exchange_strong() гарантирует замену
переменной в случае выполнения условия.
▪ compare_exchange_strong() выгодно использовать
в случае однократного выполнения операции, т.е. при
необходимости заменить значение на жалаемое, и в
случае, если вычисление нового значения занимает
длительное время.
▪ Если функция compare_exchange вызывается в цикле,
тогда предпочтительнее использовать
compare_exchange_weak, чтобы избежать двойного
цикла (compare_exchange_strong реализован в виде
цикла на системах, которые не поддерживают атомарной
операции сравнения и замены).
20
Атомарный тип atomic<T*>
Функции для типа atomic<T*>:
▪ is_lock_free, load, store, exchange,
compare_exchange_weak, compare_exchange_strong
▪ обменять и прибавить:
fetch_add, operator++, operator+=
▪ обменять и вычесть:
fetch_sub, operator--, operator-=
class C {};
C arr[10];
std::atomic<C*> ptr(arr);
C* x = ptr.fetch_add(2);
assert(x == arr);
assert(p.load() == &some_array[2]);
p--; x = p;
assert(x == &arr[1]);
операции
чтения-
модификации-
записи
21
Стандартные атомарные целочисленные типы
Функции для типов стандартных атомарных
целочисленных типов (int, unsigned int, long,
unsigned long, etc):
▪ is_lock_free, load, store, exchange,
compare_exchange_weak,
compare_exchange_strong
▪ обменять и прибавить (fetch_add, operator++,
operator+=) обменять и вычесть (fetch_sub,
operator--, operator-=)
▪ operator&=, operator|=, operator^=
Отсутствуют операции:
▫ умножения, деления, сдвига
22
Пользовательские атомарные типы
В качестве шаблона std::atomic<> может
выступать тип, он должен удовлетворять
требованиям
▪ В нём должен присутствовать тривиальный
оператор присваивания. Нет виртуальных
функций и виртуальных базовых классов, а
оператор присваивания генерируется
автоматически (например, memcpy)
▪ Тип должен допускать побитовое сравнение на
равенство (например, с помощью memcmp)
23
Метаморфозы
программы
или страшная правда
24
О дивный новый и прекрасный параллельный мир!
Выполняет ли компьютер программу, которую вы
написали?
25
О дивный новый и прекрасный параллельный мир!
Выполняет ли компьютер программу, которую вы
написали?
НЕТ
26
О дивный новый и прекрасный параллельный мир!
Выполняет ли компьютер программу, которую вы
написали?
НЕТПросто дело в том что...
иерархическая структура памяти, внеочередное
выполнение команд процессора и компиляторная
оптимизация. 27
Аппаратно вызванные трансформации программы
кэш 2
кэш 1
Память
кэш 2
кэш 1 кэш 1 кэш 1
Процессорные
ядра
28
Аппаратно вызванные трансформации программы
кэш 2
кэш 1
Память
кэш 2
кэш 1 кэш 1 кэш 1
Процессорные
ядра
Когерентность
кэша (MESI,
MOESI, MESIF)
29
Аппаратно вызванные трансформации программы
кэш 2
кэш 1
Память
кэш 2
кэш 1 кэш 1 кэш 1
Процессорные
ядра
Внеочередное
выполнение
инструкций (out-
of-order execution)
Когерентность
кэша (MESI,
MOESI, MESIF)
30
Аппаратно вызванные трансформации программы
Intel Nehalem Core
Pipeline
31
Аппаратно вызванные трансформации программы
32
Компиляторная оптимизация - виды оптимизаций
▪ Peephole-оптимизация
▪ Локальная оптимизация
▪ Внутрипроцедурная оптимизация
▪ Оптимизация циклов
▪ Межпроцедурная оптимизация
33
Компиляторная оптимизация - виды оптимизаций
▪ Peephole-оптимизация
▪ Локальная оптимизация
▪ Внутрипроцедурная оптимизация
▪ Оптимизация циклов
▫ Анализ индуктивных переменных
▫ Деление цикла на части
▫ Объединение циклов
▫ Инверсия цикла
▫ Расщепление цикла
▪ Межпроцедурная оптимизация 34
Компиляторная оптимизация - виды оптимизаций
-O
-fauto-inc-dec
-fbranch-count-reg
-fcombine-stack-adjustments
-fcompare-elim
-fcprop-registers
-fdce
-fdefer-pop
-fdelayed-branch
-fdse
-fforward-propagate
-fguess-branch-probability
-fif-conversion2
-fif-conversion
-finline-functions-called-once
-fipa-pure-const
-fipa-profile
-fipa-reference
-fmerge-constants
-fmove-loop-invariants
-fshrink-wrap
-fsplit-wide-types
-ftree-bit-ccp
-ftree-ccp
-fssa-phiopt
-ftree-ch
-ftree-copy-prop
-ftree-copyrename
-ftree-dce
-ftree-dominator-opts
-ftree-dse
-ftree-forwprop
-ftree-fre
-ftree-phiprop
-ftree-sink
-ftree-slsr
-ftree-sra
-ftree-pta
-ftree-ter
-funit-at-a-time
-O2
-fauto-inc-dec
-fbranch-count-reg
-fcombine-stack-adjustments
-fcompare-elim
-fcprop-registers
-fdce
-fdefer-pop
-fdelayed-branch
-fdse
-fforward-propagate
-fguess-branch-probability
-fif-conversion2
-fif-conversion
-finline-functions-called-once
-fipa-pure-const
-fipa-profile
-fipa-reference
-fmerge-constants
-fmove-loop-invariants
-fshrink-wrap
-fsplit-wide-types
-ftree-bit-ccp
-ftree-ccp
-fssa-phiopt
-ftree-ch
-ftree-copy-prop
-ftree-copyrename
-ftree-dce
-ftree-dominator-opts
-ftree-dse
-ftree-forwprop
-ftree-fre
-ftree-phiprop
-ftree-sink
-ftree-slsr
-ftree-sra
-ftree-pta
-ftree-ter
-funit-at-a-time
-fthread-jumps
-falign-functions -falign-
jumps
-falign-loops
-falign-labels
-fcaller-saves
-fcrossjumping
-fcse-follow-jumps -fcse-skip-
blocks
-fdelete-null-pointer-checks
-fdevirtualize -fdevirtualize-
speculatively
-fexpensive-optimizations
-fgcse
-fgcse-lm
-fhoist-adjacent-loads
-finline-small-functions
-findirect-inlining
-fipa-cp
-fipa-sra
-fipa-icf
-fisolate-erroneous-paths-
dereference
-foptimize-sibling-calls
-foptimize-strlen
-fpartial-inlining
-fpeephole2
-freorder-blocks -freorder-
blocks-and-partition -freorder-
functions
-frerun-cse-after-loop
-fsched-interblock -fsched-
spec
-fschedule-insns -fschedule-
insns2
-fstrict-aliasing -fstrict-
overflow
-ftree-builtin-call-dce
-ftree-switch-conversion -
ftree-tail-merge
-ftree-pre
-ftree-vrp
-fuse-caller-save
-O3
-fauto-inc-dec
-fbranch-count-reg
-fcombine-stack-adjustments
-fcompare-elim
-fcprop-registers
-fdce
-fdefer-pop
-fdelayed-branch
-fdse
-fforward-propagate
-fguess-branch-probability
-fif-conversion2
-fif-conversion
-finline-functions-called-once
-fipa-pure-const
-fipa-profile
-fipa-reference
-fmerge-constants
-fmove-loop-invariants
-fshrink-wrap
-fsplit-wide-types
-ftree-bit-ccp
-ftree-ccp
-fssa-phiopt
-ftree-ch
-ftree-copy-prop
-ftree-copyrename
-ftree-dce
-ftree-dominator-opts
-ftree-dse
-ftree-forwprop
-ftree-fre
-ftree-phiprop
-ftree-sink
-ftree-slsr
-ftree-sra
-ftree-pta
-ftree-ter
-funit-at-a-time
-fthread-jumps
-falign-functions -falign-jumps
-falign-loops
-falign-labels
-fcaller-saves
-fcrossjumping
-fcse-follow-jumps -fcse-skip-
blocks
-fdelete-null-pointer-checks
-fdevirtualize -fdevirtualize-
speculatively
-fexpensive-optimizations
-fgcse
-fgcse-lm
-fhoist-adjacent-loads
-finline-small-functions
-findirect-inlining
-fipa-cp
-fipa-sra
-fipa-icf
-fisolate-erroneous-paths-
dereference
-foptimize-sibling-calls
-foptimize-strlen
-fpartial-inlining
-fpeephole2
-freorder-blocks -freorder-blocks-
and-partition -freorder-functions
-frerun-cse-after-loop
-fsched-interblock -fsched-spec
-fschedule-insns -fschedule-insns2
-fstrict-aliasing -fstrict-overflow
-ftree-builtin-call-dce
-ftree-switch-conversion -ftree-
tail-merge
-ftree-pre
-ftree-vrp
-fuse-caller-save
-finline-functions
-funswitch-loops
-fpredictive-commoning
-fgcse-after-reload
-ftree-loop-vectorize
-ftree-loop-distribute-patterns
-ftree-slp-vectorize
-fvect-cost-model
-ftree-partial-pre
-fipa-cp-clone
35
Компиляторная оптимизация - примеры оптимизаций
x = 1;
y = 2;
x = 3;
for (i = 0; i < n; i++)
sum += a[i];
y = 2;
x = 3;
r1 = sum;
for (i = 0; i < n; i++)
r1 += a[i];
sum = r1;
for (i = 0; i < n; i++)
j = 42 * i;
j = -42
for (i = 0; i < n; i++)
j = j + 42;
36
Компиляторная оптимизация - примеры оптимизаций
x = Johann;
y = Sebastian;
z = Bach;
for (i = 0; i < n; i++)
for (j = 0; j < m;j++);
a[i][j] = 1;
z = Bach;
x = Johann;
y = Sebastian;
for (j = 0; j < n; j++)
for (i = 0; i < m;
i++);
a[i][j] = 1;
for (i = 0; i < n; i++)
a[i] = 1;
for (i = 0; i < n; i++)
b[i] = 2;
for (i = 0; i < n; i++)
{
a[i] = 1;
b[i] = 2;
} 37
Компиляторная оптимизация - примеры оптимизаций
Что компилятор знает:
Все операции с памятью совершаются в текущем
потоке, что они в точности означают, и какие
существуют зависимости по данным.
Что компилятор не знает:
Какие области памяти доступны и изменяются в
разных потоках.
Как решить:
Сказать! Как-то пометить операции, которые
выполняются с разделяемыми переменными.
38
Этапы трансформации программы
Исходный код
Компилятор
удаление подвыражений, свёртка
констант, оптимизация циклов, ...
Процессор
предвыборка, спекулятивное
выполнение инструкций,
буферизация, HTM, ...
Реальное выполнение
программы
Кэш
частные и общие кэши, буферы
записи, ...
“Гораздо лучше
выполнять другую
программу - не ту, что
вы написали. Вам на
самом деле даже не
хочется выполнять эту
вашу чушь - вы хотите
запускать другую
программу! Всё это
делается для вашего
блага.”
39
Этапы трансформации программы
▪ Как правило нельзя
определить, на каком уровне
произошла трансформация.
▪ Трансформации на всех
уровнях эквивалентны, что
позволяет рассматривать их
как переупорядочивание
операций загрузки (loads)
и записи (stores).
▪ Необходимое условие при
выполнении трансформаций
- сохранение иллюзии
последовательно
согласованного кода.
40
Исходный код
Компилятор
удаление подвыражений, свёртка
констант, оптимизация циклов, ...
Процессор
предвыборка, спекулятивное
выполнение инструкций,
буферизация, HTM, ...
Реальное выполнение
программы
Кэш
частные и общие кэши, буферы
записи, ...
Последовательная согласованность (sequential consistency, SC)
“Результат выполнения программы такой, как если бы
операции всех процессоров выполнялись последовательно и
результат операции каждого отдельного процессора
появлялся бы в этой последовательности в порядке,
который определяется программой.” (Л. Лэмпорт, 1979)
▪ Рассмотрим многопроцессорную систему, состоящую из
нескольких последовательных процессоров.
▪ Операции, выполняемые процессорами над некоторой
областью памяти (страница, объект, адрес, ...),
появляются в одном и том же порядке для всех
процессоров, несмотря на то, что фактическая
последовательность выполнения операций может
быть другой.
41
Последовательная согласованность (sequential consistency, SC)
“Результат выполнения программы такой, как если бы
операции всех процессоров выполнялись последовательно и
результат операции каждого отдельного процессора
появлялся бы в этой последовательности в порядке,
который определяется программой.” (Л. Лэмпорт, 1979)
▪ Рассмотрим многопроцессорную систему, состоящую из
нескольких последовательных процессоров.
▪ Операции, выполняемые процессорами над некоторой
областью памяти (страница, объект, адрес, ...),
появляются в одном и том же порядке для всех
процессоров, несмотря на то, что фактическая
последовательность выполнения операций может
быть другой.
И это прекрасно!
42
Последовательная согласованность (sequential consistency, SC)
“Результат выполнения программы такой, как если бы
операции всех процессоров выполнялись последовательно и
результат операции каждого отдельного процессора
появлялся бы в этой последовательности в порядке,
который определяется программой.” (Л. Лэмпорт, 1979)
▪ Рассмотрим многопроцессорную систему, состоящую из
нескольких последовательных процессоров.
▪ Операции, выполняемые процессорами над некоторой
областью памяти (страница, объект, адрес, ...),
появляются в одном и том же порядке для всех
процессоров,
несмотря на то, что фактическая последовательность
выполнения операций может быть другой.
И это прекрасно!
но...43
Последовательная согласованность (sequential consistency, SC)
… но вы этого не хотите!
▪ Скорее всего очень нерационально выполнять в точности
то, что вы написали.
▪ Гораздо лучше выполнить нечто иное, которое бы работало
так же, как и то, что вы написали, но выполнялось бы
гораздо быстрее.
Поэтому
▪ Мы (программное обеспечение ПО: компилятор и
аппаратное обеспечение АО: кэш, процессор) будем это
делать!
▪ А вы (программисты), в свою очередь, должны обеспечить
возможность корректной трансформации и выполнения
программы так, чтобы сохранялась иллюзия
последовательной согласованности, включив в свою
программу необходимые ограничения. 44
Модель памяти - это договор
Вы обещаете
Корректно
реализовать
синхронизацию
в вашей
программе
(путём добавления
необходимых
инструкций в
программу,
делающих её
безопасной
относительно гонок)
Система
обещает
Обеспечить
иллюзию
выполнения той
программы,
которую вы
написали.
(путём компиляции
и выполнения)
45
Модель памяти - это договор
Вы обещаете
Корректно
реализовать
синхронизацию
в вашей
программе
(путём добавления
необходимых
инструкций в
программу,
делающих её
безопасной
относительно гонок)
Система
обещает
Обеспечить
иллюзию
выполнения той
программы,
которую вы
написали.
(путём компиляции
и выполнения)
Модель памяти определяет, какие действия вы должны
совершить и как должна отреагировать система, чтобы
обеспечить выполнение операций с памятью в необходимой
последовательности. 46
Аппаратное
переупорядочивание
инструкций
47
Аппаратное переупорядочивание инструкций
Первое правило робототехники компиляторов и
процессоров при упорядочивании доступа к памяти:
Нельзя изменять поведение
однопоточной программы.
▪ В однопоточных программах переупорядочивания
остаются незамеченными.
▪ То же самое - при многопоточном программировании
на основе мьютексов, семафоров и т.д.
▪ Но не при использовании атомарных переменных
и техник программирования без блокировок.
48
Переупорядочивание на примере алгоритма Деккера
Поток 1
flag1 = 1;
if (flag2 != 0)
// ожидать освобождения
// критической секции
else
// войти в критическую
// секцию
Алгоритм Деккера позволяет решать проблему взаимного
исключения (в теории), был опубликован в 1965 г.
Он не приводит к взаимным исключениям (deadlock) и
свободен от голодания (starvation).
Поток 2
flag2 = 1;
if (flag1 != 0)
// ожидать освобождения
// критической секции
else
// войти в критическую
// секцию
49
Переупорядочивание на примере алгоритма Деккера
Процессор 1 Процессор 2
flag2 = 1;
if (flag1 != 0)
{ … }
Память: flag1 = 0, flag2 = 0
flag1 = 1;
if (flag2 != 0)
{ … }
Store Buffer Store Buffer
50
Переупорядочивание на примере алгоритма Деккера
Процессор 1 Процессор 2
Память: flag1 = 0, flag2 = 0
flag1 = 1;
if (flag2 != 0)
{ … }
flag2 = 1;
if (flag1 != 0)
{ … }
Store Buffer
flag1 = 1
Store Buffer
Сохранение
1 в буфере
51
Переупорядочивание на примере алгоритма Деккера
Процессор 1 Процессор 2
Память: flag1 = 0, flag2 = 0
flag1 = 1;
if (flag2 != 0)
{ … }
flag2 = 1;
if (flag1 != 0)
{ … }
Store Buffer
flag1 = 1
Сохранение
1 в буфере
Сохранение
1 в буфере
Store Buffer
flag1 = 1
52
Переупорядочивание на примере алгоритма Деккера
Процессор 1 Процессор 2
Память: flag1 = 0, flag2 = 0
flag1 = 1;
if (flag2 != 0)
{ … }
flag2 = 1;
if (flag1 != 0)
{ … }
Store Buffer
flag1 = 1
Сохранение
1 в буфере
Сохранение
1 в буфере
Store Buffer
flag1 = 1
Чтение 0
для flag2
Чтение 0
для flag1
StoreLoad
53
Переупорядочивание на примере алгоритма Деккера
Процессор 1 Процессор 2
Память: flag1 = 0, flag2 = 0
flag1 = 1;
if (flag2 != 0)
{ … }
flag2 = 1;
if (flag1 != 0)
{ … }
Store Buffer
flag1 = 1
Сохранение
1 в буфере
Сохранение
1 в буфере
Store Buffer
flag1 = 1
Чтение 0
для flag2
Чтение 0
для flag1
54
Переупорядочивание на примере алгоритма Деккера
Процессор 1 Процессор 2
Память: flag1 = 1, flag2 = 1
flag1 = 1;
if (flag2 != 0)
{ … }
flag2 = 1;
if (flag1 != 0)
{ … }
Store Buffer
flag1 = 1
Сохранение
1 в буфере
Сохранение
1 в буфере
Store Buffer
flag1 = 1
Чтение 0
для flag2
Чтение 0
для flag1
Сброс буфера
(flag1)
Сброс буфера
(flag2)
55
Переупорядочивание в различных процессорах
Тип
переупоря-
дочивания /
архитектуры
Alpha ARMv7 POWER SPARC
RMO
SPARC
PSO
SPARC
TSO
x86 AMD64 IA-64
Loads после
loads
Loads после
stores
Stores после
stores
Stores после
loads
АО с loads
АО с stores
Зависимые
loads
АО - атомарные операции 56
Переупорядочивание в различных процессорах
IBM Blue Gene
Смартфоны
DEC Alpha
Xbox 360
PowerPC
ARM
SPARC TSO
x86 / 64
более сильное упорядочивание
(strong model, sequential consistency)
более слабое упорядочивание
(weak model, relaxed ordering)
Как правило сильное
упорядочивание
Ослабленное упорядочивание
с поддержкой зависимостей
по данным
Вполне
ослабленное
упорядочивание
PowerMac
57
Переупорядочивание в различных процессорах
IBM Blue Gene
Смартфоны
DEC Alpha
Xbox 360
PowerPC
ARM
SPARC TSO
x86 / 64
более сильное упорядочивание
(strong model, sequential consistency)
более слабое упорядочивание
(weak model, relaxed ordering)
Как правило сильное
упорядочивание
Ослабленное упорядочивание
с поддержкой зависимостей
по данным
Вполне
ослабленное
упорядочивание
PowerMac
При сильном
упорядочивании
(сильная модель памяти)
каждая инструкция
неявно реализует
семантику захвата и
освобождения.
При слабом
упорядочивании
(слабая модель памяти)
не накладывается
никаких ограничений на
порядок выполнения
инструкций.
58
Переупорядочивание в процессорах архитектуры x86
IBM Blue Gene
Смартфоны
DEC Alpha
Xbox 360
PowerPC
ARM
SPARC TSO
x86 / 64
более сильное упорядочивание
(strong model, sequential consistency)
более слабое упорядочивание
(weak model, relaxed ordering)
Как правило сильное
упорядочивание
Ослабленное упорядочивание
с поддержкой зависимостей
по данным
Вполне
ослабленное
упорядочивание
PowerMac
59
Переупорядочивание в процессорах архитектуры x86
Intel Architectures Software Developer’s Manual, Vol. 3:
▪ Операции чтения не могут быть переупорядочены с другими
операциями чтения
▪ Операции записи не могут быть переупорядочены с другими
операциями записями
▪ Операции записи не могут быть переупорядочены с другими
операциями записи, кроме следующих исключений: …
▪ Операции чтения могут быть переупорядочены с более
старыми операциями записи в другие области памяти,
но не с операциями записи в ту же область.
▪ Операции чтения не могут перейти раньше инструкций LFENCE,
MFENCE, операции записи - раньше инструкций LFENCE,
SFENCE, MFENCE.
▪ LFENCE, SFENCE, MFENCE не могут выполниться раньше
операций чтения, записи, того или другого соответственно.
60
Переупорядочивание в процессорах архитектуры x86
Процессор 1
mov x, 1 ; запись (store)
; 1 в x
mov r1, y ; загрузка (load)
; из y в регистр
Процессор 2
mov y, 1 ; запись (store)
; 1 в x
mov r2, x ; загрузка (load)
; из y в регистр
Возможные варианты:
▪ r1 = 0, r2 = 1
▪ r1 = 1, r2 = 0
▪ r1 = 1, r2 = 1
▪ r1 = 0, r2 = 0
x = 0, y = 0
61
Переупорядочивание в процессорах архитектуры x86
Процессор 1
mov x, 1 ; запись (load)
; 1 в x
mov r1, y ; загрузка (store)
; из y в регистр
Процессор 2
mov y, 1 ; запись (load)
; 1 в x
mov r2, x ; загрузка (store)
; из y в регистр
▪ r1 = 0, r2 = 0
В процессорах архитектуры x86 достустима перестановка
операция load после store. Или, то же, операции store могут
выполняться до операций load.
x = 0, y = 0
StoreLoad
62
Переупорядочивание в процессорах архитектуры x86
Поток 1
x = 1;
asm volatile("" :::
"memory");
r1 = y;
Программный барьер памяти:
запретить переупорядочивание
инструкций компилятором.
63
Переупорядочивание в процессорах архитектуры x86
Поток 1
x = 1;
asm volatile("" :::
"memory");
r1 = y;
Поток 3
if (r1 == 0 && r2 == 0)
printf("reordering happened!n");
Поток 2
y = 1;
asm volatile("" :::
"memory");
r2 = x;
64
Переупорядочивание
может произойти!
Переупорядочивание в процессорах архитектуры x86
Поток 1
x = 1;
asm volatile("" :::
"memory");
r1 = y;
Поток 3
if (r1 == 0 && r2 == 0)
printf("reordering happened!n");
Поток 2
y = 1;
asm volatile("" :::
"memory");
r2 = x;
65
Переупорядочивание в процессорах архитектуры x86
Поток 1
x = 1;
asm volatile("mfence" :::
"memory");
asm volatile("" :::
"memory");
r1 = y;
Поток 2
y = 1;
asm volatile("mfence" :::
"memory");
asm volatile("" :::
"memory");
r2 = x;
Поток 3
if (r1 == 0 && r2 == 0)
printf("reordering happened!n");
66
Переупорядочивание в процессорах архитектуры x86
Поток 1
x = 1;
asm volatile("mfence" :::
"memory");
asm volatile("" :::
"memory");
r1 = y;
Программный барьер памяти:
запретить переупорядочивание
инструкций компилятором.
Аппаратный барьер памяти:
запретить переупорядочивание
инструкций процессором.
67
mov DWORD PTR X[rip], 1
mfence
mov eax, DWORD PTR Y[rip]
...
mov DWORD PTR r1[rip], eax
полный барьер памяти
Переупорядочивание в процессорах архитектуры x86
Поток 1
x = 1;
asm volatile("mfence" :::
"memory");
asm volatile("" :::
"memory");
r1 = y;
Поток 3
if (r1 == 0 && r2 == 0)
printf("reordering happened!n");
Переупорядочивание
не произойдёт!
Поток 2
y = 1;
asm volatile("mfence" :::
"memory");
asm volatile("" :::
"memory");
r2 = x;
68
Переупорядочивание в процессорах с ослабленным упорядочиванием
IBM Blue Gene
Смартфоны
DEC Alpha
Xbox 360
PowerPC
ARM
SPARC TSO
x86 / 64
более сильное упорядочивание
(strong model, sequential consistency)
более слабое упорядочивание
(weak model, relaxed ordering)
Как правило сильное
упорядочивание
Ослабленное упорядочивание
с поддержкой зависимостей
по данным
Вполне
ослабленное
упорядочивание
PowerMac
69
Переупорядочивание в процессорах с ослабленным упорядочиванием
Для ослабленного упорядочивания характерно то, что одно ядро может
видеть изменения в общей памяти в порядке, отличном от порядка, в
котором другое ядро вносит изменения.
int sharedCount; // глобальный счётчик
void IncShared() {
int count = 0; // локальный счётчик
while (count < N) {
randomBusyWork(); // случайная задержка
// наивная реализация мьютекса
int expected = 0;
if (flag.compare_exchange_strong(expected, 1,
std::memory_order_relaxed)); {
sharedVal++; // выполняется под защитой мьютекса
flag.store(0, std::memory_order_relaxed))
count++;
}
}
70
Переупорядочивание в процессорах с ослабленным упорядочиванием
Для ослабленного упорядочивания характерно то, что одно ядро может
видеть изменения в общей памяти в порядке, отличном от порядка, в
котором другое ядро вносит изменения.
int sharedCount; // глобальный счётчик
void IncShared() {
int count = 0; // локальный счётчик
while (count < N) {
randomBusyWork(); // случайная задержка
// наивная реализация мьютекса
int expected = 0;
if (flag.compare_exchange_strong(expected, 1,
std::memory_order_relaxed)); {
sharedVal++; // выполняется под защитой мьютекса
flag.store(0, std::memory_order_relaxed))
count++;
}
} StoreStore
71
int sharedCount; // глобальный счётчик
void IncShared() {
int count = 0; // локальный счётчик
while (count < N) {
randomBusyWork(); // случайная задержка
// наивная реализация мьютекса
int expected = 0;
if (flag.compare_exchange_strong(expected, 1,
std::memory_order_relaxed)); {
sharedVal++; // выполняется под защитой мьютекса
asm volatile("" ::: "memory");
flag.store(0, std::memory_order_relaxed))
count++;
}
}
StoreStore
Переупорядочивание в процессорах с ослабленным упорядочиванием
Для ослабленного упорядочивания характерно то, что одно ядро может
видеть изменения в общей памяти в порядке, отличном от порядка, в
котором другое ядро вносит изменения.
запрет компиляторного
переупорядочивания 72
Переупорядочивание в процессорах с ослабленным упорядочиванием
Поток 1
int sharedCount;
void IncShared() {
int count = 0;
while (count < N) {
randomBusyWork();
int expected = 0;
if (...) {
sharedVal++;
asm volatile(...);
flag.store(0, ...)
count++;
}
}
Поток 2
int sharedCount;
void IncShared() {
int count = 0;
while (count < N) {
randomBusyWork();
int expected = 0;
if (...) {
sharedVal++;
asm volatile(...);
flag.store(0, ...)
count++;
}
}
73
Переупорядочивание в процессорах с ослабленным упорядочиванием
Поток 1
int sharedCount;
void IncShared() {
int count = 0;
while (count < N) {
randomBusyWork();
int expected = 0;
if (...) {
sharedVal++;
asm volatile(...);
(1) flag.store(0, ...);
count++;
}
}
Поток 2
int sharedCount;
void IncShared() {
int count = 0;
while (count < N) {
randomBusyWork();
int expected = 0;
if (...) {
sharedVal++;
asm volatile(...);
flag.store(0, ...)
count++;
}
}
74
Переупорядочивание в процессорах с ослабленным упорядочиванием
Поток 1
int sharedCount;
void IncShared() {
int count = 0;
while (count < N) {
randomBusyWork();
int expected = 0;
if (...) {
(2) sharedVal++;
asm volatile(...);
(1) flag.store(0, ...);
count++;
}
}
Поток 2
int sharedCount;
void IncShared() {
int count = 0;
while (count < N) {
randomBusyWork();
int expected = 0;
if (...) {
(3) sharedVal++;
asm volatile(...);
flag.store(0, ...)
count++;
}
}
75
Переупорядочивание в процессорах с ослабленным упорядочиванием
$ ./prog 100000 // N = 100000
sharedCount = 199348
sharedCount = 199034
sharedCount = 199517
sharedCount = 199829
sharedCount = 199113
sharedCount = 199566
Допустим N = 100000
Каждый из 2 потоков выполняет 100000 операций
инкремента разделяемой переменной sharedCount++.
Ожидаемое значение: sharedVal = 200000
В реальности:
76
Переупорядочивание при выполнении операция захвата-освобождения в
процессорах архитектуры POWER
Поток 1 (процессор 1)
// Подготавливаем данные
// и устанавливаем флаг
// готовности
for (i = 0; i < n; i++) {
widget[i].x = x;
widget[i].y = y;
widget[i].ready = true;
}
Поток 2 (процессор 2)
// Если данные готовы,
// обрабатываем их
for (i = 0; i < n; i++) {
if (widget[i].ready) {
do_some_stuff(widget[i]);
}
}
L2-кэш
Буфер 1 Буфер 2 Буфер N
... ...
77
Переупорядочивание при выполнении операция захвата-освобождения в
процессорах архитектуры POWER
Поток 1 (процессор 1)
// Подготавливаем данные
// и устанавливаем флаг
// готовности
for (i = 0; i < n; i++) {
widget[i].x = x;
widget[i].y = y;
widget[i].ready = true;
}
Поток 2 (процессор 2)
// Если данные готовы,
// обрабатываем их
for (i = 0; i < n; i++) {
if (widget[i].ready) {
do_some_stuff(widget[i]);
}
}
L2-кэш
Буфер 1 Буфер 2 Буфер N
... ...
78
Переупорядочивание при выполнении операция захвата-освобождения в
процессорах архитектуры POWER
Поток 1 (процессор 1)
// Подготавливаем данные
// и устанавливаем флаг
// готовности
for (i = 0; i < n; i++) {
widget[i].x = x;
widget[i].y = y;
widget[i].ready = true;
}
Поток 2 (процессор 2)
// Если данные готовы,
// обрабатываем их
for (i = 0; i < n; i++) {
if (widget[i].ready) {
do_some_stuff(widget[i]);
}
}
L2-кэш
Буфер 1 Буфер 2 Буфер N
... ...
79
Переупорядочивание при выполнении операция захвата-освобождения в
процессорах архитектуры POWER
Поток 1 (процессор 1)
// Подготавливаем данные
// и устанавливаем флаг
// готовности
for (i = 0; i < n; i++) {
widget[i].x = x;
widget[i].y = y;
widget[i].ready = true;
}
Поток 2 (процессор 2)
// Если данные готовы,
// обрабатываем их
for (i = 0; i < n; i++) {
if (widget[i].ready) {
do_some_stuff(widget[i]);
}
}
L2-кэш
Буфер 1 Буфер 2 Буфер N
... ...
80
Переупорядочивание при выполнении операция захвата-освобождения в
процессорах архитектуры POWER
Поток 1 (процессор 1)
// Подготавливаем данные
// и устанавливаем флаг
// готовности
for (i = 0; i < n; i++) {
widget[i].x = x;
widget[i].y = y;
widget[i].ready = true;
}
Поток 2 (процессор 2)
// Если данные готовы,
// обрабатываем их
for (i = 0; i < n; i++) {
if (widget[i].ready) {
do_some_stuff(widget[i]);
}
}
L2-кэш: widget[i].y = y; widget[i].ready = true;
... ...
81
Переупорядочивание при выполнении операция захвата-освобождения в
процессорах архитектуры POWER
Поток 1 (процессор 1)
// Подготавливаем данные
// и устанавливаем флаг
// готовности
for (i = 0; i < n; i++) {
widget[i].x = x;
widget[i].y = y;
widget[i].ready = true;
}
Поток 2 (процессор 2)
// Если данные готовы,
// обрабатываем их
for (i = 0; i < n; i++) {
if (widget[i].ready) {
do_some_stuff(widget[i]);
}
}
L2-кэш: widget[i].y = y; widget[i].ready = true;
... ...
StoreStore
82
Программное
переупорядочивание
инструкций
83
Программное переупорядочивание инструкций
Первое правило робототехники компиляторов и
процессоров при упорядочивании доступа к памяти:
Нельзя изменять поведение
однопоточной программы.
▪ В однопоточных программах переупорядочивания
остаются незамеченными.
▪ То же самое - при многопоточном программировании
на основе мьютексов, семафоров и т.д.
▪ Но не при использовании атомарных переменных
и техник программирования без блокировок.
84
Программное переупорядочивание инструкций
mov eax, DWORD PTR y[rip]
add eax, 111
mov DWORD PTR x[rip], eax
mov DWORD PTR y[rip], 222
int x, y;
int main() {
x = y + 111;
y = 222;
printf("%d%d", x, y);
gcc -S -masm=intel prog.c
выполнение команды
x = y + 111 завершено
выполнение команды
y = 222 завершено
85
Программное переупорядочивание инструкций
mov eax, DWORD PTR y[rip]
mov edx, 222
...
mov DWORD PTR y[rip], 222
lea esi, [rax+111]
...
mov DWORD PTR x[rip], esi
int x, y;
int main() {
x = y + 111;
y = 222;
printf("%d%d", x, y);
gcc -S -masm=intel -O2 prog.c
выполнение команды
y = 222 завершено
выполнение команды
x = y + 111 завершено
y = 222 выполняется
раньше x = y + 111
86
Программное переупорядочивание инструкций
mov eax, DWORD PTR y[rip]
add eax, 111
mov DWORD PTR x[rip], eax
mov esi, DWORD PTR x[rip]
mov edx, 222
...
xor eax, eax
mov DWORD PTR y[rip], 222
int x, y;
int main() {
x = y + 111;
asm volatile("" ::: "memory");
y = 222;
printf("%d%d", x, y);
gcc -S -masm=intel -O2 prog.c
явный барьер
87
Программное переупорядочивание инструкций на примере
с публикацией данных (операция захвата-освобождения)
int data;
bool isReleased = false; // данные опубликованы?
void releaseData(int val) // опубликовать данные
{
data = val; // записать данные
isReleased = true; // данные опубликованы!
} // можно с ними работать
88
Программное переупорядочивание инструкций на примере
с публикацией данных (операция захвата-освобождения)
Поток 1
int data;
bool isReleased = false;
void releaseData(int val)
{
data = val;
isReleased = true;
}
Поток 2
void utilizeData()
{
while (!isReleased);
doSomething(data);
}
Данные должны быть
проинициализированы в 1 потоке
перед тем, как они будут
использованы во 2 потоке.
89
Программное переупорядочивание инструкций на примере
с публикацией данных (операция захвата-освобождения)
Поток 1
int data;
bool isReleased = false;
void releaseData(int val)
{
data = val;
isReleased = true;
}
Поток 2
void utilizeData()
{
while (!isReleased);
doSomething(data);
}
1. Данные проиницаилизрованы
90
Программное переупорядочивание инструкций на примере
с публикацией данных (операция захвата-освобождения)
Поток 1
int data;
bool isReleased = false;
void releaseData(int val)
{
data = val;
isReleased = true;
}
Поток 2
void utilizeData()
{
while (!isReleased);
doSomething(data);
}
1. Данные проициализированы
2. Ура! Второй поток может
обрабатывать
91
Программное переупорядочивание инструкций на примере
с публикацией данных (операция захвата-освобождения)
Поток 1
int data;
bool isReleased = false;
void releaseData(int val)
{
data = val;
isReleased = true;
}
Поток 2
void utilizeData()
{
while (!isReleased);
doSomething(data);
}
92
Программное переупорядочивание инструкций на примере
с публикацией данных (операция захвата-освобождения)
Поток 1
int data;
bool isReleased = false;
void releaseData(int val)
{
data = val;
isReleased = true;
}
Поток 2
void utilizeData()
{
while (!isReleased);
doSomething(data);
}
Из-за переупорядочивания
инструкций компилятором флаг
выставляется до того, как
данные готовы.
93
StoreStore
Программное переупорядочивание инструкций на примере
с публикацией данных (операция захвата-освобождения)
Поток 1
int data;
bool isReleased = false;
void releaseData(int val)
{
data = val;
isReleased = true;
}
Поток 2
void utilizeData()
{
while (!isReleased);
doSomething(data);
}
Из-за переупорядочивания
инструкций компилятором флаг
выставляется до того, как
данные готовы.
StoreStore
94
Программное переупорядочивание инструкций на примере
с публикацией данных (операция захвата-освобождения)
Поток 1
int data;
bool isReleased = false;
void releaseData(int val)
{
data = val;
isReleased = true;
}
Поток 2
void utilizeData()
{
while (!isReleased);
doSomething(data);
}
Из-за переупорядочивания
инструкций компилятором флаг
выставляется до того, как
данные готовы.
95
Программное переупорядочивание инструкций на примере
с публикацией данных (операция захвата-освобождения)
Поток 1
int data;
bool isReleased = false;
void releaseData(int val)
{
data = val;
isReleased = true;
}
Поток 2
void utilizeData()
{
while (!isReleased);
doSomething(data);
}
96
Программное переупорядочивание инструкций на примере
с публикацией данных (операция захвата-освобождения)
Поток 1
int data;
bool isReleased = false;
void releaseData(int val)
{
data = val;
asm volatile("" :::
"memory");
isReleased = true;
}
Поток 2
void utilizeData()
{
while (!isReleased);
asm volatile("" :::
"memory");
doSomething(data);
}
97
Программное переупорядочивание инструкций на примере
с публикацией данных (операция захвата-освобождения)
Поток 1
int data;
bool isReleased = false;
void releaseData(int val)
{
data = val;
asm volatile("" :::
"memory");
isReleased = true;
}
Поток 2
void utilizeData()
{
while (!isReleased);
asm volatile("" :::
"memory");
doSomething(data);
}
Операция записи-
освобождения
Операция чтения-
захвата
98
Операции сохранения из чистого воздуха (out-of-thin air stores)
if (x > 0)
y++;
register int r = y;
if (x > 0)
r++;
y = r;
Размещение переменной в регистре
в процессе оптимизирующей
компиляции
создание новой регистровой
переменной
Новая операция сохранения
(store) “из чистого воздуха”
Создание операций сохранения “из чистого
воздуха” не допустимо в соответствии с
последним стандартом, однако... 99
Операции сохранения из чистого воздуха (out-of-thin air stores)
static pthread_mutex_t lock = PTHREAD_MUTEX_INITALIZER;
static int count = 0;
int trylock() {
int rc;
rc = pthread_mutex_trylock(&mutex);
if (rc == 0)
count++;
однако в С & PThreads такая оптимизация допускается:
static int count = 0;
int trylock() {
register int r = count;
int rc;
rc = pthread_mutex_trylock(&mutex);
if (rc == 0)
count++;
count = r;
новый store
и новая гонка данных! 100
Базовое отношение
happens-before
(происходит-раньше)
101
Отношение happens-before (происходит-раньше)
Отношение happens-before определяет, какие операции видят
последствия других операций.
Допустим A и В - операции в многопоточной программе. Тогда если
А происходит-раньше В, тогда эффект (результат операции,
который отразился в памяти) операции А становится видим для
потока, выполняющего операцию В.
x = 10; // A
y = x + 1; // B
102
Отношение happens-before (происходит-раньше)
Отношение happens-before определяет, какие операции видят
последствия других операций.
Допустим A и В - операции в многопоточной программе. Тогда если
А происходит-раньше В, тогда эффект (результат операции,
который отразился в памяти) операции А становится видим для
потока, выполняющего операцию В.
x = 10;
y = x + 1;
z = sqrt(x * y);
Все соотношения
happens-before для
операции А (x = 10)
103
x = 10;
y = x + 1;
z = sqrt(x * y);
m = k - 5;
print(m)
print(x)
Отношение happens-before (происходит-раньше)
Отношение happens-before определяет, какие операции видят
последствия других операций.
Допустим A и В - операции в многопоточной программе. Тогда если
А происходит-раньше В, тогда эффект (результат операции,
который отразился в памяти) операции А становится видим для
потока, выполняющего операцию В.
104
Поток 1 Поток 2
x = 10;
y = x + 1;
z = sqrt(x * y);
m = k - 5;
print(m)
w = x + 2
Отношение happens-before (происходит-раньше)
Отношение happens-before определяет, какие операции видят
последствия других операций.
Допустим A и В - операции в многопоточной программе. Тогда если
А происходит-раньше В, тогда эффект (результат операции,
который отразился в памяти) операции А становится видим для
потока, выполняющего операцию В.
105
Отношение happens-before определяет, какие операции видят
последствия других операций.
Допустим A и В - операции в многопоточной программе. Тогда если
А происходит-раньше В, тогда эффект (результат операции,
который отразился в памяти) операции А становится видим для
потока, выполняющего операцию В.
Происходит-раньше ≠ происходит раньше
1. Из того, что А происходит-раньше В не следует, что А
происходит раньше В.
2. Из того, что А происходит раньше В не следует, что А
происходит-раньше В
106
Происходит-раньше не означает происходит раньше
int x, y;
int main() {
x = y + 111; // A
y = 222; // B
printf("%d%d", x, y);
1. Из того, что А происходит-раньше В не следует, что А
происходит раньше В.
107
Происходит-раньше не означает происходит раньше
mov eax, DWORD PTR y[rip]
mov edx, 222
...
mov DWORD PTR y[rip], 222
lea esi, [rax+111]
...
mov DWORD PTR x[rip], esi
int x, y;
int main() {
x = y + 111; // A
y = 222; // B
printf("%d%d", x, y);
gcc -S -masm=intel -O2 prog.c
1. Из того, что А происходит-раньше В не следует, что А
происходит раньше В.
108
Происходит-раньше не означает происходит раньше
mov eax, DWORD PTR y[rip]
mov edx, 222
...
mov DWORD PTR y[rip], 222
lea esi, [rax+111]
...
mov DWORD PTR x[rip], esi
int x, y;
int main() {
x = y + 111;
y = 222;
printf("%d%d", x, y);
gcc -S -masm=intel -O2 prog.c
выполнение команды
y = 222 завершено
выполнение команды
x = y + 111 завершено
y = 222 выполняется
раньше x = y + 111
1. Из того, что А происходит-раньше В не следует, что А
происходит раньше В.
109
Происходит раньше не означает происходит-раньше
2. Из того, что А происходит раньше В не следует, что А
происходит-раньше В
Поток 2
if (ready) // B
print(x);
Поток 1
x = 42
ready = true; // A
int x;
bool ready = false;
110
Происходит раньше не означает происходит-раньше
2. Из того, что А происходит раньше В не следует, что А
происходит-раньше В
Поток 2
if (ready) // B
print(x);
Поток 1
x = 42
ready = true; // A
int x;
bool ready = false;
111
Происходит раньше не означает происходит-раньше
2. Из того, что А происходит раньше В не следует, что А
происходит-раньше В
Поток 2
if (ready)
print(x);
Поток 1
x = 42
ready = true;
int x;
bool ready = false;
112
происходит-раньше
Происходит раньше не означает происходит-раньше
2. Из того, что А происходит раньше В не следует, что А
происходит-раньше В
Поток 2
if (ready)
print(x);
Поток 1
x = 42
ready = true;
int x;
bool ready = false;
113
происходит-раньше
Отношение happens-before определяет, какие операции видят
последствия других операций.
Допустим A и В - операции в многопоточной программе. Тогда если
А происходит-раньше В, тогда эффект (результат операции,
который отразился в памяти) операции А становится видим для
потока, выполняющего операцию В.
Происходит-раньше ≠ происходит раньше
1. Из того, что А происходит-раньше В не следует, что А
происходит раньше В.
2. Из того, что А происходит раньше В не следует, что А
происходит-раньше В
Отношение происходит-раньше имеет место тогда (и только
тогда), когда это определёно стандартом языка.
114
Виды упорядочивания
инструкций в С++
115
Последовательная согласованность (sequential consistency)
std::atomic<int> x{0}, y{0}, v{0}, w{0}
void thread1() {
x.store(1, std::memory_order_seq_cst);
v.store(y.load(std::memory_order_seq_cst),
std::memory_order_seq_cst);
}
void thread2() {
y.store(1, std::memory_order_seq_cst);
w.store(x.load(std::memory_order_seq_cst),
std::memory_order_seq_cst);
}
int main() {
std::thread t1{thread1}, t2{thread2};
t1.join();
t2.join();
assert(v != 0 || w != 0); assert не сработает
116
Последовательная согласованность (sequential consistency)
std::atomic<int> data{0};
std::atomic<bool> ready{false};
int main() {
std::thread producer{[]{
data.store(42, std::memory_order_seq_cst);
ready.store(true, std::memory_order_seq_cst);
}};
std::thread consumer{[]{
while (!ready.load(std::memory_order_seq_cst)) { }
std::cout << data.load(std::memory_order_seq_cst);
}};
producer.join();
consumer.join();
42
117
Последовательная согласованность (sequential consistency)
std::atomic<int> z{0};
std::atomic<bool> x{false}, y{false};
int main(int argc, const char *argv[]) {
std::thread store_y{[]{
x.store(true, std::memory_order_seq_cst); }};
std::thread store_x{[]{
y.store(true, std::memory_order_seq_cst); }};
std::thread read_x_then_y{[]{
while (!x.load(std::memory_order_seq_cst)) { }
if (y.load(std::memory_order_seq_cst))
z.fetch_add(1, std::memory_order_seq_cst); }};
std::thread read_y_then_x{[]{
while (!y.load(std::memory_order_seq_cst)) { }
if (y.load(std::memory_order_seq_cst))
z.fetch_add(1, std::memory_order_seq_cst); }};
store_x.join(); store_y.join();
read_x_then_y.join(); read_y_then_x.join();
std::cout << z.load(std::memory_order_seq_cst);
z > 0
118
Ослабленное упорядочение (relaxed ordering)
std::atomic<int> x{0}, y{0}, v{0}, w{0}
void thread1() {
x.store(1, std::memory_order_relaxed);
v.store(y.load(std::memory_order_relaxed),
std::memory_order_relaxed);
}
void thread2() {
y.store(1, std::memory_order_relaxed);
w.store(x.load(std::memory_order_relaxed),
std::memory_order_relaxed);
}
int main() {
std::thread t1{thread1}, t2{thread2};
t1.join();
t2.join();
assert(v != 0 || w != 0);
assert может
сработать
119
Ослабленное упорядочение (relaxed ordering)
std::atomic<int> data{0};
std::atomic<bool> ready{false};
int main() {
std::thread producer{[]{
data.store(42, std::memory_order_relaxed);
ready.store(true, std::memory_order_relaxed);
}};
std::thread consumer{[]{
while (!ready.load(std::memory_order_relaxed)) { }
std::cout << data.load(std::memory_order_relaxed);
}};
producer.join();
consumer.join();
42 не гарантируется
120
Ослабленное упорядочение (relaxed ordering)
std::atomic<int> z{0};
std::atomic<bool> x{false}, y{false};
int main(int argc, const char *argv[]) {
std::thread store_y{[]{
x.store(true, std::memory_order_relaxed); }};
std::thread store_x{[]{
y.store(true, std::memory_order_relaxed); }};
std::thread read_x_then_y{[]{
while (!x.load(std::memory_order_relaxed)) { }
if (y.load(std::memory_order_relaxed))
z.fetch_add(1, std::memory_order_relaxed); }};
std::thread read_y_then_x{[]{
while (!y.load(std::memory_order_relaxed)) { }
if (y.load(std::memory_order_relaxed))
z.fetch_add(1, std::memory_order_relaxed); }};
store_x.join(); store_y.join();
read_x_then_y.join(); read_y_then_x.join();
std::cout << z.load(std::memory_order_relaxed);
z >= 0
121
Метафора человека с блокнотом
Иван
34
9
45
-3
21
0
9
7
11
Иван
Значение переменной x
x.load(memory_order_relaxed)
122
Метафора человека с блокнотом
Иван
34
9
45
-3
21
0
9
7
11
Иван
Значение переменной x
123
x.load(memory_order_relaxed)
Метафора человека с блокнотом
Иван
34
9
45
-3
21
0
9
7
11
Иван
Значение переменной x
124
x.load(memory_order_relaxed)
encore!
Метафора человека с блокнотом
Иван
34
9
45
-3
21
0
9
7
11
Значение переменной x
x.load(memory_order_relaxed)
Иван
125
Метафора человека с блокнотом
Иван
34
9
45
-3
21
0
9
7
11
-8
Иван
Значение переменной x
x.store(-8,
memory_order_relaxed)
126
Метафора человека с блокнотом
Иван
34
9
45
-3
21
0
9
7
11
-8
Значение переменной x
x.load(memory_order_relaxed)
Иван
127
Метафора человека с блокнотом
Иван
Сергей
Анна
... 34
9
45
-3
21
0
9
7
11
-8
Иван
Анна Сергей
Значение переменной x
128
Барьеры памяти
129
Виды переупорядочиваний
StoreLoad StoreStore
LoadStoreLoadLoad
130
Иерархическая структура современных многоядерных систем
кэш 2
кэш 1
Память
кэш 2
кэш 1 кэш 1 кэш 1
Процессорные
ядра
131
Метафора репозитория
1
кэш L1
2
кэш L1
Память
132
Метафора репозитория
1
кэш L1
2
кэш L1
Память
mov [x], 1
mov r1, [y]
mov [y], 1
mov r2, [x]
133
Метафора репозитория
1
2
Рекс
Мухтар
Центральный
репозиторий
134
Метафора репозитория
1
2
Рекс
Мухтар
Центральный
репозиторий
Утечка данных из
центрального
репозитория в
локальный и обратно
135
Метафора репозитория
1
2
Рекс
Мухтар
Центральный
репозиторий
mov [x], 1
x = 1
x = 0
Утечка данных из
центрального
репозитория в
локальный и обратно
x = 0
136
Метафора репозитория
1
2
Рекс
Мухтар
Центральный
репозиторий
mov [x], 1
x = 1
x = 1
Утечка данных из
центрального
репозитория в
локальный и обратно
x = 0
137
Метафора репозитория
1
2
Рекс
Мухтар
Центральный
репозиторий
mov [x], 1
x = 1
x = 1
Утечка данных из
центрального
репозитория в
локальный и обратно
x = 1
138
Метафора репозитория
1
2
Рекс
Мухтар
Центральный
репозиторий
mov [x], 1
x = 1
x = ?
x = ?
Неизвестно, когда
изменения распространятся
на другие потоки.
139
Метафора репозитория
1
2
Рекс
Мухтар
Центральный
репозиторий
mov [x], 1
mov r1, [y]
mov [y], 1
mov r2, [x]
x = 0
y = 0
x = 0
y = 0
x = 0
y = 0
140
Метафора репозитория
1
2
Рекс
Мухтар
Центральный
репозиторий
mov [x], 1
mov r1, [y]
mov [y], 1
mov r2, [x]
x = 0
y = 1
x = 1
y = 0
x = 0
y = 0
141
Метафора репозитория
1
2
Рекс
Мухтар
Центральный
репозиторий
mov [x], 1
mov r1, [y]
mov [y], 1
mov r2, [x]
x = ? {0,1}
y = 1
x = 1
y = ? {0,1}
x = ? {0,1}
y = ? {0,1}
142
Виды барьеров
StoreLoad StoreStore
LoadStoreLoadLoad
143
Барьер LoadLoad
1
Рекс
Центральный
репозиторий
LoadLoad
x = 1
y = 0
x = 1
y = 0
git pull, hg pull,
svn update,
cvs update
Барьер LoadLoad:
▪ Предотвращает переупорядочивания между
загрузками до барьера и загрузками после
барьера.
▪ Гарантирует, что загруженные из центрального
репозитория (памяти) в локальный репозиторий
(кэш) значения будут по крайней мере такие же
новые, как и последнее значение, которое
“просочилось” из центрального репозитория. 144
Барьер LoadLoad
1
Рекс
Центральный
репозиторий
LoadLoad
x = 1
y = 0
x = 1
y = 0
git pull, hg pull,
svn update,
cvs update
// Так получилось, что
// widget.ready = true утекло
// в локальный репозиторий
if (widget.ready) {
// Подгрузить всё остальное
// содержимое widget -
// такое же “свежее”, как ready
LoadLoadFence();
do_something(widget);
} 145
Барьер StoreStore
1
Рекс
Центральный
репозиторий
StoreStore
x = 1
y = 0
Барьер StoreStore:
▪ Предотвращает переупорядочивания между
сохранениями до барьера и сохранениями после
барьера.
▪ Гарантирует, что загруженные из локального
репозитория (кэш) в локальный репозиторий
(память) значения будут по крайней мере такие
же новые, как и последнее значение, которое
“просочилось” из локального репозитория.
x = 1
y = 0
git push, hg push,
svn commit, cvs
commit
146
Барьер StoreStore
1
Рекс
Центральный
репозиторий
StoreStore
x = 1
y = 0
x = 1
y = 0
git push, hg push,
svn commit, cvs
commit
widget.x = x;
widget.y = y;
StoreStoreFence();
widget.ready = true;
// Если ready = true просочится
// Мухтару, то он увидит увидит
// на центральном репозитории
// всё остальные поля widget,
// которые для него подготовил Рекс 147
Барьер LoadStore
1
Рекс
mov r1, [y]
mov r2, [x]
mov [z], 42
mov [w], r3
mov [v], r4
Операции загрузки
load
Операции сохранения
store
Переупорядочивание LoadStore
1. Есть набор инструкций, состоящий из операций
сохранения и загрузки.
148
Барьер LoadStore
1
Рекс
mov r1, [y]
mov r2, [x]
mov [z], 42
mov [w], r3
mov [v], r4
Операции загрузки
load - отложены
Операции сохранения
store - выполнены
Переупорядочивание LoadStore
1. Есть набор инструкций, состоящий из операций
сохранения и загрузки.
2. Если Рекс встречает операцию загрузки, то он
просматривает следующие операции сохранения,
и если они абсолютно не связаны с текущей
операцией загрузки, то он откладывает
выполнение операции загрузки и в первую
очередь выполняет операции сохранения. 149
Барьер LoadStore
1
Рекс
mov r1, [y]
mov r2, [x]
mov [z], 42
mov [w], r3
mov [v], r4
Будут промахи по
кэшу...
Будут попадания в
кэш...
Переупорядочивание LoadStore
1. Есть набор инструкций, состоящий из операций
сохранения и загрузки.
2. Если Рекс встречает операцию загрузки, которые
промахиваются по кэшу, то он просматривает следующие
операции сохранения, которые попадают в кэш, и если
они абсолютно не связаны с текущей операцией загрузки,
то он откладывает выполнение операции загрузки и в
первую очередь выполняет операции сохранения. 150
Барьер LoadStore
1
Рекс
mov r1, [y]
mov r2, [x]
LoadStore
mov [z], 42
mov [w], r3
mov [v], r4
Будут промахи по
кэшу...
Будут попадания в
кэш...
Переупорядочивание LoadStore
1. Есть набор инструкций, состоящий из операций
сохранения и загрузки.
2. Если Рекс встречает операцию загрузки, которые
промахиваются по кэшу, то он просматривает следующие
операции сохранения, которые попадают в кэш, и если
они абсолютно не связаны с текущей операцией загрузки,
то он откладывает выполнение операции загрузки и в
первую очередь выполняет операции сохранения. 151
Барьер StoreLoad
1
Рекс
mov [x], 1
mov r1, [y]
2
Мухтар
mov [y], 1
mov r2, [x]
x = 1
y = 0
x = 0
y = 1
r1 = 0
r2 = 0
152
Барьер StoreLoad
1
Рекс
mov [x], 1
StoreLoad
mov r1, [y]
2
Мухтар
mov [y], 1
StoreLoad
mov r2, [x]
x = 1
y = ?
x = ?
y = 1
Барьер StoreLoad:
▪ Гарантирует видимость для других
процессоров всех операций
сохранения, выполненных до
барьера.
▪ Обеспечивает для всех операций
загрузки, выполненных после
барьера, получение результатов,
которые имеют место во время
барьера.
▪ Барьер предотвращает r1 = r2 = 0
▪ StoreLoad ≠ StoreStore +
LoadLoad
153
Барьер StoreLoad
1
Рекс
mov [x], 1
StoreLoad
mov r1, [y]
x = 1
y = ?
Барьер StoreLoad (≠ StoreStore + LoadLoad):
1. Отправка (push) всех изменений в центральный репозиторий.
Центральный
репозиторий
154
Барьер StoreLoad
1
Рекс
mov [x], 1
StoreLoad
mov r1, [y]
x = 1
y = ?
Барьер StoreLoad (≠ StoreStore + LoadLoad):
1. Отправка (push) всех изменений в центральный репозиторий.
2. Ожидание завершения выполнения операции отправки (в
отличие от StoreStore, который может выполняться с задержкой).
Центральный
репозиторий
155
Барьер StoreLoad
1
Рекс
mov [x], 1
StoreLoad
mov r1, [y]
x = 1
y = ?
Барьер StoreLoad (≠ StoreStore + LoadLoad):
1. Отправка (push) всех изменений в центральный репозиторий.
2. Ожидание завершения выполнения операции отправки (в
отличие от StoreStore, который может выполняться с задержкой).
3. Загрузка (pull) всех последних изменений из центрального
репозитория (в отличие от LoadLoad, который не загружает
абсолютно последние изменения)
Центральный
репозиторий
156
Семантика захват-
освобождение (acquire-
release semantics)
157
Семантика захвата (acquire)
Семантика захвата и освобождения
▪ Применяется к операциям чтения или чтения-
модификации-записи, при этом такая операция
становится операцией чтения-захвата (read-acquire).
▪ Предотвращает переупорядочивание инструкции чтения-
захвата и всех следующих в программе операций чтения
или записи.
▪ Применяется к операциям записи или чтения-
модификации-записи, причём такая операция становится
операцией записи-освобождения (write-release).
▪ Предотвращает переупорядочивание инструкции записи-
освобождения со всеми предшествующими в программе
операциями чтения или записи.
Семантика освобождения (release) 158
read-acquire
Семантика захвата и освобождения
read / write
read / write
read / write
read / write
read / write
read / write
read / write
read / write
read / write
read / write
write-release 159
read-acquire
Семантика захвата и освобождения
read / write
read / write
read / write
read / write
read / write
read / write
read / write
read / write
read / write
read / write
write-release 160
Захват и освобождение - в терминах барьеров
StoreLoadFence StoreStoreFence
LoadStoreFenceLoadLoadFence
Acquire (захват)
Release (освобождение)
161
LoadLoadFence + LoadStoreFence
Захват и освобождение - в терминах барьеров
read / write
read / write
read / write
read / write
read / write
read / write
read / write
read / write
StoreStoreFence + LoadStoreFence
read-acquire
write-release 162
read / write
read / write
read / write
read / write
read / write
read / write
read / write
read / write
LoadLoadFence + LoadStoreFence
Захват и освобождение - в терминах барьеров
StoreStoreFence + LoadStoreFence
read-acquire
write-release 163
LoadLoadFence + LoadStoreFence
Захват и освобождение - в терминах барьеров
read / write
read / write
read / write
read / write
read / write
read / write
read / write
read / write
StoreStoreFence + LoadStoreFence
read-acquire
write-release 164
LoadLoadFence + LoadStoreFence
Захват и освобождение - в терминах барьеров
read / write
read / write
read / write
read / write
read / write
read / write
read / write
read / write
StoreStoreFence + LoadStoreFence
read-acquire
write-release
1
2
165
LoadLoadFence + LoadStoreFence
Захват и освобождение - в терминах барьеров
read / write
read / write
read / write
read / write
read / write
read / write
read / write
read / write
StoreStoreFence + LoadStoreFence
read-acquire
write-release
2
1
2
1
166
LoadLoadFence + LoadStoreFence
Захват и освобождение - в терминах барьеров
read / write
read / write
read / write
read / write
read / write
read / write
read / write
read / write
LoadStoreFence + StoreStoreFence
read-acquire
write-release 167
Поток 2
ready.load(
memory_order_acquire);
print(x);
Поток 1
x = 42
ready.store(true,
memory_order_release);
Семантика захват-освобождение на примере с публикацией
int x;
bool ready = false;
168
Поток 1
x = 42
ready.store(true,
memory_order_release);
Семантика захват-освобождение на примере с публикацией
Поток 2
ready.load(
memory_order_acquire);
print(x);
int x;
bool ready = false;
Синхронизируется-с
Synchronizes-with
Всё, что
здесь...
… будет
видно здесь
169
Поток 1
x = 42
ready.store(true,
memory_order_release);
Семантика захват-освобождение на примере с публикацией
Поток 2
ready.load(
memory_order_acquire);
print(x);
int x;
bool ready = false;
Синхронизируется-с
Synchronizes-with
… будет
видно здесь
Всё, что
здесь...
Операция записи-
освобождения
Операция чтения-
захвата
170
Семантика захват-освобождение на примере с публикацией
Поток 1
void initWidget(x, y, z) {
w.x = x;
w.y = x;
w.z = z;
ready.store(true,
memory_order_release);
}
Поток 2
void useWidget() {
while (!ready.load(
memory_order_acquire))
{}
doSomething(w);
}
widget w;
bool ready = false;
171
Поток 2
void useWidget() {
while (!ready.load(
memory_order_acquire))
{}
doSomething(w);
}
Семантика захват-освобождение на примере с публикацией
Поток 1
void initWidget(x, y, z) {
w.x = x;
w.y = x;
w.z = z;
ready.store(true,
memory_order_release);
}
widget w;
bool ready = false;
Синхронизируется-с
Synchronizes-with
Всё, что
здесь...
… будет
видно
здесь
Операция записи-
освобождения
Операция
чтения-захвата
172
Поток 2
void useWidget() {
while (!ready.load(
memory_order_acquire))
{}
s1 = to_string(w);
s2 = "number " + s1;
print(s2);
}
Семантика захват-освобождение на примере с публикацией
int x, y, z;
bool ready = false;
Поток 1
void initWidget(x, y, z) {
x = 10
y = x + 20;
z = x + 12;
ready.store(true,
memory_order_release);
}
Синхронизируется-с
Synchronizes-with
173
Поток 2
void useWidget() {
while (!ready.load(
memory_order_acquire))
{}
s1 = to_string(w);
s2 = "number " + s1;
print(s2);
}
Семантика захват-освобождение на примере с публикацией
int x, y, z;
bool ready = false;
Поток 1
void initWidget(x, y, z) {
x = 10
y = x + 20;
z = x + 12;
ready.store(true,
memory_order_release);
}
Синхронизируется-с
Synchronizes-with
Операция записи-
освобождения
Операция
чтения-захвата
174
Захват и освобождение в С++
std::memory_order_acquire
▪ Применяется к операциям чтения или чтения-
модификации-записи: load, compare_exchange,
fetch_add, fetch_or, etc.
▫ x.load(std::memory_order_acquire);
▫ compare_exchange_weak(x, y,
std::memory_order_acquire);
▪ Применяется к операциям записи или чтения-
модификации-записи: store, operator=, load,
compare_exchange, fetch_add, fetch_or, etc
▫ x.store(42, std::memory_order_release);
▫ compare_exchange_weak(x, y,
std::memory_order_release);
std::memory_order_release 175
Отношение
синхронизируется-с
(synchronized-with)
176
Отношение synchronized-with (синхронизируется-с)
Отношение syncrhonized-with более сильное по сравнению с happens-
before, т.е.:
synchronizes-with ⟶ happens-before
177
Отношение synchronized-with (синхронизируется-с)
Отношение syncrhonized-with более сильное по сравнению с happens-
before, т.е.:
synchronizes-with ⟶ happens-before
Операция записи A над переменной x синхронизируется-с такой
операцией операцией чтения B над x, которая читает значение,
сохранённое
1. или операцией A.
2. или следующей за A операцией записи над x в том же потоке,
который выполнил A.
3. или последовательностью операций чтения-модификации-записи
над x в любом потоке, при условии, что значение, прочитанное
первым потоком в этой последовательности, является значением,
записанным операцией A.
178
Отношение synchronized-with (синхронизируется-с)
Отношение syncrhonized-with более сильное по сравнению с happens-
before, т.е.:
synchronizes-with ⟶ happens-before
Операция записи A над переменной x синхронизируется-с такой
операцией операцией чтения B над x, которая читает значение,
сохранённое
1. или операцией A.
2. или следующей за A операцией записи над x в том же потоке,
который выполнил A.
3. или последовательностью операций чтения-модификации-записи
над x в любом потоке, при условии, что значение, прочитанное
первым потоком в этой последовательности, является значением,
записанным операцией A.
179
Отношение synchronized-with (синхронизируется-с)
Отношение syncrhonized-with более сильное по сравнению с happens-
before, т.е.:
synchronizes-with ⟶ happens-before
Операция записи A над переменной x синхронизируется-с такой
операцией операцией чтения B над x, которая читает значение,
сохранённое
1. или операцией A.
2. или следующей за A операцией записи над x в том же потоке,
который выполнил A.
3. или последовательностью операций чтения-модификации-записи
над x в любом потоке, при условии, что значение, прочитанное
первым потоком в этой последовательности, является значением,
записанным операцией A.
Иначе: Если поток 1 сохраняет значение, а поток 2 читает это
значение, то существует отношение синхронизируется-с между
операциями сохранения и загрузки. 180
Поток 1
void prepare() {
w.x = x;
w.y = x;
cntr++;
w.ready.store(true,
memory_order_release);
}
Операци Acquire-release (synchronized-with) в С++
Поток 2
void utilize() {
while (!w.ready.load(
memory_order_acquire))
{}
doSomethingWith(w);
writeLog(cntr);
}
Widget w;
int cntr;
Синхронизируется-с
Synchronizes-with
181
Поток 1
void prepare(Widget &w) {
w.x = x;
w.y = x;
cntr++;
w.ready.store(true,
memory_order_release);
}
Операци Acquire-release (synchronized-with) в С++
Поток 2
void utilize() {
if (!w.ready.load(
memory_order_acquire)){
doSomethingWith(w);
writeLog(cntr);
}
Widget w;
int cntr;
Синхронизируется-с
Synchronizes-with
182
ПВТ - весна 2015 - Лекция 7. Модель памяти С++. Внеочередное выполнение инструкций. Барьеры памяти. Атомарные операции
ПВТ - весна 2015 - Лекция 7. Модель памяти С++. Внеочередное выполнение инструкций. Барьеры памяти. Атомарные операции
ПВТ - весна 2015 - Лекция 7. Модель памяти С++. Внеочередное выполнение инструкций. Барьеры памяти. Атомарные операции
ПВТ - весна 2015 - Лекция 7. Модель памяти С++. Внеочередное выполнение инструкций. Барьеры памяти. Атомарные операции
ПВТ - весна 2015 - Лекция 7. Модель памяти С++. Внеочередное выполнение инструкций. Барьеры памяти. Атомарные операции
ПВТ - весна 2015 - Лекция 7. Модель памяти С++. Внеочередное выполнение инструкций. Барьеры памяти. Атомарные операции
ПВТ - весна 2015 - Лекция 7. Модель памяти С++. Внеочередное выполнение инструкций. Барьеры памяти. Атомарные операции
ПВТ - весна 2015 - Лекция 7. Модель памяти С++. Внеочередное выполнение инструкций. Барьеры памяти. Атомарные операции
ПВТ - весна 2015 - Лекция 7. Модель памяти С++. Внеочередное выполнение инструкций. Барьеры памяти. Атомарные операции
ПВТ - весна 2015 - Лекция 7. Модель памяти С++. Внеочередное выполнение инструкций. Барьеры памяти. Атомарные операции
ПВТ - весна 2015 - Лекция 7. Модель памяти С++. Внеочередное выполнение инструкций. Барьеры памяти. Атомарные операции
ПВТ - весна 2015 - Лекция 7. Модель памяти С++. Внеочередное выполнение инструкций. Барьеры памяти. Атомарные операции
ПВТ - весна 2015 - Лекция 7. Модель памяти С++. Внеочередное выполнение инструкций. Барьеры памяти. Атомарные операции
ПВТ - весна 2015 - Лекция 7. Модель памяти С++. Внеочередное выполнение инструкций. Барьеры памяти. Атомарные операции
ПВТ - весна 2015 - Лекция 7. Модель памяти С++. Внеочередное выполнение инструкций. Барьеры памяти. Атомарные операции
ПВТ - весна 2015 - Лекция 7. Модель памяти С++. Внеочередное выполнение инструкций. Барьеры памяти. Атомарные операции
ПВТ - весна 2015 - Лекция 7. Модель памяти С++. Внеочередное выполнение инструкций. Барьеры памяти. Атомарные операции
ПВТ - весна 2015 - Лекция 7. Модель памяти С++. Внеочередное выполнение инструкций. Барьеры памяти. Атомарные операции
ПВТ - весна 2015 - Лекция 7. Модель памяти С++. Внеочередное выполнение инструкций. Барьеры памяти. Атомарные операции
ПВТ - весна 2015 - Лекция 7. Модель памяти С++. Внеочередное выполнение инструкций. Барьеры памяти. Атомарные операции
ПВТ - весна 2015 - Лекция 7. Модель памяти С++. Внеочередное выполнение инструкций. Барьеры памяти. Атомарные операции
ПВТ - весна 2015 - Лекция 7. Модель памяти С++. Внеочередное выполнение инструкций. Барьеры памяти. Атомарные операции
ПВТ - весна 2015 - Лекция 7. Модель памяти С++. Внеочередное выполнение инструкций. Барьеры памяти. Атомарные операции
ПВТ - весна 2015 - Лекция 7. Модель памяти С++. Внеочередное выполнение инструкций. Барьеры памяти. Атомарные операции
ПВТ - весна 2015 - Лекция 7. Модель памяти С++. Внеочередное выполнение инструкций. Барьеры памяти. Атомарные операции
ПВТ - весна 2015 - Лекция 7. Модель памяти С++. Внеочередное выполнение инструкций. Барьеры памяти. Атомарные операции
ПВТ - весна 2015 - Лекция 7. Модель памяти С++. Внеочередное выполнение инструкций. Барьеры памяти. Атомарные операции
ПВТ - весна 2015 - Лекция 7. Модель памяти С++. Внеочередное выполнение инструкций. Барьеры памяти. Атомарные операции
ПВТ - весна 2015 - Лекция 7. Модель памяти С++. Внеочередное выполнение инструкций. Барьеры памяти. Атомарные операции
ПВТ - весна 2015 - Лекция 7. Модель памяти С++. Внеочередное выполнение инструкций. Барьеры памяти. Атомарные операции
ПВТ - весна 2015 - Лекция 7. Модель памяти С++. Внеочередное выполнение инструкций. Барьеры памяти. Атомарные операции
ПВТ - весна 2015 - Лекция 7. Модель памяти С++. Внеочередное выполнение инструкций. Барьеры памяти. Атомарные операции
ПВТ - весна 2015 - Лекция 7. Модель памяти С++. Внеочередное выполнение инструкций. Барьеры памяти. Атомарные операции
ПВТ - весна 2015 - Лекция 7. Модель памяти С++. Внеочередное выполнение инструкций. Барьеры памяти. Атомарные операции
ПВТ - весна 2015 - Лекция 7. Модель памяти С++. Внеочередное выполнение инструкций. Барьеры памяти. Атомарные операции
ПВТ - весна 2015 - Лекция 7. Модель памяти С++. Внеочередное выполнение инструкций. Барьеры памяти. Атомарные операции
ПВТ - весна 2015 - Лекция 7. Модель памяти С++. Внеочередное выполнение инструкций. Барьеры памяти. Атомарные операции
ПВТ - весна 2015 - Лекция 7. Модель памяти С++. Внеочередное выполнение инструкций. Барьеры памяти. Атомарные операции
ПВТ - весна 2015 - Лекция 7. Модель памяти С++. Внеочередное выполнение инструкций. Барьеры памяти. Атомарные операции
ПВТ - весна 2015 - Лекция 7. Модель памяти С++. Внеочередное выполнение инструкций. Барьеры памяти. Атомарные операции
ПВТ - весна 2015 - Лекция 7. Модель памяти С++. Внеочередное выполнение инструкций. Барьеры памяти. Атомарные операции
ПВТ - весна 2015 - Лекция 7. Модель памяти С++. Внеочередное выполнение инструкций. Барьеры памяти. Атомарные операции
ПВТ - весна 2015 - Лекция 7. Модель памяти С++. Внеочередное выполнение инструкций. Барьеры памяти. Атомарные операции
ПВТ - весна 2015 - Лекция 7. Модель памяти С++. Внеочередное выполнение инструкций. Барьеры памяти. Атомарные операции

More Related Content

What's hot

Использование юнит-тестов для повышения качества разработки
Использование юнит-тестов для повышения качества разработкиИспользование юнит-тестов для повышения качества разработки
Использование юнит-тестов для повышения качества разработки
victor-yastrebov
 
Лекция 7: Многопоточное программирование: часть 3 (OpenMP)
Лекция 7: Многопоточное программирование: часть 3 (OpenMP)Лекция 7: Многопоточное программирование: часть 3 (OpenMP)
Лекция 7: Многопоточное программирование: часть 3 (OpenMP)
Mikhail Kurnosov
 
2012 03 14_parallel_programming_lecture05
2012 03 14_parallel_programming_lecture052012 03 14_parallel_programming_lecture05
2012 03 14_parallel_programming_lecture05
Computer Science Club
 

What's hot (20)

ПВТ - весна 2015 - Лекция 4. Шаблоны многопоточного программирования
ПВТ - весна 2015 - Лекция 4. Шаблоны многопоточного программированияПВТ - весна 2015 - Лекция 4. Шаблоны многопоточного программирования
ПВТ - весна 2015 - Лекция 4. Шаблоны многопоточного программирования
 
ПВТ - весна 2015 - Лекция 0. Описание курса
ПВТ - весна 2015 - Лекция 0. Описание курсаПВТ - весна 2015 - Лекция 0. Описание курса
ПВТ - весна 2015 - Лекция 0. Описание курса
 
Parallel STL
Parallel STLParallel STL
Parallel STL
 
ПВТ - весна 2015 - Лекция 5. Многопоточное программирование в С++. Синхрониза...
ПВТ - весна 2015 - Лекция 5. Многопоточное программирование в С++. Синхрониза...ПВТ - весна 2015 - Лекция 5. Многопоточное программирование в С++. Синхрониза...
ПВТ - весна 2015 - Лекция 5. Многопоточное программирование в С++. Синхрониза...
 
Использование шаблонов и RTTI для конфигурации симулятора флеш-накопителя - Г...
Использование шаблонов и RTTI для конфигурации симулятора флеш-накопителя - Г...Использование шаблонов и RTTI для конфигурации симулятора флеш-накопителя - Г...
Использование шаблонов и RTTI для конфигурации симулятора флеш-накопителя - Г...
 
Некоторые паттерны реализации полиморфного поведения в C++ – Дмитрий Леванов,...
Некоторые паттерны реализации полиморфного поведения в C++ – Дмитрий Леванов,...Некоторые паттерны реализации полиморфного поведения в C++ – Дмитрий Леванов,...
Некоторые паттерны реализации полиморфного поведения в C++ – Дмитрий Леванов,...
 
ПВТ - осень 2014 - Лекция 5 - Многопоточное программирование в языке С++. Р...
ПВТ - осень 2014 - Лекция 5 - Многопоточное программирование в языке С++.   Р...ПВТ - осень 2014 - Лекция 5 - Многопоточное программирование в языке С++.   Р...
ПВТ - осень 2014 - Лекция 5 - Многопоточное программирование в языке С++. Р...
 
Дмитрий Кашицын, Троллейбус из буханки: алиасинг и векторизация в LLVM
Дмитрий Кашицын, Троллейбус из буханки: алиасинг и векторизация в LLVMДмитрий Кашицын, Троллейбус из буханки: алиасинг и векторизация в LLVM
Дмитрий Кашицын, Троллейбус из буханки: алиасинг и векторизация в LLVM
 
ПВТ - осень 2014 - Лекция 4 - Стандарт POSIX Threads. Реентерабельность. Сигн...
ПВТ - осень 2014 - Лекция 4 - Стандарт POSIX Threads. Реентерабельность. Сигн...ПВТ - осень 2014 - Лекция 4 - Стандарт POSIX Threads. Реентерабельность. Сигн...
ПВТ - осень 2014 - Лекция 4 - Стандарт POSIX Threads. Реентерабельность. Сигн...
 
Лекция 6. Стандарт OpenMP
Лекция 6. Стандарт OpenMPЛекция 6. Стандарт OpenMP
Лекция 6. Стандарт OpenMP
 
Лекция 1. Основные понятия стандарта MPI. Дифференцированные обмены
Лекция 1. Основные понятия стандарта MPI. Дифференцированные обменыЛекция 1. Основные понятия стандарта MPI. Дифференцированные обмены
Лекция 1. Основные понятия стандарта MPI. Дифференцированные обмены
 
Использование юнит-тестов для повышения качества разработки
Использование юнит-тестов для повышения качества разработкиИспользование юнит-тестов для повышения качества разработки
Использование юнит-тестов для повышения качества разработки
 
Лекция 7: Многопоточное программирование: часть 3 (OpenMP)
Лекция 7: Многопоточное программирование: часть 3 (OpenMP)Лекция 7: Многопоточное программирование: часть 3 (OpenMP)
Лекция 7: Многопоточное программирование: часть 3 (OpenMP)
 
Лекция 2. Оптимизация ветвлений и циклов (Branch prediction and loop optimiz...
Лекция 2. Оптимизация ветвлений и циклов (Branch prediction and loop optimiz...Лекция 2. Оптимизация ветвлений и циклов (Branch prediction and loop optimiz...
Лекция 2. Оптимизация ветвлений и циклов (Branch prediction and loop optimiz...
 
Юрий Ефимочев, Компилируемые в реальном времени DSL для С++
Юрий Ефимочев, Компилируемые в реальном времени DSL для С++ Юрий Ефимочев, Компилируемые в реальном времени DSL для С++
Юрий Ефимочев, Компилируемые в реальном времени DSL для С++
 
2012 03 14_parallel_programming_lecture05
2012 03 14_parallel_programming_lecture052012 03 14_parallel_programming_lecture05
2012 03 14_parallel_programming_lecture05
 
Дракон в мешке: от LLVM к C++ и проблемам неопределенного поведения
Дракон в мешке: от LLVM к C++ и проблемам неопределенного поведенияДракон в мешке: от LLVM к C++ и проблемам неопределенного поведения
Дракон в мешке: от LLVM к C++ и проблемам неопределенного поведения
 
Лекция 8. Intel Threading Building Blocks
Лекция 8. Intel Threading Building BlocksЛекция 8. Intel Threading Building Blocks
Лекция 8. Intel Threading Building Blocks
 
Александр Тарасенко, Использование python для автоматизации отладки С/C++ код...
Александр Тарасенко, Использование python для автоматизации отладки С/C++ код...Александр Тарасенко, Использование python для автоматизации отладки С/C++ код...
Александр Тарасенко, Использование python для автоматизации отладки С/C++ код...
 
Григорий Демченко, Асинхронность и неблокирующая синхронизация
Григорий Демченко, Асинхронность и неблокирующая синхронизацияГригорий Демченко, Асинхронность и неблокирующая синхронизация
Григорий Демченко, Асинхронность и неблокирующая синхронизация
 

Similar to ПВТ - весна 2015 - Лекция 7. Модель памяти С++. Внеочередное выполнение инструкций. Барьеры памяти. Атомарные операции

Разработка высокопроизводительных серверных приложений для Linux/Unix (Алекса...
Разработка высокопроизводительных серверных приложений для Linux/Unix (Алекса...Разработка высокопроизводительных серверных приложений для Linux/Unix (Алекса...
Разработка высокопроизводительных серверных приложений для Linux/Unix (Алекса...
Ontico
 
Девять кругов ада или PostgreSQL Vacuum / Алексей Лесовский (PostgreSQL-Consu...
Девять кругов ада или PostgreSQL Vacuum / Алексей Лесовский (PostgreSQL-Consu...Девять кругов ада или PostgreSQL Vacuum / Алексей Лесовский (PostgreSQL-Consu...
Девять кругов ада или PostgreSQL Vacuum / Алексей Лесовский (PostgreSQL-Consu...
Ontico
 
Как построить высокопроизводительный Front-end сервер (Александр Крижановский)
Как построить высокопроизводительный Front-end сервер (Александр Крижановский) Как построить высокопроизводительный Front-end сервер (Александр Крижановский)
Как построить высокопроизводительный Front-end сервер (Александр Крижановский)
Ontico
 

Similar to ПВТ - весна 2015 - Лекция 7. Модель памяти С++. Внеочередное выполнение инструкций. Барьеры памяти. Атомарные операции (20)

Lab5
Lab5Lab5
Lab5
 
PostgreSQL Vacuum: Nine Circles of Hell
PostgreSQL Vacuum: Nine Circles of HellPostgreSQL Vacuum: Nine Circles of Hell
PostgreSQL Vacuum: Nine Circles of Hell
 
Разработка высокопроизводительных серверных приложений для Linux/Unix (Алекса...
Разработка высокопроизводительных серверных приложений для Linux/Unix (Алекса...Разработка высокопроизводительных серверных приложений для Linux/Unix (Алекса...
Разработка высокопроизводительных серверных приложений для Linux/Unix (Алекса...
 
Что особенного в СУБД для данных в оперативной памяти / Константин Осипов (Ta...
Что особенного в СУБД для данных в оперативной памяти / Константин Осипов (Ta...Что особенного в СУБД для данных в оперативной памяти / Константин Осипов (Ta...
Что особенного в СУБД для данных в оперативной памяти / Константин Осипов (Ta...
 
Девять кругов ада или PostgreSQL Vacuum / Алексей Лесовский (PostgreSQL-Consu...
Девять кругов ада или PostgreSQL Vacuum / Алексей Лесовский (PostgreSQL-Consu...Девять кругов ада или PostgreSQL Vacuum / Алексей Лесовский (PostgreSQL-Consu...
Девять кругов ада или PostgreSQL Vacuum / Алексей Лесовский (PostgreSQL-Consu...
 
Call of Postgres: Advanced Operations (part 3)
Call of Postgres: Advanced Operations (part 3)Call of Postgres: Advanced Operations (part 3)
Call of Postgres: Advanced Operations (part 3)
 
Оптимизация программ для современных процессоров и Linux, Александр Крижановс...
Оптимизация программ для современных процессоров и Linux, Александр Крижановс...Оптимизация программ для современных процессоров и Linux, Александр Крижановс...
Оптимизация программ для современных процессоров и Linux, Александр Крижановс...
 
Multimaster2
Multimaster2Multimaster2
Multimaster2
 
"Мультимастер для PostgreSQL" Кельвич Станислав, Книжник Константин, PostgresPro
"Мультимастер для PostgreSQL" Кельвич Станислав, Книжник Константин, PostgresPro"Мультимастер для PostgreSQL" Кельвич Станислав, Книжник Константин, PostgresPro
"Мультимастер для PostgreSQL" Кельвич Станислав, Книжник Константин, PostgresPro
 
Как построить высокопроизводительный Front-end сервер (Александр Крижановский)
Как построить высокопроизводительный Front-end сервер (Александр Крижановский) Как построить высокопроизводительный Front-end сервер (Александр Крижановский)
Как построить высокопроизводительный Front-end сервер (Александр Крижановский)
 
Atomics, CAS and Nonblocking algorithms
Atomics, CAS and Nonblocking algorithmsAtomics, CAS and Nonblocking algorithms
Atomics, CAS and Nonblocking algorithms
 
Nonblocking algorithms/CAS/Atomics by Alexey Fyodorov
Nonblocking algorithms/CAS/Atomics by Alexey FyodorovNonblocking algorithms/CAS/Atomics by Alexey Fyodorov
Nonblocking algorithms/CAS/Atomics by Alexey Fyodorov
 
Семинар 5. Многопоточное программирование на OpenMP (часть 5)
Семинар 5. Многопоточное программирование на OpenMP (часть 5)Семинар 5. Многопоточное программирование на OpenMP (часть 5)
Семинар 5. Многопоточное программирование на OpenMP (часть 5)
 
Поговорим про память
Поговорим про памятьПоговорим про память
Поговорим про память
 
Константин Осипов
Константин ОсиповКонстантин Осипов
Константин Осипов
 
Антон Потапов — С++ контейнеры и многопоточность: вместе или врозь?
Антон Потапов — С++ контейнеры и многопоточность: вместе или врозь?Антон Потапов — С++ контейнеры и многопоточность: вместе или врозь?
Антон Потапов — С++ контейнеры и многопоточность: вместе или врозь?
 
Низкоуровневые оптимизации. Андрей Аксенов. Unigine Open Air 2013
Низкоуровневые оптимизации. Андрей Аксенов. Unigine Open Air 2013Низкоуровневые оптимизации. Андрей Аксенов. Unigine Open Air 2013
Низкоуровневые оптимизации. Андрей Аксенов. Unigine Open Air 2013
 
Лекция 6. Стандарт OpenMP
Лекция 6. Стандарт OpenMPЛекция 6. Стандарт OpenMP
Лекция 6. Стандарт OpenMP
 
Алексей Федоров
Алексей ФедоровАлексей Федоров
Алексей Федоров
 
Anton Tsitou "Cycle ORM and Graphs"
Anton Tsitou "Cycle ORM and Graphs"Anton Tsitou "Cycle ORM and Graphs"
Anton Tsitou "Cycle ORM and Graphs"
 

More from Alexey Paznikov

More from Alexey Paznikov (17)

Лекция 6. Параллельная сортировка. Алгоритмы комбинаторного поиска. Параллель...
Лекция 6. Параллельная сортировка. Алгоритмы комбинаторного поиска. Параллель...Лекция 6. Параллельная сортировка. Алгоритмы комбинаторного поиска. Параллель...
Лекция 6. Параллельная сортировка. Алгоритмы комбинаторного поиска. Параллель...
 
Лекция 5. Метод конечных разностей (параллельные алгоритмы в стандарте MPI)
Лекция 5. Метод конечных разностей (параллельные алгоритмы в стандарте MPI)Лекция 5. Метод конечных разностей (параллельные алгоритмы в стандарте MPI)
Лекция 5. Метод конечных разностей (параллельные алгоритмы в стандарте MPI)
 
Лекция 4. Производные типы данных в стандарте MPI
Лекция 4. Производные типы данных в стандарте MPIЛекция 4. Производные типы данных в стандарте MPI
Лекция 4. Производные типы данных в стандарте MPI
 
Лекция 3. Виртуальные топологии в MPI. Параллельные алгоритмы в стандарте MPI...
Лекция 3. Виртуальные топологии в MPI. Параллельные алгоритмы в стандарте MPI...Лекция 3. Виртуальные топологии в MPI. Параллельные алгоритмы в стандарте MPI...
Лекция 3. Виртуальные топологии в MPI. Параллельные алгоритмы в стандарте MPI...
 
Лекция 2. Коллективные операции в MPI. Параллельные алгоритмы случайного блуж...
Лекция 2. Коллективные операции в MPI. Параллельные алгоритмы случайного блуж...Лекция 2. Коллективные операции в MPI. Параллельные алгоритмы случайного блуж...
Лекция 2. Коллективные операции в MPI. Параллельные алгоритмы случайного блуж...
 
ПВТ - весна 2015 - Лекция 1. Актуальность параллельных вычислений. Анализ пар...
ПВТ - весна 2015 - Лекция 1. Актуальность параллельных вычислений. Анализ пар...ПВТ - весна 2015 - Лекция 1. Актуальность параллельных вычислений. Анализ пар...
ПВТ - весна 2015 - Лекция 1. Актуальность параллельных вычислений. Анализ пар...
 
ПВТ - осень 2014 - Лекция 3 - Стандарт POSIX Threads
ПВТ - осень 2014 - Лекция 3 - Стандарт POSIX ThreadsПВТ - осень 2014 - Лекция 3 - Стандарт POSIX Threads
ПВТ - осень 2014 - Лекция 3 - Стандарт POSIX Threads
 
ПВТ - осень 2014 - Лекция 2 - Архитектура вычислительных систем с общей памятью
ПВТ - осень 2014 - Лекция 2 - Архитектура вычислительных систем с общей памятьюПВТ - осень 2014 - Лекция 2 - Архитектура вычислительных систем с общей памятью
ПВТ - осень 2014 - Лекция 2 - Архитектура вычислительных систем с общей памятью
 
ПВТ - осень 2014 - лекция 1 - Введение в параллельные вычисления
ПВТ - осень 2014 - лекция 1 - Введение в параллельные вычисленияПВТ - осень 2014 - лекция 1 - Введение в параллельные вычисления
ПВТ - осень 2014 - лекция 1 - Введение в параллельные вычисления
 
ПВТ - осень 2014 - лекция 1а - Описание курса
ПВТ - осень 2014 - лекция 1а - Описание курсаПВТ - осень 2014 - лекция 1а - Описание курса
ПВТ - осень 2014 - лекция 1а - Описание курса
 
Анализ эффективности выполнения алгоритма параллельной редукции в языке Cray ...
Анализ эффективности выполнения алгоритма параллельной редукции в языке Cray ...Анализ эффективности выполнения алгоритма параллельной редукции в языке Cray ...
Анализ эффективности выполнения алгоритма параллельной редукции в языке Cray ...
 
ТФРВС - весна 2014 - лекция 11
ТФРВС - весна 2014 - лекция 11ТФРВС - весна 2014 - лекция 11
ТФРВС - весна 2014 - лекция 11
 
ТФРВС - весна 2014 - лекция 10
ТФРВС - весна 2014 - лекция 10ТФРВС - весна 2014 - лекция 10
ТФРВС - весна 2014 - лекция 10
 
ТФРВС - весна 2014 - лекция 9
 ТФРВС - весна 2014 - лекция 9 ТФРВС - весна 2014 - лекция 9
ТФРВС - весна 2014 - лекция 9
 
ТФРВС - весна 2014 - лекция 8
ТФРВС - весна 2014 - лекция 8ТФРВС - весна 2014 - лекция 8
ТФРВС - весна 2014 - лекция 8
 
ТФРВС - весна 2014 - лекция 7
ТФРВС - весна 2014 - лекция 7ТФРВС - весна 2014 - лекция 7
ТФРВС - весна 2014 - лекция 7
 
ТФРВС - весна 2014 - лекция 6
ТФРВС - весна 2014 - лекция 6ТФРВС - весна 2014 - лекция 6
ТФРВС - весна 2014 - лекция 6
 

ПВТ - весна 2015 - Лекция 7. Модель памяти С++. Внеочередное выполнение инструкций. Барьеры памяти. Атомарные операции

  • 1. Лекция 7. Атомарные операции. Внеочередное выполнение инструкций. Барьеры памяти. Семантика захвата- освобождения. Модель памяти C++ Пазников Алексей Александрович Кафедра вычислительных систем СибГУТИ Сайт курса: http://cpct.sibsutis.ru/~apaznikov/teaching/ Вопросы: https://piazza.com/sibsutis.ru/fall2014/pct14/home Параллельные вычислительные технологии Осень 2014 (Parallel Computing Technologies, PCT 14)
  • 3. Атомарные операции ▪ Операция над разделяемой переменной атомарная, если она выполняется потоком за один неделимый шаг. Ни один из других потоков не может обнаружить эту переменную в промежуточном состоянии. ▪ Если операции, которые совершают потоки над раздялемыми переменными, не атомарны, то это приведёт к гонкам данных. ▪ Гонки данных являются причиной неопредлённого поведения, поскольку они приводят к частичным (фрагментированным, “разорванным”) чтениям и записям переменных. 3
  • 4. Фрагментированные чтения и записи uint64_t shared; int main() { shared = 0x100000002; ... 4
  • 5. Фрагментированные чтения и записи mov DWORD PTR shared, 2 mov DWORD PTR shared+4, 1 uint64_t shared; int main() { shared = 0x100000002; ... gcc -m32 -S -masm=intel -O1 prog.c запись младших 32 бит запись старших 32 бит ▪ Выполнение присваивания 64-битного целого shared = 42 на 32-разрядной архитектуре выполняется за 2 инструкции. ▪ Операция записи не атомарная. 5
  • 6. Фрагментированные чтения и записи mov DWORD PTR shared, 2 mov DWORD PTR shared+4, 1 uint64_t shared; int main() { shared = 0x100000002; ... gcc -m32 -S -masm=intel -O1 prog.c 1 поток запись старших 32 бит ▪ Вытеснение потока после записи младших бит приведёт к тому, что эти биты останутся в памяти и будут использованы другими потоками. ▪ На многоядерных системах даже не требуется вытеснения. 6
  • 7. Фрагментированные чтения и записи mov DWORD PTR shared, 2 mov DWORD PTR shared+4, 1 uint64_t shared; int main() { shared = 0x100000002; ... gcc -m32 -S -masm=intel -O1 prog.c 1 поток 2 поток ▪ Вытеснение потока после записи младших бит приведёт к тому, что эти биты останутся в памяти, а старшие будут записаны другим потоком. ▪ На многоядерных системах даже не требуется вытеснения. 7
  • 8. Фрагментированные чтения и записи mov eax, DWORD PTR shared mov edx, DWORD PTR shared+4 ret uint64_t shared; uint64_t getShared() { return shared; } gcc -m32 -S -masm=intel -O1 prog.c чтение младших 32 бит чтение старших 32 бит ▪ Выполнение чтения 64-битного целого shared на 32- разрядной архитектуре выполняется за 2 инструкции. ▪ Операция чтения не атомарная. 8
  • 9. Фрагментированные чтения и записи strd r0, r1, [r2] ▪ Инструкции могут быть неатомарными, даже если выполняются одной процессорной инструкцией. Например, в ARMv7 инструкция для помещения содержимого двух 32-битных регистров в один 64-битный: На некоторых процессорах эта инструкция реализуются двумя отдельными операциями сохранения. 32-битная операция mov атомарна только для выравненных данных. В остальных случаях операция неатомарная ▪ Вытеснение потоков, выполняющих данные операции, или выполнение операций в двугих потоках в многоядерных системах приводит к неопределённому поведению. 9
  • 10. Атомарность в С и С++ ▪ В языках С и С++ предполагается, что все операции неатомарны. ▪ Операции могут быть атомарными в большинстве случаев, например, операция присваивания 32-битного целого значения. ▪ Тем не менее, все инструкции должны рассматриваться как неатомарные. ▪ К счастью, в С и С++ есть набор шаблонов атомарных типов данных. 10
  • 11. Атомарность в С и С++ ▪ Атомарные операции в С++ неделимы. Из любого потока нельзя обнаружить эту операцию выполненной частично - она либо выполнена, либо невыполнена. ▪ Это позволяет избежать гонок данных. ▪ Для атомарных типов определён метод is_lock_free, позволяющий определить, являются ли операции над ним напрямую с помощью атомарных инструкций, или они эмулируются. 11
  • 12. Атомарные типы в С и С++ ▪ std::atomic_flag - единственный тип, который не имеет функции is_lock_free. Он предельно простой, всецело атомарный и поддерживает одну операцию: test_and_set - проверить и установить. ▪ Остальные типы определяются специализацией шаблона std::atomic<>, например std::atomic<int> и std:: atomic<void*> 12
  • 13. Атомарные типы в С и С++ Атомарные тип Соответствующая специализация std::atomic_bool std::atomic<bool> std::atomic_char std::atomic<char> std::atomic_schar std::atomic<signed char> std::atomic_uchar std::atomic<unsigned char> std::atomic_short std::atomic<short> std::atomic_ushort std::atomic<unsigned short> std::atomic_int std::atomic<int> std::atomic_uint std::atomic<unsigned int> std::atomic_long std::atomic<long> ... + пользовательские типы 13
  • 14. Операции над атомарными типами ▪ Операции сохранения: store, clear, etc. Упорядочение memory_order_relaxed, memory_order_release, memory_order_seq_cst. ▪ Операции загрузки: load, etc. Упорядочение memory_order_relaxed, memory_order_consume, memory_order_acquire, memory_order_seq_cst. ▪ Операции чтения-модификации-записи: compare_exchange, fetch_add, test_and_set, etc. Упорядочение memory_order_relaxed, memory_order_consume, memory_order_acquire, memory_order_release, memory_order_seq_cst, memory_order_acq_rel. 14
  • 15. Атомарный флаг std::atomic_flag std::atomic_flag должен быть проиниализирован: std::atomic_flag flag = ATOMIC_FLAG_INIT Очистить флаг (операция сохранения): установить значение false: flag.clear(std::memory_order_release); Установить значение флага в true и вернуть предыдущее значение: bool x = flag.test_and_set(); Для атомарного флага запрещены операции копирования и присваивания. 15
  • 16. Реализация спинлока на основе атомарного флага class spinlock_mutex { std::atomic_flag flag; public: spinlock_mutex(): flag{ATOMIC_FLAG_INIT} { } void lock() { while (flag.test_and_set( std::memory_order_acquire)); } void unlock() { flag.clear(std::memory_order_release); } }; n.b. spinlock_mutex можно использовать с lock_guard и unique_guard! 16 Записать в flag 1, вернуть то, что было Записать 0 в flag
  • 17. Операции сохранения и загрузки атомарных типов // Объявить переменную и проициализировать true std::atomic<bool> b(true); // Загрузить значение в переменной в неатомарную // переменную x bool x = b.load(std::memory_order_acquire); // Записать в переменную b значение true b.store(true); // Обменять значение переменной b со значением false, // вернуть предыдущее значение b в переменную x. x = b.exchange(false, std::memory_order_acq_rel); 17
  • 18. Операция “сравнить и обменять” Операция compare_exchange (“сравнить и обменять”): 1. Сравнить текущее значение атомарной переменной с ожидаемым expected. 2. Если значения совпали, сохранить новое значение desired и вернуть true. 3. Если значения не совпадают, то ожидаемое значение expected заменяется фактическим значением переменной, функция возвращает false. bool compare_exchange_weak(T& expected, T desired, std::memory_order order = std::memory_order_seq_cst); bool compare_exchange_strong(T& expected, T desired, std::memory_order order = std::memory_order_seq_cst); 18
  • 19. Операция “сравнить и обменять” compare_exchange_weak() - сохранение может не произойти, даже если текущее значение совпадает с ожидаемым. Значение переменной не изменится, функция возвращает false. Последнее возможно при отсутствии аппаратной поддержки команды сравнить-и-обменять, из-за того, что поток может быть вытеснен в середине требуемой последовательности команд (ложный отказ). Из-за возможного ложного отказа функцию compare_exchange_weak() обычно вызывают в цикле: bool expected = false; extern atomic<bool> b; ... while (!b.compare_exchange_weak(expected, true); 19
  • 20. Операция “сравнить и обменять” compare_exchange_strong() гарантирует замену переменной в случае выполнения условия. ▪ compare_exchange_strong() выгодно использовать в случае однократного выполнения операции, т.е. при необходимости заменить значение на жалаемое, и в случае, если вычисление нового значения занимает длительное время. ▪ Если функция compare_exchange вызывается в цикле, тогда предпочтительнее использовать compare_exchange_weak, чтобы избежать двойного цикла (compare_exchange_strong реализован в виде цикла на системах, которые не поддерживают атомарной операции сравнения и замены). 20
  • 21. Атомарный тип atomic<T*> Функции для типа atomic<T*>: ▪ is_lock_free, load, store, exchange, compare_exchange_weak, compare_exchange_strong ▪ обменять и прибавить: fetch_add, operator++, operator+= ▪ обменять и вычесть: fetch_sub, operator--, operator-= class C {}; C arr[10]; std::atomic<C*> ptr(arr); C* x = ptr.fetch_add(2); assert(x == arr); assert(p.load() == &some_array[2]); p--; x = p; assert(x == &arr[1]); операции чтения- модификации- записи 21
  • 22. Стандартные атомарные целочисленные типы Функции для типов стандартных атомарных целочисленных типов (int, unsigned int, long, unsigned long, etc): ▪ is_lock_free, load, store, exchange, compare_exchange_weak, compare_exchange_strong ▪ обменять и прибавить (fetch_add, operator++, operator+=) обменять и вычесть (fetch_sub, operator--, operator-=) ▪ operator&=, operator|=, operator^= Отсутствуют операции: ▫ умножения, деления, сдвига 22
  • 23. Пользовательские атомарные типы В качестве шаблона std::atomic<> может выступать тип, он должен удовлетворять требованиям ▪ В нём должен присутствовать тривиальный оператор присваивания. Нет виртуальных функций и виртуальных базовых классов, а оператор присваивания генерируется автоматически (например, memcpy) ▪ Тип должен допускать побитовое сравнение на равенство (например, с помощью memcmp) 23
  • 25. О дивный новый и прекрасный параллельный мир! Выполняет ли компьютер программу, которую вы написали? 25
  • 26. О дивный новый и прекрасный параллельный мир! Выполняет ли компьютер программу, которую вы написали? НЕТ 26
  • 27. О дивный новый и прекрасный параллельный мир! Выполняет ли компьютер программу, которую вы написали? НЕТПросто дело в том что... иерархическая структура памяти, внеочередное выполнение команд процессора и компиляторная оптимизация. 27
  • 28. Аппаратно вызванные трансформации программы кэш 2 кэш 1 Память кэш 2 кэш 1 кэш 1 кэш 1 Процессорные ядра 28
  • 29. Аппаратно вызванные трансформации программы кэш 2 кэш 1 Память кэш 2 кэш 1 кэш 1 кэш 1 Процессорные ядра Когерентность кэша (MESI, MOESI, MESIF) 29
  • 30. Аппаратно вызванные трансформации программы кэш 2 кэш 1 Память кэш 2 кэш 1 кэш 1 кэш 1 Процессорные ядра Внеочередное выполнение инструкций (out- of-order execution) Когерентность кэша (MESI, MOESI, MESIF) 30
  • 31. Аппаратно вызванные трансформации программы Intel Nehalem Core Pipeline 31
  • 33. Компиляторная оптимизация - виды оптимизаций ▪ Peephole-оптимизация ▪ Локальная оптимизация ▪ Внутрипроцедурная оптимизация ▪ Оптимизация циклов ▪ Межпроцедурная оптимизация 33
  • 34. Компиляторная оптимизация - виды оптимизаций ▪ Peephole-оптимизация ▪ Локальная оптимизация ▪ Внутрипроцедурная оптимизация ▪ Оптимизация циклов ▫ Анализ индуктивных переменных ▫ Деление цикла на части ▫ Объединение циклов ▫ Инверсия цикла ▫ Расщепление цикла ▪ Межпроцедурная оптимизация 34
  • 35. Компиляторная оптимизация - виды оптимизаций -O -fauto-inc-dec -fbranch-count-reg -fcombine-stack-adjustments -fcompare-elim -fcprop-registers -fdce -fdefer-pop -fdelayed-branch -fdse -fforward-propagate -fguess-branch-probability -fif-conversion2 -fif-conversion -finline-functions-called-once -fipa-pure-const -fipa-profile -fipa-reference -fmerge-constants -fmove-loop-invariants -fshrink-wrap -fsplit-wide-types -ftree-bit-ccp -ftree-ccp -fssa-phiopt -ftree-ch -ftree-copy-prop -ftree-copyrename -ftree-dce -ftree-dominator-opts -ftree-dse -ftree-forwprop -ftree-fre -ftree-phiprop -ftree-sink -ftree-slsr -ftree-sra -ftree-pta -ftree-ter -funit-at-a-time -O2 -fauto-inc-dec -fbranch-count-reg -fcombine-stack-adjustments -fcompare-elim -fcprop-registers -fdce -fdefer-pop -fdelayed-branch -fdse -fforward-propagate -fguess-branch-probability -fif-conversion2 -fif-conversion -finline-functions-called-once -fipa-pure-const -fipa-profile -fipa-reference -fmerge-constants -fmove-loop-invariants -fshrink-wrap -fsplit-wide-types -ftree-bit-ccp -ftree-ccp -fssa-phiopt -ftree-ch -ftree-copy-prop -ftree-copyrename -ftree-dce -ftree-dominator-opts -ftree-dse -ftree-forwprop -ftree-fre -ftree-phiprop -ftree-sink -ftree-slsr -ftree-sra -ftree-pta -ftree-ter -funit-at-a-time -fthread-jumps -falign-functions -falign- jumps -falign-loops -falign-labels -fcaller-saves -fcrossjumping -fcse-follow-jumps -fcse-skip- blocks -fdelete-null-pointer-checks -fdevirtualize -fdevirtualize- speculatively -fexpensive-optimizations -fgcse -fgcse-lm -fhoist-adjacent-loads -finline-small-functions -findirect-inlining -fipa-cp -fipa-sra -fipa-icf -fisolate-erroneous-paths- dereference -foptimize-sibling-calls -foptimize-strlen -fpartial-inlining -fpeephole2 -freorder-blocks -freorder- blocks-and-partition -freorder- functions -frerun-cse-after-loop -fsched-interblock -fsched- spec -fschedule-insns -fschedule- insns2 -fstrict-aliasing -fstrict- overflow -ftree-builtin-call-dce -ftree-switch-conversion - ftree-tail-merge -ftree-pre -ftree-vrp -fuse-caller-save -O3 -fauto-inc-dec -fbranch-count-reg -fcombine-stack-adjustments -fcompare-elim -fcprop-registers -fdce -fdefer-pop -fdelayed-branch -fdse -fforward-propagate -fguess-branch-probability -fif-conversion2 -fif-conversion -finline-functions-called-once -fipa-pure-const -fipa-profile -fipa-reference -fmerge-constants -fmove-loop-invariants -fshrink-wrap -fsplit-wide-types -ftree-bit-ccp -ftree-ccp -fssa-phiopt -ftree-ch -ftree-copy-prop -ftree-copyrename -ftree-dce -ftree-dominator-opts -ftree-dse -ftree-forwprop -ftree-fre -ftree-phiprop -ftree-sink -ftree-slsr -ftree-sra -ftree-pta -ftree-ter -funit-at-a-time -fthread-jumps -falign-functions -falign-jumps -falign-loops -falign-labels -fcaller-saves -fcrossjumping -fcse-follow-jumps -fcse-skip- blocks -fdelete-null-pointer-checks -fdevirtualize -fdevirtualize- speculatively -fexpensive-optimizations -fgcse -fgcse-lm -fhoist-adjacent-loads -finline-small-functions -findirect-inlining -fipa-cp -fipa-sra -fipa-icf -fisolate-erroneous-paths- dereference -foptimize-sibling-calls -foptimize-strlen -fpartial-inlining -fpeephole2 -freorder-blocks -freorder-blocks- and-partition -freorder-functions -frerun-cse-after-loop -fsched-interblock -fsched-spec -fschedule-insns -fschedule-insns2 -fstrict-aliasing -fstrict-overflow -ftree-builtin-call-dce -ftree-switch-conversion -ftree- tail-merge -ftree-pre -ftree-vrp -fuse-caller-save -finline-functions -funswitch-loops -fpredictive-commoning -fgcse-after-reload -ftree-loop-vectorize -ftree-loop-distribute-patterns -ftree-slp-vectorize -fvect-cost-model -ftree-partial-pre -fipa-cp-clone 35
  • 36. Компиляторная оптимизация - примеры оптимизаций x = 1; y = 2; x = 3; for (i = 0; i < n; i++) sum += a[i]; y = 2; x = 3; r1 = sum; for (i = 0; i < n; i++) r1 += a[i]; sum = r1; for (i = 0; i < n; i++) j = 42 * i; j = -42 for (i = 0; i < n; i++) j = j + 42; 36
  • 37. Компиляторная оптимизация - примеры оптимизаций x = Johann; y = Sebastian; z = Bach; for (i = 0; i < n; i++) for (j = 0; j < m;j++); a[i][j] = 1; z = Bach; x = Johann; y = Sebastian; for (j = 0; j < n; j++) for (i = 0; i < m; i++); a[i][j] = 1; for (i = 0; i < n; i++) a[i] = 1; for (i = 0; i < n; i++) b[i] = 2; for (i = 0; i < n; i++) { a[i] = 1; b[i] = 2; } 37
  • 38. Компиляторная оптимизация - примеры оптимизаций Что компилятор знает: Все операции с памятью совершаются в текущем потоке, что они в точности означают, и какие существуют зависимости по данным. Что компилятор не знает: Какие области памяти доступны и изменяются в разных потоках. Как решить: Сказать! Как-то пометить операции, которые выполняются с разделяемыми переменными. 38
  • 39. Этапы трансформации программы Исходный код Компилятор удаление подвыражений, свёртка констант, оптимизация циклов, ... Процессор предвыборка, спекулятивное выполнение инструкций, буферизация, HTM, ... Реальное выполнение программы Кэш частные и общие кэши, буферы записи, ... “Гораздо лучше выполнять другую программу - не ту, что вы написали. Вам на самом деле даже не хочется выполнять эту вашу чушь - вы хотите запускать другую программу! Всё это делается для вашего блага.” 39
  • 40. Этапы трансформации программы ▪ Как правило нельзя определить, на каком уровне произошла трансформация. ▪ Трансформации на всех уровнях эквивалентны, что позволяет рассматривать их как переупорядочивание операций загрузки (loads) и записи (stores). ▪ Необходимое условие при выполнении трансформаций - сохранение иллюзии последовательно согласованного кода. 40 Исходный код Компилятор удаление подвыражений, свёртка констант, оптимизация циклов, ... Процессор предвыборка, спекулятивное выполнение инструкций, буферизация, HTM, ... Реальное выполнение программы Кэш частные и общие кэши, буферы записи, ...
  • 41. Последовательная согласованность (sequential consistency, SC) “Результат выполнения программы такой, как если бы операции всех процессоров выполнялись последовательно и результат операции каждого отдельного процессора появлялся бы в этой последовательности в порядке, который определяется программой.” (Л. Лэмпорт, 1979) ▪ Рассмотрим многопроцессорную систему, состоящую из нескольких последовательных процессоров. ▪ Операции, выполняемые процессорами над некоторой областью памяти (страница, объект, адрес, ...), появляются в одном и том же порядке для всех процессоров, несмотря на то, что фактическая последовательность выполнения операций может быть другой. 41
  • 42. Последовательная согласованность (sequential consistency, SC) “Результат выполнения программы такой, как если бы операции всех процессоров выполнялись последовательно и результат операции каждого отдельного процессора появлялся бы в этой последовательности в порядке, который определяется программой.” (Л. Лэмпорт, 1979) ▪ Рассмотрим многопроцессорную систему, состоящую из нескольких последовательных процессоров. ▪ Операции, выполняемые процессорами над некоторой областью памяти (страница, объект, адрес, ...), появляются в одном и том же порядке для всех процессоров, несмотря на то, что фактическая последовательность выполнения операций может быть другой. И это прекрасно! 42
  • 43. Последовательная согласованность (sequential consistency, SC) “Результат выполнения программы такой, как если бы операции всех процессоров выполнялись последовательно и результат операции каждого отдельного процессора появлялся бы в этой последовательности в порядке, который определяется программой.” (Л. Лэмпорт, 1979) ▪ Рассмотрим многопроцессорную систему, состоящую из нескольких последовательных процессоров. ▪ Операции, выполняемые процессорами над некоторой областью памяти (страница, объект, адрес, ...), появляются в одном и том же порядке для всех процессоров, несмотря на то, что фактическая последовательность выполнения операций может быть другой. И это прекрасно! но...43
  • 44. Последовательная согласованность (sequential consistency, SC) … но вы этого не хотите! ▪ Скорее всего очень нерационально выполнять в точности то, что вы написали. ▪ Гораздо лучше выполнить нечто иное, которое бы работало так же, как и то, что вы написали, но выполнялось бы гораздо быстрее. Поэтому ▪ Мы (программное обеспечение ПО: компилятор и аппаратное обеспечение АО: кэш, процессор) будем это делать! ▪ А вы (программисты), в свою очередь, должны обеспечить возможность корректной трансформации и выполнения программы так, чтобы сохранялась иллюзия последовательной согласованности, включив в свою программу необходимые ограничения. 44
  • 45. Модель памяти - это договор Вы обещаете Корректно реализовать синхронизацию в вашей программе (путём добавления необходимых инструкций в программу, делающих её безопасной относительно гонок) Система обещает Обеспечить иллюзию выполнения той программы, которую вы написали. (путём компиляции и выполнения) 45
  • 46. Модель памяти - это договор Вы обещаете Корректно реализовать синхронизацию в вашей программе (путём добавления необходимых инструкций в программу, делающих её безопасной относительно гонок) Система обещает Обеспечить иллюзию выполнения той программы, которую вы написали. (путём компиляции и выполнения) Модель памяти определяет, какие действия вы должны совершить и как должна отреагировать система, чтобы обеспечить выполнение операций с памятью в необходимой последовательности. 46
  • 48. Аппаратное переупорядочивание инструкций Первое правило робототехники компиляторов и процессоров при упорядочивании доступа к памяти: Нельзя изменять поведение однопоточной программы. ▪ В однопоточных программах переупорядочивания остаются незамеченными. ▪ То же самое - при многопоточном программировании на основе мьютексов, семафоров и т.д. ▪ Но не при использовании атомарных переменных и техник программирования без блокировок. 48
  • 49. Переупорядочивание на примере алгоритма Деккера Поток 1 flag1 = 1; if (flag2 != 0) // ожидать освобождения // критической секции else // войти в критическую // секцию Алгоритм Деккера позволяет решать проблему взаимного исключения (в теории), был опубликован в 1965 г. Он не приводит к взаимным исключениям (deadlock) и свободен от голодания (starvation). Поток 2 flag2 = 1; if (flag1 != 0) // ожидать освобождения // критической секции else // войти в критическую // секцию 49
  • 50. Переупорядочивание на примере алгоритма Деккера Процессор 1 Процессор 2 flag2 = 1; if (flag1 != 0) { … } Память: flag1 = 0, flag2 = 0 flag1 = 1; if (flag2 != 0) { … } Store Buffer Store Buffer 50
  • 51. Переупорядочивание на примере алгоритма Деккера Процессор 1 Процессор 2 Память: flag1 = 0, flag2 = 0 flag1 = 1; if (flag2 != 0) { … } flag2 = 1; if (flag1 != 0) { … } Store Buffer flag1 = 1 Store Buffer Сохранение 1 в буфере 51
  • 52. Переупорядочивание на примере алгоритма Деккера Процессор 1 Процессор 2 Память: flag1 = 0, flag2 = 0 flag1 = 1; if (flag2 != 0) { … } flag2 = 1; if (flag1 != 0) { … } Store Buffer flag1 = 1 Сохранение 1 в буфере Сохранение 1 в буфере Store Buffer flag1 = 1 52
  • 53. Переупорядочивание на примере алгоритма Деккера Процессор 1 Процессор 2 Память: flag1 = 0, flag2 = 0 flag1 = 1; if (flag2 != 0) { … } flag2 = 1; if (flag1 != 0) { … } Store Buffer flag1 = 1 Сохранение 1 в буфере Сохранение 1 в буфере Store Buffer flag1 = 1 Чтение 0 для flag2 Чтение 0 для flag1 StoreLoad 53
  • 54. Переупорядочивание на примере алгоритма Деккера Процессор 1 Процессор 2 Память: flag1 = 0, flag2 = 0 flag1 = 1; if (flag2 != 0) { … } flag2 = 1; if (flag1 != 0) { … } Store Buffer flag1 = 1 Сохранение 1 в буфере Сохранение 1 в буфере Store Buffer flag1 = 1 Чтение 0 для flag2 Чтение 0 для flag1 54
  • 55. Переупорядочивание на примере алгоритма Деккера Процессор 1 Процессор 2 Память: flag1 = 1, flag2 = 1 flag1 = 1; if (flag2 != 0) { … } flag2 = 1; if (flag1 != 0) { … } Store Buffer flag1 = 1 Сохранение 1 в буфере Сохранение 1 в буфере Store Buffer flag1 = 1 Чтение 0 для flag2 Чтение 0 для flag1 Сброс буфера (flag1) Сброс буфера (flag2) 55
  • 56. Переупорядочивание в различных процессорах Тип переупоря- дочивания / архитектуры Alpha ARMv7 POWER SPARC RMO SPARC PSO SPARC TSO x86 AMD64 IA-64 Loads после loads Loads после stores Stores после stores Stores после loads АО с loads АО с stores Зависимые loads АО - атомарные операции 56
  • 57. Переупорядочивание в различных процессорах IBM Blue Gene Смартфоны DEC Alpha Xbox 360 PowerPC ARM SPARC TSO x86 / 64 более сильное упорядочивание (strong model, sequential consistency) более слабое упорядочивание (weak model, relaxed ordering) Как правило сильное упорядочивание Ослабленное упорядочивание с поддержкой зависимостей по данным Вполне ослабленное упорядочивание PowerMac 57
  • 58. Переупорядочивание в различных процессорах IBM Blue Gene Смартфоны DEC Alpha Xbox 360 PowerPC ARM SPARC TSO x86 / 64 более сильное упорядочивание (strong model, sequential consistency) более слабое упорядочивание (weak model, relaxed ordering) Как правило сильное упорядочивание Ослабленное упорядочивание с поддержкой зависимостей по данным Вполне ослабленное упорядочивание PowerMac При сильном упорядочивании (сильная модель памяти) каждая инструкция неявно реализует семантику захвата и освобождения. При слабом упорядочивании (слабая модель памяти) не накладывается никаких ограничений на порядок выполнения инструкций. 58
  • 59. Переупорядочивание в процессорах архитектуры x86 IBM Blue Gene Смартфоны DEC Alpha Xbox 360 PowerPC ARM SPARC TSO x86 / 64 более сильное упорядочивание (strong model, sequential consistency) более слабое упорядочивание (weak model, relaxed ordering) Как правило сильное упорядочивание Ослабленное упорядочивание с поддержкой зависимостей по данным Вполне ослабленное упорядочивание PowerMac 59
  • 60. Переупорядочивание в процессорах архитектуры x86 Intel Architectures Software Developer’s Manual, Vol. 3: ▪ Операции чтения не могут быть переупорядочены с другими операциями чтения ▪ Операции записи не могут быть переупорядочены с другими операциями записями ▪ Операции записи не могут быть переупорядочены с другими операциями записи, кроме следующих исключений: … ▪ Операции чтения могут быть переупорядочены с более старыми операциями записи в другие области памяти, но не с операциями записи в ту же область. ▪ Операции чтения не могут перейти раньше инструкций LFENCE, MFENCE, операции записи - раньше инструкций LFENCE, SFENCE, MFENCE. ▪ LFENCE, SFENCE, MFENCE не могут выполниться раньше операций чтения, записи, того или другого соответственно. 60
  • 61. Переупорядочивание в процессорах архитектуры x86 Процессор 1 mov x, 1 ; запись (store) ; 1 в x mov r1, y ; загрузка (load) ; из y в регистр Процессор 2 mov y, 1 ; запись (store) ; 1 в x mov r2, x ; загрузка (load) ; из y в регистр Возможные варианты: ▪ r1 = 0, r2 = 1 ▪ r1 = 1, r2 = 0 ▪ r1 = 1, r2 = 1 ▪ r1 = 0, r2 = 0 x = 0, y = 0 61
  • 62. Переупорядочивание в процессорах архитектуры x86 Процессор 1 mov x, 1 ; запись (load) ; 1 в x mov r1, y ; загрузка (store) ; из y в регистр Процессор 2 mov y, 1 ; запись (load) ; 1 в x mov r2, x ; загрузка (store) ; из y в регистр ▪ r1 = 0, r2 = 0 В процессорах архитектуры x86 достустима перестановка операция load после store. Или, то же, операции store могут выполняться до операций load. x = 0, y = 0 StoreLoad 62
  • 63. Переупорядочивание в процессорах архитектуры x86 Поток 1 x = 1; asm volatile("" ::: "memory"); r1 = y; Программный барьер памяти: запретить переупорядочивание инструкций компилятором. 63
  • 64. Переупорядочивание в процессорах архитектуры x86 Поток 1 x = 1; asm volatile("" ::: "memory"); r1 = y; Поток 3 if (r1 == 0 && r2 == 0) printf("reordering happened!n"); Поток 2 y = 1; asm volatile("" ::: "memory"); r2 = x; 64 Переупорядочивание может произойти!
  • 65. Переупорядочивание в процессорах архитектуры x86 Поток 1 x = 1; asm volatile("" ::: "memory"); r1 = y; Поток 3 if (r1 == 0 && r2 == 0) printf("reordering happened!n"); Поток 2 y = 1; asm volatile("" ::: "memory"); r2 = x; 65
  • 66. Переупорядочивание в процессорах архитектуры x86 Поток 1 x = 1; asm volatile("mfence" ::: "memory"); asm volatile("" ::: "memory"); r1 = y; Поток 2 y = 1; asm volatile("mfence" ::: "memory"); asm volatile("" ::: "memory"); r2 = x; Поток 3 if (r1 == 0 && r2 == 0) printf("reordering happened!n"); 66
  • 67. Переупорядочивание в процессорах архитектуры x86 Поток 1 x = 1; asm volatile("mfence" ::: "memory"); asm volatile("" ::: "memory"); r1 = y; Программный барьер памяти: запретить переупорядочивание инструкций компилятором. Аппаратный барьер памяти: запретить переупорядочивание инструкций процессором. 67 mov DWORD PTR X[rip], 1 mfence mov eax, DWORD PTR Y[rip] ... mov DWORD PTR r1[rip], eax полный барьер памяти
  • 68. Переупорядочивание в процессорах архитектуры x86 Поток 1 x = 1; asm volatile("mfence" ::: "memory"); asm volatile("" ::: "memory"); r1 = y; Поток 3 if (r1 == 0 && r2 == 0) printf("reordering happened!n"); Переупорядочивание не произойдёт! Поток 2 y = 1; asm volatile("mfence" ::: "memory"); asm volatile("" ::: "memory"); r2 = x; 68
  • 69. Переупорядочивание в процессорах с ослабленным упорядочиванием IBM Blue Gene Смартфоны DEC Alpha Xbox 360 PowerPC ARM SPARC TSO x86 / 64 более сильное упорядочивание (strong model, sequential consistency) более слабое упорядочивание (weak model, relaxed ordering) Как правило сильное упорядочивание Ослабленное упорядочивание с поддержкой зависимостей по данным Вполне ослабленное упорядочивание PowerMac 69
  • 70. Переупорядочивание в процессорах с ослабленным упорядочиванием Для ослабленного упорядочивания характерно то, что одно ядро может видеть изменения в общей памяти в порядке, отличном от порядка, в котором другое ядро вносит изменения. int sharedCount; // глобальный счётчик void IncShared() { int count = 0; // локальный счётчик while (count < N) { randomBusyWork(); // случайная задержка // наивная реализация мьютекса int expected = 0; if (flag.compare_exchange_strong(expected, 1, std::memory_order_relaxed)); { sharedVal++; // выполняется под защитой мьютекса flag.store(0, std::memory_order_relaxed)) count++; } } 70
  • 71. Переупорядочивание в процессорах с ослабленным упорядочиванием Для ослабленного упорядочивания характерно то, что одно ядро может видеть изменения в общей памяти в порядке, отличном от порядка, в котором другое ядро вносит изменения. int sharedCount; // глобальный счётчик void IncShared() { int count = 0; // локальный счётчик while (count < N) { randomBusyWork(); // случайная задержка // наивная реализация мьютекса int expected = 0; if (flag.compare_exchange_strong(expected, 1, std::memory_order_relaxed)); { sharedVal++; // выполняется под защитой мьютекса flag.store(0, std::memory_order_relaxed)) count++; } } StoreStore 71
  • 72. int sharedCount; // глобальный счётчик void IncShared() { int count = 0; // локальный счётчик while (count < N) { randomBusyWork(); // случайная задержка // наивная реализация мьютекса int expected = 0; if (flag.compare_exchange_strong(expected, 1, std::memory_order_relaxed)); { sharedVal++; // выполняется под защитой мьютекса asm volatile("" ::: "memory"); flag.store(0, std::memory_order_relaxed)) count++; } } StoreStore Переупорядочивание в процессорах с ослабленным упорядочиванием Для ослабленного упорядочивания характерно то, что одно ядро может видеть изменения в общей памяти в порядке, отличном от порядка, в котором другое ядро вносит изменения. запрет компиляторного переупорядочивания 72
  • 73. Переупорядочивание в процессорах с ослабленным упорядочиванием Поток 1 int sharedCount; void IncShared() { int count = 0; while (count < N) { randomBusyWork(); int expected = 0; if (...) { sharedVal++; asm volatile(...); flag.store(0, ...) count++; } } Поток 2 int sharedCount; void IncShared() { int count = 0; while (count < N) { randomBusyWork(); int expected = 0; if (...) { sharedVal++; asm volatile(...); flag.store(0, ...) count++; } } 73
  • 74. Переупорядочивание в процессорах с ослабленным упорядочиванием Поток 1 int sharedCount; void IncShared() { int count = 0; while (count < N) { randomBusyWork(); int expected = 0; if (...) { sharedVal++; asm volatile(...); (1) flag.store(0, ...); count++; } } Поток 2 int sharedCount; void IncShared() { int count = 0; while (count < N) { randomBusyWork(); int expected = 0; if (...) { sharedVal++; asm volatile(...); flag.store(0, ...) count++; } } 74
  • 75. Переупорядочивание в процессорах с ослабленным упорядочиванием Поток 1 int sharedCount; void IncShared() { int count = 0; while (count < N) { randomBusyWork(); int expected = 0; if (...) { (2) sharedVal++; asm volatile(...); (1) flag.store(0, ...); count++; } } Поток 2 int sharedCount; void IncShared() { int count = 0; while (count < N) { randomBusyWork(); int expected = 0; if (...) { (3) sharedVal++; asm volatile(...); flag.store(0, ...) count++; } } 75
  • 76. Переупорядочивание в процессорах с ослабленным упорядочиванием $ ./prog 100000 // N = 100000 sharedCount = 199348 sharedCount = 199034 sharedCount = 199517 sharedCount = 199829 sharedCount = 199113 sharedCount = 199566 Допустим N = 100000 Каждый из 2 потоков выполняет 100000 операций инкремента разделяемой переменной sharedCount++. Ожидаемое значение: sharedVal = 200000 В реальности: 76
  • 77. Переупорядочивание при выполнении операция захвата-освобождения в процессорах архитектуры POWER Поток 1 (процессор 1) // Подготавливаем данные // и устанавливаем флаг // готовности for (i = 0; i < n; i++) { widget[i].x = x; widget[i].y = y; widget[i].ready = true; } Поток 2 (процессор 2) // Если данные готовы, // обрабатываем их for (i = 0; i < n; i++) { if (widget[i].ready) { do_some_stuff(widget[i]); } } L2-кэш Буфер 1 Буфер 2 Буфер N ... ... 77
  • 78. Переупорядочивание при выполнении операция захвата-освобождения в процессорах архитектуры POWER Поток 1 (процессор 1) // Подготавливаем данные // и устанавливаем флаг // готовности for (i = 0; i < n; i++) { widget[i].x = x; widget[i].y = y; widget[i].ready = true; } Поток 2 (процессор 2) // Если данные готовы, // обрабатываем их for (i = 0; i < n; i++) { if (widget[i].ready) { do_some_stuff(widget[i]); } } L2-кэш Буфер 1 Буфер 2 Буфер N ... ... 78
  • 79. Переупорядочивание при выполнении операция захвата-освобождения в процессорах архитектуры POWER Поток 1 (процессор 1) // Подготавливаем данные // и устанавливаем флаг // готовности for (i = 0; i < n; i++) { widget[i].x = x; widget[i].y = y; widget[i].ready = true; } Поток 2 (процессор 2) // Если данные готовы, // обрабатываем их for (i = 0; i < n; i++) { if (widget[i].ready) { do_some_stuff(widget[i]); } } L2-кэш Буфер 1 Буфер 2 Буфер N ... ... 79
  • 80. Переупорядочивание при выполнении операция захвата-освобождения в процессорах архитектуры POWER Поток 1 (процессор 1) // Подготавливаем данные // и устанавливаем флаг // готовности for (i = 0; i < n; i++) { widget[i].x = x; widget[i].y = y; widget[i].ready = true; } Поток 2 (процессор 2) // Если данные готовы, // обрабатываем их for (i = 0; i < n; i++) { if (widget[i].ready) { do_some_stuff(widget[i]); } } L2-кэш Буфер 1 Буфер 2 Буфер N ... ... 80
  • 81. Переупорядочивание при выполнении операция захвата-освобождения в процессорах архитектуры POWER Поток 1 (процессор 1) // Подготавливаем данные // и устанавливаем флаг // готовности for (i = 0; i < n; i++) { widget[i].x = x; widget[i].y = y; widget[i].ready = true; } Поток 2 (процессор 2) // Если данные готовы, // обрабатываем их for (i = 0; i < n; i++) { if (widget[i].ready) { do_some_stuff(widget[i]); } } L2-кэш: widget[i].y = y; widget[i].ready = true; ... ... 81
  • 82. Переупорядочивание при выполнении операция захвата-освобождения в процессорах архитектуры POWER Поток 1 (процессор 1) // Подготавливаем данные // и устанавливаем флаг // готовности for (i = 0; i < n; i++) { widget[i].x = x; widget[i].y = y; widget[i].ready = true; } Поток 2 (процессор 2) // Если данные готовы, // обрабатываем их for (i = 0; i < n; i++) { if (widget[i].ready) { do_some_stuff(widget[i]); } } L2-кэш: widget[i].y = y; widget[i].ready = true; ... ... StoreStore 82
  • 84. Программное переупорядочивание инструкций Первое правило робототехники компиляторов и процессоров при упорядочивании доступа к памяти: Нельзя изменять поведение однопоточной программы. ▪ В однопоточных программах переупорядочивания остаются незамеченными. ▪ То же самое - при многопоточном программировании на основе мьютексов, семафоров и т.д. ▪ Но не при использовании атомарных переменных и техник программирования без блокировок. 84
  • 85. Программное переупорядочивание инструкций mov eax, DWORD PTR y[rip] add eax, 111 mov DWORD PTR x[rip], eax mov DWORD PTR y[rip], 222 int x, y; int main() { x = y + 111; y = 222; printf("%d%d", x, y); gcc -S -masm=intel prog.c выполнение команды x = y + 111 завершено выполнение команды y = 222 завершено 85
  • 86. Программное переупорядочивание инструкций mov eax, DWORD PTR y[rip] mov edx, 222 ... mov DWORD PTR y[rip], 222 lea esi, [rax+111] ... mov DWORD PTR x[rip], esi int x, y; int main() { x = y + 111; y = 222; printf("%d%d", x, y); gcc -S -masm=intel -O2 prog.c выполнение команды y = 222 завершено выполнение команды x = y + 111 завершено y = 222 выполняется раньше x = y + 111 86
  • 87. Программное переупорядочивание инструкций mov eax, DWORD PTR y[rip] add eax, 111 mov DWORD PTR x[rip], eax mov esi, DWORD PTR x[rip] mov edx, 222 ... xor eax, eax mov DWORD PTR y[rip], 222 int x, y; int main() { x = y + 111; asm volatile("" ::: "memory"); y = 222; printf("%d%d", x, y); gcc -S -masm=intel -O2 prog.c явный барьер 87
  • 88. Программное переупорядочивание инструкций на примере с публикацией данных (операция захвата-освобождения) int data; bool isReleased = false; // данные опубликованы? void releaseData(int val) // опубликовать данные { data = val; // записать данные isReleased = true; // данные опубликованы! } // можно с ними работать 88
  • 89. Программное переупорядочивание инструкций на примере с публикацией данных (операция захвата-освобождения) Поток 1 int data; bool isReleased = false; void releaseData(int val) { data = val; isReleased = true; } Поток 2 void utilizeData() { while (!isReleased); doSomething(data); } Данные должны быть проинициализированы в 1 потоке перед тем, как они будут использованы во 2 потоке. 89
  • 90. Программное переупорядочивание инструкций на примере с публикацией данных (операция захвата-освобождения) Поток 1 int data; bool isReleased = false; void releaseData(int val) { data = val; isReleased = true; } Поток 2 void utilizeData() { while (!isReleased); doSomething(data); } 1. Данные проиницаилизрованы 90
  • 91. Программное переупорядочивание инструкций на примере с публикацией данных (операция захвата-освобождения) Поток 1 int data; bool isReleased = false; void releaseData(int val) { data = val; isReleased = true; } Поток 2 void utilizeData() { while (!isReleased); doSomething(data); } 1. Данные проициализированы 2. Ура! Второй поток может обрабатывать 91
  • 92. Программное переупорядочивание инструкций на примере с публикацией данных (операция захвата-освобождения) Поток 1 int data; bool isReleased = false; void releaseData(int val) { data = val; isReleased = true; } Поток 2 void utilizeData() { while (!isReleased); doSomething(data); } 92
  • 93. Программное переупорядочивание инструкций на примере с публикацией данных (операция захвата-освобождения) Поток 1 int data; bool isReleased = false; void releaseData(int val) { data = val; isReleased = true; } Поток 2 void utilizeData() { while (!isReleased); doSomething(data); } Из-за переупорядочивания инструкций компилятором флаг выставляется до того, как данные готовы. 93 StoreStore
  • 94. Программное переупорядочивание инструкций на примере с публикацией данных (операция захвата-освобождения) Поток 1 int data; bool isReleased = false; void releaseData(int val) { data = val; isReleased = true; } Поток 2 void utilizeData() { while (!isReleased); doSomething(data); } Из-за переупорядочивания инструкций компилятором флаг выставляется до того, как данные готовы. StoreStore 94
  • 95. Программное переупорядочивание инструкций на примере с публикацией данных (операция захвата-освобождения) Поток 1 int data; bool isReleased = false; void releaseData(int val) { data = val; isReleased = true; } Поток 2 void utilizeData() { while (!isReleased); doSomething(data); } Из-за переупорядочивания инструкций компилятором флаг выставляется до того, как данные готовы. 95
  • 96. Программное переупорядочивание инструкций на примере с публикацией данных (операция захвата-освобождения) Поток 1 int data; bool isReleased = false; void releaseData(int val) { data = val; isReleased = true; } Поток 2 void utilizeData() { while (!isReleased); doSomething(data); } 96
  • 97. Программное переупорядочивание инструкций на примере с публикацией данных (операция захвата-освобождения) Поток 1 int data; bool isReleased = false; void releaseData(int val) { data = val; asm volatile("" ::: "memory"); isReleased = true; } Поток 2 void utilizeData() { while (!isReleased); asm volatile("" ::: "memory"); doSomething(data); } 97
  • 98. Программное переупорядочивание инструкций на примере с публикацией данных (операция захвата-освобождения) Поток 1 int data; bool isReleased = false; void releaseData(int val) { data = val; asm volatile("" ::: "memory"); isReleased = true; } Поток 2 void utilizeData() { while (!isReleased); asm volatile("" ::: "memory"); doSomething(data); } Операция записи- освобождения Операция чтения- захвата 98
  • 99. Операции сохранения из чистого воздуха (out-of-thin air stores) if (x > 0) y++; register int r = y; if (x > 0) r++; y = r; Размещение переменной в регистре в процессе оптимизирующей компиляции создание новой регистровой переменной Новая операция сохранения (store) “из чистого воздуха” Создание операций сохранения “из чистого воздуха” не допустимо в соответствии с последним стандартом, однако... 99
  • 100. Операции сохранения из чистого воздуха (out-of-thin air stores) static pthread_mutex_t lock = PTHREAD_MUTEX_INITALIZER; static int count = 0; int trylock() { int rc; rc = pthread_mutex_trylock(&mutex); if (rc == 0) count++; однако в С & PThreads такая оптимизация допускается: static int count = 0; int trylock() { register int r = count; int rc; rc = pthread_mutex_trylock(&mutex); if (rc == 0) count++; count = r; новый store и новая гонка данных! 100
  • 102. Отношение happens-before (происходит-раньше) Отношение happens-before определяет, какие операции видят последствия других операций. Допустим A и В - операции в многопоточной программе. Тогда если А происходит-раньше В, тогда эффект (результат операции, который отразился в памяти) операции А становится видим для потока, выполняющего операцию В. x = 10; // A y = x + 1; // B 102
  • 103. Отношение happens-before (происходит-раньше) Отношение happens-before определяет, какие операции видят последствия других операций. Допустим A и В - операции в многопоточной программе. Тогда если А происходит-раньше В, тогда эффект (результат операции, который отразился в памяти) операции А становится видим для потока, выполняющего операцию В. x = 10; y = x + 1; z = sqrt(x * y); Все соотношения happens-before для операции А (x = 10) 103
  • 104. x = 10; y = x + 1; z = sqrt(x * y); m = k - 5; print(m) print(x) Отношение happens-before (происходит-раньше) Отношение happens-before определяет, какие операции видят последствия других операций. Допустим A и В - операции в многопоточной программе. Тогда если А происходит-раньше В, тогда эффект (результат операции, который отразился в памяти) операции А становится видим для потока, выполняющего операцию В. 104
  • 105. Поток 1 Поток 2 x = 10; y = x + 1; z = sqrt(x * y); m = k - 5; print(m) w = x + 2 Отношение happens-before (происходит-раньше) Отношение happens-before определяет, какие операции видят последствия других операций. Допустим A и В - операции в многопоточной программе. Тогда если А происходит-раньше В, тогда эффект (результат операции, который отразился в памяти) операции А становится видим для потока, выполняющего операцию В. 105
  • 106. Отношение happens-before определяет, какие операции видят последствия других операций. Допустим A и В - операции в многопоточной программе. Тогда если А происходит-раньше В, тогда эффект (результат операции, который отразился в памяти) операции А становится видим для потока, выполняющего операцию В. Происходит-раньше ≠ происходит раньше 1. Из того, что А происходит-раньше В не следует, что А происходит раньше В. 2. Из того, что А происходит раньше В не следует, что А происходит-раньше В 106
  • 107. Происходит-раньше не означает происходит раньше int x, y; int main() { x = y + 111; // A y = 222; // B printf("%d%d", x, y); 1. Из того, что А происходит-раньше В не следует, что А происходит раньше В. 107
  • 108. Происходит-раньше не означает происходит раньше mov eax, DWORD PTR y[rip] mov edx, 222 ... mov DWORD PTR y[rip], 222 lea esi, [rax+111] ... mov DWORD PTR x[rip], esi int x, y; int main() { x = y + 111; // A y = 222; // B printf("%d%d", x, y); gcc -S -masm=intel -O2 prog.c 1. Из того, что А происходит-раньше В не следует, что А происходит раньше В. 108
  • 109. Происходит-раньше не означает происходит раньше mov eax, DWORD PTR y[rip] mov edx, 222 ... mov DWORD PTR y[rip], 222 lea esi, [rax+111] ... mov DWORD PTR x[rip], esi int x, y; int main() { x = y + 111; y = 222; printf("%d%d", x, y); gcc -S -masm=intel -O2 prog.c выполнение команды y = 222 завершено выполнение команды x = y + 111 завершено y = 222 выполняется раньше x = y + 111 1. Из того, что А происходит-раньше В не следует, что А происходит раньше В. 109
  • 110. Происходит раньше не означает происходит-раньше 2. Из того, что А происходит раньше В не следует, что А происходит-раньше В Поток 2 if (ready) // B print(x); Поток 1 x = 42 ready = true; // A int x; bool ready = false; 110
  • 111. Происходит раньше не означает происходит-раньше 2. Из того, что А происходит раньше В не следует, что А происходит-раньше В Поток 2 if (ready) // B print(x); Поток 1 x = 42 ready = true; // A int x; bool ready = false; 111
  • 112. Происходит раньше не означает происходит-раньше 2. Из того, что А происходит раньше В не следует, что А происходит-раньше В Поток 2 if (ready) print(x); Поток 1 x = 42 ready = true; int x; bool ready = false; 112 происходит-раньше
  • 113. Происходит раньше не означает происходит-раньше 2. Из того, что А происходит раньше В не следует, что А происходит-раньше В Поток 2 if (ready) print(x); Поток 1 x = 42 ready = true; int x; bool ready = false; 113 происходит-раньше
  • 114. Отношение happens-before определяет, какие операции видят последствия других операций. Допустим A и В - операции в многопоточной программе. Тогда если А происходит-раньше В, тогда эффект (результат операции, который отразился в памяти) операции А становится видим для потока, выполняющего операцию В. Происходит-раньше ≠ происходит раньше 1. Из того, что А происходит-раньше В не следует, что А происходит раньше В. 2. Из того, что А происходит раньше В не следует, что А происходит-раньше В Отношение происходит-раньше имеет место тогда (и только тогда), когда это определёно стандартом языка. 114
  • 116. Последовательная согласованность (sequential consistency) std::atomic<int> x{0}, y{0}, v{0}, w{0} void thread1() { x.store(1, std::memory_order_seq_cst); v.store(y.load(std::memory_order_seq_cst), std::memory_order_seq_cst); } void thread2() { y.store(1, std::memory_order_seq_cst); w.store(x.load(std::memory_order_seq_cst), std::memory_order_seq_cst); } int main() { std::thread t1{thread1}, t2{thread2}; t1.join(); t2.join(); assert(v != 0 || w != 0); assert не сработает 116
  • 117. Последовательная согласованность (sequential consistency) std::atomic<int> data{0}; std::atomic<bool> ready{false}; int main() { std::thread producer{[]{ data.store(42, std::memory_order_seq_cst); ready.store(true, std::memory_order_seq_cst); }}; std::thread consumer{[]{ while (!ready.load(std::memory_order_seq_cst)) { } std::cout << data.load(std::memory_order_seq_cst); }}; producer.join(); consumer.join(); 42 117
  • 118. Последовательная согласованность (sequential consistency) std::atomic<int> z{0}; std::atomic<bool> x{false}, y{false}; int main(int argc, const char *argv[]) { std::thread store_y{[]{ x.store(true, std::memory_order_seq_cst); }}; std::thread store_x{[]{ y.store(true, std::memory_order_seq_cst); }}; std::thread read_x_then_y{[]{ while (!x.load(std::memory_order_seq_cst)) { } if (y.load(std::memory_order_seq_cst)) z.fetch_add(1, std::memory_order_seq_cst); }}; std::thread read_y_then_x{[]{ while (!y.load(std::memory_order_seq_cst)) { } if (y.load(std::memory_order_seq_cst)) z.fetch_add(1, std::memory_order_seq_cst); }}; store_x.join(); store_y.join(); read_x_then_y.join(); read_y_then_x.join(); std::cout << z.load(std::memory_order_seq_cst); z > 0 118
  • 119. Ослабленное упорядочение (relaxed ordering) std::atomic<int> x{0}, y{0}, v{0}, w{0} void thread1() { x.store(1, std::memory_order_relaxed); v.store(y.load(std::memory_order_relaxed), std::memory_order_relaxed); } void thread2() { y.store(1, std::memory_order_relaxed); w.store(x.load(std::memory_order_relaxed), std::memory_order_relaxed); } int main() { std::thread t1{thread1}, t2{thread2}; t1.join(); t2.join(); assert(v != 0 || w != 0); assert может сработать 119
  • 120. Ослабленное упорядочение (relaxed ordering) std::atomic<int> data{0}; std::atomic<bool> ready{false}; int main() { std::thread producer{[]{ data.store(42, std::memory_order_relaxed); ready.store(true, std::memory_order_relaxed); }}; std::thread consumer{[]{ while (!ready.load(std::memory_order_relaxed)) { } std::cout << data.load(std::memory_order_relaxed); }}; producer.join(); consumer.join(); 42 не гарантируется 120
  • 121. Ослабленное упорядочение (relaxed ordering) std::atomic<int> z{0}; std::atomic<bool> x{false}, y{false}; int main(int argc, const char *argv[]) { std::thread store_y{[]{ x.store(true, std::memory_order_relaxed); }}; std::thread store_x{[]{ y.store(true, std::memory_order_relaxed); }}; std::thread read_x_then_y{[]{ while (!x.load(std::memory_order_relaxed)) { } if (y.load(std::memory_order_relaxed)) z.fetch_add(1, std::memory_order_relaxed); }}; std::thread read_y_then_x{[]{ while (!y.load(std::memory_order_relaxed)) { } if (y.load(std::memory_order_relaxed)) z.fetch_add(1, std::memory_order_relaxed); }}; store_x.join(); store_y.join(); read_x_then_y.join(); read_y_then_x.join(); std::cout << z.load(std::memory_order_relaxed); z >= 0 121
  • 122. Метафора человека с блокнотом Иван 34 9 45 -3 21 0 9 7 11 Иван Значение переменной x x.load(memory_order_relaxed) 122
  • 123. Метафора человека с блокнотом Иван 34 9 45 -3 21 0 9 7 11 Иван Значение переменной x 123 x.load(memory_order_relaxed)
  • 124. Метафора человека с блокнотом Иван 34 9 45 -3 21 0 9 7 11 Иван Значение переменной x 124 x.load(memory_order_relaxed) encore!
  • 125. Метафора человека с блокнотом Иван 34 9 45 -3 21 0 9 7 11 Значение переменной x x.load(memory_order_relaxed) Иван 125
  • 126. Метафора человека с блокнотом Иван 34 9 45 -3 21 0 9 7 11 -8 Иван Значение переменной x x.store(-8, memory_order_relaxed) 126
  • 127. Метафора человека с блокнотом Иван 34 9 45 -3 21 0 9 7 11 -8 Значение переменной x x.load(memory_order_relaxed) Иван 127
  • 128. Метафора человека с блокнотом Иван Сергей Анна ... 34 9 45 -3 21 0 9 7 11 -8 Иван Анна Сергей Значение переменной x 128
  • 131. Иерархическая структура современных многоядерных систем кэш 2 кэш 1 Память кэш 2 кэш 1 кэш 1 кэш 1 Процессорные ядра 131
  • 133. Метафора репозитория 1 кэш L1 2 кэш L1 Память mov [x], 1 mov r1, [y] mov [y], 1 mov r2, [x] 133
  • 135. Метафора репозитория 1 2 Рекс Мухтар Центральный репозиторий Утечка данных из центрального репозитория в локальный и обратно 135
  • 136. Метафора репозитория 1 2 Рекс Мухтар Центральный репозиторий mov [x], 1 x = 1 x = 0 Утечка данных из центрального репозитория в локальный и обратно x = 0 136
  • 137. Метафора репозитория 1 2 Рекс Мухтар Центральный репозиторий mov [x], 1 x = 1 x = 1 Утечка данных из центрального репозитория в локальный и обратно x = 0 137
  • 138. Метафора репозитория 1 2 Рекс Мухтар Центральный репозиторий mov [x], 1 x = 1 x = 1 Утечка данных из центрального репозитория в локальный и обратно x = 1 138
  • 139. Метафора репозитория 1 2 Рекс Мухтар Центральный репозиторий mov [x], 1 x = 1 x = ? x = ? Неизвестно, когда изменения распространятся на другие потоки. 139
  • 140. Метафора репозитория 1 2 Рекс Мухтар Центральный репозиторий mov [x], 1 mov r1, [y] mov [y], 1 mov r2, [x] x = 0 y = 0 x = 0 y = 0 x = 0 y = 0 140
  • 141. Метафора репозитория 1 2 Рекс Мухтар Центральный репозиторий mov [x], 1 mov r1, [y] mov [y], 1 mov r2, [x] x = 0 y = 1 x = 1 y = 0 x = 0 y = 0 141
  • 142. Метафора репозитория 1 2 Рекс Мухтар Центральный репозиторий mov [x], 1 mov r1, [y] mov [y], 1 mov r2, [x] x = ? {0,1} y = 1 x = 1 y = ? {0,1} x = ? {0,1} y = ? {0,1} 142
  • 144. Барьер LoadLoad 1 Рекс Центральный репозиторий LoadLoad x = 1 y = 0 x = 1 y = 0 git pull, hg pull, svn update, cvs update Барьер LoadLoad: ▪ Предотвращает переупорядочивания между загрузками до барьера и загрузками после барьера. ▪ Гарантирует, что загруженные из центрального репозитория (памяти) в локальный репозиторий (кэш) значения будут по крайней мере такие же новые, как и последнее значение, которое “просочилось” из центрального репозитория. 144
  • 145. Барьер LoadLoad 1 Рекс Центральный репозиторий LoadLoad x = 1 y = 0 x = 1 y = 0 git pull, hg pull, svn update, cvs update // Так получилось, что // widget.ready = true утекло // в локальный репозиторий if (widget.ready) { // Подгрузить всё остальное // содержимое widget - // такое же “свежее”, как ready LoadLoadFence(); do_something(widget); } 145
  • 146. Барьер StoreStore 1 Рекс Центральный репозиторий StoreStore x = 1 y = 0 Барьер StoreStore: ▪ Предотвращает переупорядочивания между сохранениями до барьера и сохранениями после барьера. ▪ Гарантирует, что загруженные из локального репозитория (кэш) в локальный репозиторий (память) значения будут по крайней мере такие же новые, как и последнее значение, которое “просочилось” из локального репозитория. x = 1 y = 0 git push, hg push, svn commit, cvs commit 146
  • 147. Барьер StoreStore 1 Рекс Центральный репозиторий StoreStore x = 1 y = 0 x = 1 y = 0 git push, hg push, svn commit, cvs commit widget.x = x; widget.y = y; StoreStoreFence(); widget.ready = true; // Если ready = true просочится // Мухтару, то он увидит увидит // на центральном репозитории // всё остальные поля widget, // которые для него подготовил Рекс 147
  • 148. Барьер LoadStore 1 Рекс mov r1, [y] mov r2, [x] mov [z], 42 mov [w], r3 mov [v], r4 Операции загрузки load Операции сохранения store Переупорядочивание LoadStore 1. Есть набор инструкций, состоящий из операций сохранения и загрузки. 148
  • 149. Барьер LoadStore 1 Рекс mov r1, [y] mov r2, [x] mov [z], 42 mov [w], r3 mov [v], r4 Операции загрузки load - отложены Операции сохранения store - выполнены Переупорядочивание LoadStore 1. Есть набор инструкций, состоящий из операций сохранения и загрузки. 2. Если Рекс встречает операцию загрузки, то он просматривает следующие операции сохранения, и если они абсолютно не связаны с текущей операцией загрузки, то он откладывает выполнение операции загрузки и в первую очередь выполняет операции сохранения. 149
  • 150. Барьер LoadStore 1 Рекс mov r1, [y] mov r2, [x] mov [z], 42 mov [w], r3 mov [v], r4 Будут промахи по кэшу... Будут попадания в кэш... Переупорядочивание LoadStore 1. Есть набор инструкций, состоящий из операций сохранения и загрузки. 2. Если Рекс встречает операцию загрузки, которые промахиваются по кэшу, то он просматривает следующие операции сохранения, которые попадают в кэш, и если они абсолютно не связаны с текущей операцией загрузки, то он откладывает выполнение операции загрузки и в первую очередь выполняет операции сохранения. 150
  • 151. Барьер LoadStore 1 Рекс mov r1, [y] mov r2, [x] LoadStore mov [z], 42 mov [w], r3 mov [v], r4 Будут промахи по кэшу... Будут попадания в кэш... Переупорядочивание LoadStore 1. Есть набор инструкций, состоящий из операций сохранения и загрузки. 2. Если Рекс встречает операцию загрузки, которые промахиваются по кэшу, то он просматривает следующие операции сохранения, которые попадают в кэш, и если они абсолютно не связаны с текущей операцией загрузки, то он откладывает выполнение операции загрузки и в первую очередь выполняет операции сохранения. 151
  • 152. Барьер StoreLoad 1 Рекс mov [x], 1 mov r1, [y] 2 Мухтар mov [y], 1 mov r2, [x] x = 1 y = 0 x = 0 y = 1 r1 = 0 r2 = 0 152
  • 153. Барьер StoreLoad 1 Рекс mov [x], 1 StoreLoad mov r1, [y] 2 Мухтар mov [y], 1 StoreLoad mov r2, [x] x = 1 y = ? x = ? y = 1 Барьер StoreLoad: ▪ Гарантирует видимость для других процессоров всех операций сохранения, выполненных до барьера. ▪ Обеспечивает для всех операций загрузки, выполненных после барьера, получение результатов, которые имеют место во время барьера. ▪ Барьер предотвращает r1 = r2 = 0 ▪ StoreLoad ≠ StoreStore + LoadLoad 153
  • 154. Барьер StoreLoad 1 Рекс mov [x], 1 StoreLoad mov r1, [y] x = 1 y = ? Барьер StoreLoad (≠ StoreStore + LoadLoad): 1. Отправка (push) всех изменений в центральный репозиторий. Центральный репозиторий 154
  • 155. Барьер StoreLoad 1 Рекс mov [x], 1 StoreLoad mov r1, [y] x = 1 y = ? Барьер StoreLoad (≠ StoreStore + LoadLoad): 1. Отправка (push) всех изменений в центральный репозиторий. 2. Ожидание завершения выполнения операции отправки (в отличие от StoreStore, который может выполняться с задержкой). Центральный репозиторий 155
  • 156. Барьер StoreLoad 1 Рекс mov [x], 1 StoreLoad mov r1, [y] x = 1 y = ? Барьер StoreLoad (≠ StoreStore + LoadLoad): 1. Отправка (push) всех изменений в центральный репозиторий. 2. Ожидание завершения выполнения операции отправки (в отличие от StoreStore, который может выполняться с задержкой). 3. Загрузка (pull) всех последних изменений из центрального репозитория (в отличие от LoadLoad, который не загружает абсолютно последние изменения) Центральный репозиторий 156
  • 158. Семантика захвата (acquire) Семантика захвата и освобождения ▪ Применяется к операциям чтения или чтения- модификации-записи, при этом такая операция становится операцией чтения-захвата (read-acquire). ▪ Предотвращает переупорядочивание инструкции чтения- захвата и всех следующих в программе операций чтения или записи. ▪ Применяется к операциям записи или чтения- модификации-записи, причём такая операция становится операцией записи-освобождения (write-release). ▪ Предотвращает переупорядочивание инструкции записи- освобождения со всеми предшествующими в программе операциями чтения или записи. Семантика освобождения (release) 158
  • 159. read-acquire Семантика захвата и освобождения read / write read / write read / write read / write read / write read / write read / write read / write read / write read / write write-release 159
  • 160. read-acquire Семантика захвата и освобождения read / write read / write read / write read / write read / write read / write read / write read / write read / write read / write write-release 160
  • 161. Захват и освобождение - в терминах барьеров StoreLoadFence StoreStoreFence LoadStoreFenceLoadLoadFence Acquire (захват) Release (освобождение) 161
  • 162. LoadLoadFence + LoadStoreFence Захват и освобождение - в терминах барьеров read / write read / write read / write read / write read / write read / write read / write read / write StoreStoreFence + LoadStoreFence read-acquire write-release 162
  • 163. read / write read / write read / write read / write read / write read / write read / write read / write LoadLoadFence + LoadStoreFence Захват и освобождение - в терминах барьеров StoreStoreFence + LoadStoreFence read-acquire write-release 163
  • 164. LoadLoadFence + LoadStoreFence Захват и освобождение - в терминах барьеров read / write read / write read / write read / write read / write read / write read / write read / write StoreStoreFence + LoadStoreFence read-acquire write-release 164
  • 165. LoadLoadFence + LoadStoreFence Захват и освобождение - в терминах барьеров read / write read / write read / write read / write read / write read / write read / write read / write StoreStoreFence + LoadStoreFence read-acquire write-release 1 2 165
  • 166. LoadLoadFence + LoadStoreFence Захват и освобождение - в терминах барьеров read / write read / write read / write read / write read / write read / write read / write read / write StoreStoreFence + LoadStoreFence read-acquire write-release 2 1 2 1 166
  • 167. LoadLoadFence + LoadStoreFence Захват и освобождение - в терминах барьеров read / write read / write read / write read / write read / write read / write read / write read / write LoadStoreFence + StoreStoreFence read-acquire write-release 167
  • 168. Поток 2 ready.load( memory_order_acquire); print(x); Поток 1 x = 42 ready.store(true, memory_order_release); Семантика захват-освобождение на примере с публикацией int x; bool ready = false; 168
  • 169. Поток 1 x = 42 ready.store(true, memory_order_release); Семантика захват-освобождение на примере с публикацией Поток 2 ready.load( memory_order_acquire); print(x); int x; bool ready = false; Синхронизируется-с Synchronizes-with Всё, что здесь... … будет видно здесь 169
  • 170. Поток 1 x = 42 ready.store(true, memory_order_release); Семантика захват-освобождение на примере с публикацией Поток 2 ready.load( memory_order_acquire); print(x); int x; bool ready = false; Синхронизируется-с Synchronizes-with … будет видно здесь Всё, что здесь... Операция записи- освобождения Операция чтения- захвата 170
  • 171. Семантика захват-освобождение на примере с публикацией Поток 1 void initWidget(x, y, z) { w.x = x; w.y = x; w.z = z; ready.store(true, memory_order_release); } Поток 2 void useWidget() { while (!ready.load( memory_order_acquire)) {} doSomething(w); } widget w; bool ready = false; 171
  • 172. Поток 2 void useWidget() { while (!ready.load( memory_order_acquire)) {} doSomething(w); } Семантика захват-освобождение на примере с публикацией Поток 1 void initWidget(x, y, z) { w.x = x; w.y = x; w.z = z; ready.store(true, memory_order_release); } widget w; bool ready = false; Синхронизируется-с Synchronizes-with Всё, что здесь... … будет видно здесь Операция записи- освобождения Операция чтения-захвата 172
  • 173. Поток 2 void useWidget() { while (!ready.load( memory_order_acquire)) {} s1 = to_string(w); s2 = "number " + s1; print(s2); } Семантика захват-освобождение на примере с публикацией int x, y, z; bool ready = false; Поток 1 void initWidget(x, y, z) { x = 10 y = x + 20; z = x + 12; ready.store(true, memory_order_release); } Синхронизируется-с Synchronizes-with 173
  • 174. Поток 2 void useWidget() { while (!ready.load( memory_order_acquire)) {} s1 = to_string(w); s2 = "number " + s1; print(s2); } Семантика захват-освобождение на примере с публикацией int x, y, z; bool ready = false; Поток 1 void initWidget(x, y, z) { x = 10 y = x + 20; z = x + 12; ready.store(true, memory_order_release); } Синхронизируется-с Synchronizes-with Операция записи- освобождения Операция чтения-захвата 174
  • 175. Захват и освобождение в С++ std::memory_order_acquire ▪ Применяется к операциям чтения или чтения- модификации-записи: load, compare_exchange, fetch_add, fetch_or, etc. ▫ x.load(std::memory_order_acquire); ▫ compare_exchange_weak(x, y, std::memory_order_acquire); ▪ Применяется к операциям записи или чтения- модификации-записи: store, operator=, load, compare_exchange, fetch_add, fetch_or, etc ▫ x.store(42, std::memory_order_release); ▫ compare_exchange_weak(x, y, std::memory_order_release); std::memory_order_release 175
  • 177. Отношение synchronized-with (синхронизируется-с) Отношение syncrhonized-with более сильное по сравнению с happens- before, т.е.: synchronizes-with ⟶ happens-before 177
  • 178. Отношение synchronized-with (синхронизируется-с) Отношение syncrhonized-with более сильное по сравнению с happens- before, т.е.: synchronizes-with ⟶ happens-before Операция записи A над переменной x синхронизируется-с такой операцией операцией чтения B над x, которая читает значение, сохранённое 1. или операцией A. 2. или следующей за A операцией записи над x в том же потоке, который выполнил A. 3. или последовательностью операций чтения-модификации-записи над x в любом потоке, при условии, что значение, прочитанное первым потоком в этой последовательности, является значением, записанным операцией A. 178
  • 179. Отношение synchronized-with (синхронизируется-с) Отношение syncrhonized-with более сильное по сравнению с happens- before, т.е.: synchronizes-with ⟶ happens-before Операция записи A над переменной x синхронизируется-с такой операцией операцией чтения B над x, которая читает значение, сохранённое 1. или операцией A. 2. или следующей за A операцией записи над x в том же потоке, который выполнил A. 3. или последовательностью операций чтения-модификации-записи над x в любом потоке, при условии, что значение, прочитанное первым потоком в этой последовательности, является значением, записанным операцией A. 179
  • 180. Отношение synchronized-with (синхронизируется-с) Отношение syncrhonized-with более сильное по сравнению с happens- before, т.е.: synchronizes-with ⟶ happens-before Операция записи A над переменной x синхронизируется-с такой операцией операцией чтения B над x, которая читает значение, сохранённое 1. или операцией A. 2. или следующей за A операцией записи над x в том же потоке, который выполнил A. 3. или последовательностью операций чтения-модификации-записи над x в любом потоке, при условии, что значение, прочитанное первым потоком в этой последовательности, является значением, записанным операцией A. Иначе: Если поток 1 сохраняет значение, а поток 2 читает это значение, то существует отношение синхронизируется-с между операциями сохранения и загрузки. 180
  • 181. Поток 1 void prepare() { w.x = x; w.y = x; cntr++; w.ready.store(true, memory_order_release); } Операци Acquire-release (synchronized-with) в С++ Поток 2 void utilize() { while (!w.ready.load( memory_order_acquire)) {} doSomethingWith(w); writeLog(cntr); } Widget w; int cntr; Синхронизируется-с Synchronizes-with 181
  • 182. Поток 1 void prepare(Widget &w) { w.x = x; w.y = x; cntr++; w.ready.store(true, memory_order_release); } Операци Acquire-release (synchronized-with) в С++ Поток 2 void utilize() { if (!w.ready.load( memory_order_acquire)){ doSomethingWith(w); writeLog(cntr); } Widget w; int cntr; Синхронизируется-с Synchronizes-with 182