SlideShare une entreprise Scribd logo
1  sur  228
Télécharger pour lire hors ligne
ˠ˱́˱˼˼˶˼̍˾̌˶˳̌̈˹̂˼˹̃˶˼̍˾̌˶̃˶̆˾˿˼˿˴˹˹ 
˟̂˶˾̍3DUDOOHORPSXWLQJ7HFKQRORJLHV37
˜˶˻̇˹̐ˑ̃˿˽˱́˾̌˶˿̀˶́˱̇˹˹ 
˓˾˶˿̈˶́˶˵˾˿˶˳̌̀˿˼˾˶˾˹˶˹˾̂̃́̄˻̇˹˺ 
˒˱́̍˶́̌̀˱˽̐̃˹ˢ˶˽˱˾̃˹˻˱˸˱̆˳˱̃˱ 
˿̂˳˿˲˿˷˵˶˾˹̐˝˿˵˶˼̍̀˱˽̐̃˹ 
ˠ˱˸˾˹˻˿˳ˑ˼˶˻̂˶˺ˑ˼˶˻̂˱˾˵́˿˳˹̈ 
˛˱̅˶˵́˱˳̌̈˹̂˼˹̃˶˼̍˾̌̆̂˹̂̃˶˽ˢ˹˲˔ˤˣ˙ 
ˢ˱˺̃˻̄́̂˱KWWSFSFWVLEVXWLVUXaDSD]QLNRYWHDFKLQJ 
˓˿̀́˿̂̌KWWSVSLD]]DFRPVLEVXWLVUXIDOOSFWKRPH
ˑ̃˿˽˱́˾̌˶ 
̀˶́˶˽˶˾˾̌˶ 
2
ˑ̃˿˽˱́˾̌˶˿̀˶́˱̇˹˹ 
▪ Операция над разделяемой переменной 
атомарная, если она выполняется потоком 
за один неделимый шаг. Ни один из других 
потоков не может обнаружить эту переменную 
в промежуточном состоянии. 
▪ Если операции, которые совершают потоки над 
раздялемыми переменными, не атомарны, то это 
приведёт к гонкам данных. 
▪ Гонки данных являются причиной неопредлённого 
поведения, поскольку они приводят к частичным 
(фрагментированным, “разорванным”) 
чтениям и записям переменных. 
3
˥́˱˴˽˶˾̃˹́˿˳˱˾˾̌˶̈̃˶˾˹̐˹˸˱̀˹̂˹ 
uint64_t shared; 
int main() { 
shared = 0x100000002; 
... 
gcc -m32 -S -masm=intel -O1 prog.c 
mov DWORD PTR shared, 2 
mov DWORD PTR shared+4, 1 
запись младших 32 бит 
запись старших 32 бит 
▪ Выполнение присваивания 64-битного целого shared = 42 
на 32-разрядной архитектуре выполняется 
за 2 инструкции. 
▪ Операция записи не атомарная. 
4
˥́˱˴˽˶˾̃˹́˿˳˱˾˾̌˶̈̃˶˾˹̐˹˸˱̀˹̂˹ 
uint64_t shared; 
int main() { 
shared = 0x100000002; 
... 
gcc -m32 -S -masm=intel -O1 prog.c 
mov DWORD PTR shared, 2 
mov DWORD PTR shared+4, 1 
1 поток 
запись старших 32 бит 
▪ Вытеснение потока после записи младших бит приведёт к 
тому, что эти биты останутся в памяти и будут 
использованы другими потоками. 
▪ На многоядерных системах даже не требуется 
вытеснения. 5
˥́˱˴˽˶˾̃˹́˿˳˱˾˾̌˶̈̃˶˾˹̐˹˸˱̀˹̂˹ 
uint64_t shared; 
int main() { 
shared = 0x100000002; 
... 
gcc -m32 -S -masm=intel -O1 prog.c 
mov DWORD PTR shared, 2 
mov DWORD PTR shared+4, 1 
1 поток 
2 поток 
▪ Вытеснение потока после записи младших бит приведёт к 
тому, что эти биты останутся в памяти, а старшие будут 
записаны другим потоком. 
▪ На многоядерных системах даже не требуется 
вытеснения. 6
˥́˱˴˽˶˾̃˹́˿˳˱˾˾̌˶̈̃˶˾˹̐˹˸˱̀˹̂˹ 
uint64_t shared; 
uint64_t getShared() { 
return shared; 
} 
gcc -m32 -S -masm=intel -O1 prog.c 
mov eax, DWORD PTR shared 
mov edx, DWORD PTR shared+4 
ret 
чтение младших 32 бит 
чтение старших 32 бит 
▪ Выполнение чтения 64-битного целого shared на 32- 
разрядной архитектуре выполняется 
за 2 инструкции. 
▪ Операция чтения не атомарная. 
7
˥́˱˴˽˶˾̃˹́˿˳˱˾˾̌˶̈̃˶˾˹̐˹˸˱̀˹̂˹ 
▪ Инструкции могут быть неатомарными, даже если 
выполняются одной процессорной инструкцией. 
Например, в ARMv7 инструкция для помещения содержимого 
двух 32-битных регистров в один 64-битный: 
strd r0, r1, [r2] 
На некоторых процессорах эта инструкция реализуются двумя 
отдельными операциями сохранения. 
32-битная операция mov атомарна только для выравненных 
данных. В остальных случаях операция неатомарная 
▪ Вытеснение потоков, выполняющих данные операции, или 
выполнение операций в двугих потоках в многоядерных 
системах приводит к неопределённому поведению. 
8
ˑ̃˿˽˱́˾˿̂̃̍˳ˢ˹ˢ 
▪ В языках С и С++ предполагается, что все 
операции неатомарны. 
▪ Операции могут быть атомарными в 
большинстве случаев, например, операция 
присваивания 32-битного целого значения. 
▪ Тем не менее, все инструкции должны 
рассматриваться как неатомарные. 
▪ К счастью, в С и С++ есть набор шаблонов 
атомарных типов данных. 
9
ˑ̃˿˽˱́˾˿̂̃̍˳ˢ˹ˢ 
▪ Атомарные операции в С++ неделимы. Из 
любого потока нельзя обнаружить эту 
операцию выполненной частично - она 
либо выполнена, либо невыполнена. 
▪ Это позволяет избежать гонок данных. 
▪ Для атомарных типов определён метод 
is_lock_free, позволяющий определить, 
являются ли операции над ним напрямую 
с помощью атомарных инструкций, или они 
эмулируются. 
10
ˑ̃˿˽˱́˾̌˶̃˹̀̌˳ˢ˹ˢ 
▪ std::atomic_flag - единственный тип, 
который не имеет функции is_lock_free. Он 
предельно простой, всецело атомарный и 
поддерживает одну операцию: test_and_set - 
проверить и установить. 
▪ Остальные типы определяются 
специализацией шаблона std::atomic, 
например std::atomicint и std:: 
atomicvoid* 
11
ˑ̃˿˽˱́˾̌˶̃˹̀̌˳ˢ˹ˢ 
Атомарные тип Соответствующая специализация 
std::atomic_bool std::atomicbool 
std::atomic_char std::atomicchar 
std::atomic_schar std::atomicsigned char 
std::atomic_uchar std::atomicunsigned char 
std::atomic_short std::atomicshort 
std::atomic_ushort std::atomicunsigned short 
std::atomic_int std::atomicint 
std::atomic_uint std::atomicunsigned int 
std::atomic_long std::atomiclong 
... 
+ пользовательские типы 
12
˟̀˶́˱̇˹˹˾˱˵˱̃˿˽˱́˾̌˽˹̃˹̀˱˽˹ 
▪ Операции сохранения: 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. 
13
ˑ̃˿˽˱́˾̌˺̅˼˱˴VWGDWRPLFBIODJ 
std::atomic_flag должен быть проиниализирован: 
std::atomic_flag flag = ATOMIC_FLAG_INIT 
Очистить флаг (операция сохранения): установить значение 
false: 
flag.clear(std::memory_order_release); 
Установить значение флага в true и вернуть предыдущее 
значение: 
bool x = flag.test_and_set(); 
Для атомарного флага запрещены операции копирования и 
присваивания. 
14
ˡ˶˱˼˹˸˱̇˹̐̂̀˹˾˼˿˻˱˾˱˿̂˾˿˳˶˱̃˿˽˱́˾˿˴˿̅˼˱˴˱ 
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. Можно использовать с lock_guard и unique_guard! 15
˟̀˶́˱̇˹˹̂˿̆́˱˾˶˾˹̐˹˸˱˴́̄˸˻˹˱̃˿˽˱́˾̌̆̃˹̀˿˳ 
// Объявить переменную и проициализировать true 
std::atomicbool 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); 
16
˟̀˶́˱̇˹̐q̂́˱˳˾˹̃̍˹˿˲˽˶˾̐̃̍r 
Операция 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); 
17
˟̀˶́˱̇˹̐q̂́˱˳˾˹̃̍˹˿˲˽˶˾̐̃̍r 
compare_exchange_weak() - сохранение может не 
произойти, даже если текущее значение совпадает с 
ожидаемым. Значение переменной не изменится, функция 
возвращает false. 
Последнее возможно при отсутствии аппаратной 
поддержки команды сравнить-и-обменять, из-за того, что 
поток может быть вытеснен в середине требуемой 
последовательности команд (ложный отказ). 
Из-за возможного ложного отказа функцию 
compare_exchange_weak() обычно вызывают в цикле: 
bool expected = false; 
extern atomicbool b; 
... 
while (!b.compare_exchange_weak(expected, true); 
18
˟̀˶́˱̇˹̐q̂́˱˳˾˹̃̍˹˿˲˽˶˾̐̃̍r 
compare_exchange_strong() гарантирует замену 
переменной в случае выполнения условия. 
▪ compare_exchange_strong() выгодно использовать в 
случае однократном выполнении операции, т.е. при 
необходимости заменить значение на жалаемое, и в 
случае, если вычисление нового значения занимает 
длительное время. 
▪ Если функция compare_exchange вызывается в цикле, 
тогда предпочтительнее использовать 
compare_exchange_weak, чтобы избежать двойного 
цикла (compare_exchange_strong реализован в виде 
цикла на системах, которые не поддерживают 
атомарной операции сравнения и замены). 
19
ˑ̃˿˽˱́˾̌˺̃˹̀DWRPLF7
! 
Функции для типа atomicT*: 
▪ 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::atomicC* ptr(arr); 
C* x = ptr.fetch_add(2); 
assert(x == arr); 
assert(p.load() == some_array[2]); 
p--; x = p; 
assert(x == arr[1]); 
операции 
чтения- 
модификации- 
записи 
20
ˢ̃˱˾˵˱́̃˾̌˶˱̃˿˽˱́˾̌˶̇˶˼˿̈˹̂˼˶˾˾̌˶̃˹̀̌ 
Функции для типов стандартных атомарных 
целочисленных типов (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^= 
Отсутствуют операции: 
▫ умножения, деления, сдвига 
21
ˠ˿˼̍˸˿˳˱̃˶˼̍̂˻˹˶˱̃˿˽˱́˾̌˶̃˹̀̌ 
В качестве шаблона std::atomic может 
выступать тип, он должен удовлетворять 
требованиям 
▪ В нём должен присутствовать тривиальный 
оператор присваивания. Нет виртуальных 
функций и виртуальных базовых классов, а 
оператор присваивания генерируется 
автоматически (например, memcpy) 
▪ Тип должен допускать побитовое сравнение на 
равенство (например, с помощью memcmp) 
22
˞˶̂˻˿˼̍˻˿ 
̀́˹˽˶́˿˳̀˿̂˼˶˵̂̃˳˹˺ 
̀˶́˶̄̀˿́̐˵˿̈˹˳˱˾˹̐ 
˳ˢ 
23
ˠ́˹˽˶́̌̀˶́˶̄̀˿́̐˵˿̈˹˳˱˾˹̐˳ˢ 
x = y = v = w = 0 
thread1() { 
x = 1 
v = y 
} 
thread2() { 
y = 1 
w = x 
} 
main() { 
start_threads(thread1, thread2) 
join_all_threads() 
assert(v != 0 || w != 0) assert может сработать! 
24
ˠ́˹˽˶́̌̀˶́˶̄̀˿́̐˵˿̈˹˳˱˾˹̐˳ˢ 
ready = false 
producer() { 
data = 42 
ready = true 
} 
consumer() { 
while (!ready) 
{ } 
print(data) 
} 
data = 42 не гарантируется 
25
ˠ́˹˽˶́̌̀˶́˶̄̀˿́̐˵˿̈˹˳˱˾˹̐˳ˢ 
x = y = false, z = 0 
thread_store_x() { x = true } 
thread_store_y() { y = true } 
thread_read_x_then_y() { 
while (!x) { } 
if (y) z++ 
} 
thread_read_y_then_x() { 
while (!y) { } 
if (x) z++ 
} 
main { 
run_all_threads(store_x, store_y, 
read_x_then_y, read_y_then_x) 
join_all_threads() 
assert(z != 0) assert может сработать! 26
˝˶̃˱˽˿́̅˿˸̌ 
̀́˿˴́˱˽˽̌ 
˹˼˹̂̃́˱̉˾˱̐̀́˱˳˵˱ 
27
˟˵˹˳˾̌˺˾˿˳̌˺˹̀́˶˻́˱̂˾̌˺̀˱́˱˼˼˶˼̍˾̌˺˽˹́ 
Выполняет ли компьютер программу, которую вы 
написали? 
28
˟˵˹˳˾̌˺˾˿˳̌˺˹̀́˶˻́˱̂˾̌˺̀˱́˱˼˼˶˼̍˾̌˺˽˹́ 
Выполняет ли компьютер программу, которую вы 
написали? 
НЕТ 
29
˟˵˹˳˾̌˺˾˿˳̌˺˹̀́˶˻́˱̂˾̌˺̀˱́˱˼˼˶˼̍˾̌˺˽˹́ 
Выполняет ли компьютер программу, которую вы 
написали? 
НЕТ Просто дело в том что... 
иерархическая структура памяти, внеочередное 
выполнение команд процессора и компиляторная 
оптимизация. 30
ˑ̀̀˱́˱̃˾˿˳̌˸˳˱˾˾̌˶̃́˱˾̂̅˿́˽˱̇˹˹̀́˿˴́˱˽˽̌ 
кэш 2 
кэш 1 
Память 
кэш 2 
кэш 1 кэш 1 кэш 1 
Процессорные 
ядра 
31
ˑ̀̀˱́˱̃˾˿˳̌˸˳˱˾˾̌˶̃́˱˾̂̅˿́˽˱̇˹˹̀́˿˴́˱˽˽̌ 
кэш 2 
кэш 1 
Память 
кэш 2 
кэш 1 кэш 1 кэш 1 
Когерентность 
кэша (MESI, 
MOESI, MESIF) 
Процессорные 
ядра 
32
ˑ̀̀˱́˱̃˾˿˳̌˸˳˱˾˾̌˶̃́˱˾̂̅˿́˽˱̇˹˹̀́˿˴́˱˽˽̌ 
кэш 2 
кэш 1 
Память 
кэш 2 
кэш 1 кэш 1 кэш 1 
Когерентность 
кэша (MESI, 
MOESI, MESIF) 
Внеочередное 
выполнение 
инструкций (out-of- 
order execution) 
Процессорные 
ядра 
33
ˑ̀̀˱́˱̃˾˿˳̌˸˳˱˾˾̌˶̃́˱˾̂̅˿́˽˱̇˹˹̀́˿˴́˱˽˽̌ 
Intel 64 CISC macro-instructions 
Instruction Fetch  
PreDecode 
Instruction Queue (IQ) 
Decode 
Rename/Allocate 
Retirement Unit 
(Re-Order 
Buffer) 
Scheduler 
Reservation 
Stations 
Execution Units 
Intel 64 CISC 
macro-instr. 
Execution 
Engine 
(out-of-order) 
ITLB Instruction Cache (32KiB) 
L2 TLB 
L2 Cache 
(256 KiB, 8-way) 
DTLB Data Cache (32KiB) 
L3 
Cache 
Front-End 
Pipeline 
(in-order) 
Nehalem RISC 
micro-operations 
,QWHO1HKDOHPRUH 
3LSHOLQH 
34
ˑ̀̀˱́˱̃˾˿˳̌˸˳˱˾˾̌˶̃́˱˾̂̅˿́˽˱̇˹˹̀́˿˴́˱˽˽̌ 
16 
ITLB L1 I-cache (32 KiB, 4-way) 
byte/cycle 
Instruction 
Fetch Unit 
(IFU) 
Pre Decode, 
Prefetch Buffer, 
Instruction Length 
Decoder 
Instruction Queue (IQ) 
(18 entry – 18 instruction max.) 
Instruction Decoding Unit (IDU) 
3 simple + 1 complex 
Simple Complex 
Simple Simple micro-cod 
Decoded Instruction Queue (DIQ, 28 uops. max) 
Loop Stream Detection, Micro-Fusion, Macro- 
Fusion 
Intel64 CISC 
macro-instr. 
Nehalem 
RISC 
micro-operations 
4 micro-ops. 
/cycle 
Unified L2- 
Cache 
6 instr./cycle 
Branch 
Prediction 
Unit (BPU) 
5 instructions/cycle 
4 uops./cycle 
35
˛˿˽̀˹˼̐̃˿́˾˱̐˿̀̃˹˽˹˸˱̇˹̐˳˹˵̌˿̀̃˹˽˹˸˱̇˹˺ 
▪ Peephole-оптимизация 
▪ Локальная оптимизация 
▪ Внутрипроцедурная оптимизация 
▪ Оптимизация циклов 
▪ Межпроцедурная оптимизация 
36
˛˿˽̀˹˼̐̃˿́˾˱̐˿̀̃˹˽˹˸˱̇˹̐˳˹˵̌˿̀̃˹˽˹˸˱̇˹˺ 
▪ Peephole-оптимизация 
▪ Локальная оптимизация 
▪ Внутрипроцедурная оптимизация 
▪ Оптимизация циклов 
▫ Анализ индуктивных переменных 
▫ Деление цикла на части 
▫ Объединение циклов 
▫ Инверсия цикла 
▫ Расщепление цикла 
▪ Межпроцедурная оптимизация 37
˛˿˽̀˹˼̐̃˿́˾˱̐˿̀̃˹˽˹˸˱̇˹̐˳˹˵̌˿̀̃˹˽˹˸˱̇˹˺ 
-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 
38
˛˿˽̀˹˼̐̃˿́˾˱̐˿̀̃˹˽˹˸˱̇˹̐̀́˹˽˶́̌˿̀̃˹˽˹˸˱̇˹˺ 
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; 
39
˛˿˽̀˹˼̐̃˿́˾˱̐˿̀̃˹˽˹˸˱̇˹̐̀́˹˽˶́̌˿̀̃˹˽˹˸˱̇˹˺ 
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; 
} 40
˛˿˽̀˹˼̐̃˿́˾˱̐˿̀̃˹˽˹˸˱̇˹̐̀́˹˽˶́̌˿̀̃˹˽˹˸˱̇˹˺ 
Что компилятор знает: 
Все операции с памятью совершаются в текущем 
потоке, что они в точности означают, и какие 
существуют зависимости по данным. 
Что компилятор не знает: 
Какие области памяти доступны и изменяются в 
разных потоках. 
Как решить: 
Сказать! Как-то пометить операции, которые 
выполняются с разделяемыми переменными. 
41
ˮ̃˱̀̌̃́˱˾̂̅˿́˽˱̇˹˹̀́˿˴́˱˽˽̌ 
Исходный код 
Компилятор 
удаление подвыражений, свёртка 
констант, оптимизация циклов, ... 
Процессор 
предвыборка, спекулятивное 
выполнение инструкций, 
буферизация, HTM, ... 
Кэш 
частные и общие кэши, буферы 
записи, ... 
Реальное выполнение 
программы 
“Гораздо лучше 
выполнять другую 
программу - не ту, что 
вы написали. Вам на 
самом деле даже не 
хочется выполнять эту 
вашу чушь - вы хотите 
запускать другую 
программу! Всё это 
делается для вашего 
блага.” 
42
ˮ̃˱̀̌̃́˱˾̂̅˿́˽˱̇˹˹̀́˿˴́˱˽˽̌ 
▪ Как правило нельзя 
определить, на каком уровне 
произошла трансформация. 
▪ Трансформации на всех 
уровнях эквивалентны, что 
позволяет рассматривать их 
как переупорядочивание 
операций загрузки (loads) 
и записи (stores). 
▪ Необходимое условие при 
выполнении трансформаций 
- сохранение иллюзии 
последовательно 
согласованного кода. 
43 
Исходный код 
Компилятор 
удаление подвыражений, свёртка 
констант, оптимизация циклов, ... 
Процессор 
предвыборка, спекулятивное 
выполнение инструкций, 
буферизация, HTM, ... 
Кэш 
частные и общие кэши, буферы 
записи, ... 
Реальное выполнение 
программы
ˠ˿̂˼˶˵˿˳˱̃˶˼̍˾˱̐̂˿˴˼˱̂˿˳˱˾˾˿̂̃̍VHTXHQWLDOFRQVLVWHQF6
“Результат выполнения программы такой, как если бы 
операции всех процессоров выполнялись последовательно и 
результат операции каждого отдельного процессора 
появлялся бы в этой последовательности в порядке, 
который определяется программой.” (Л. Лэмпорт, 1979) 
▪ Рассмотрим многопроцессорную систему, состоящую из 
нескольких последовательных процессоров. 
▪ Операции, выполняемые процессорами над некоторой 
областью памяти (страница, объект, адрес, ...), 
появляются в одном и том же порядке для всех 
процессоров, несмотря на то, что фактическая 
последовательность выполнения операций может 
быть другой. 
44
ˠ˿̂˼˶˵˿˳˱̃˶˼̍˾˱̐̂˿˴˼˱̂˿˳˱˾˾˿̂̃̍VHTXHQWLDOFRQVLVWHQF6
“Результат выполнения программы такой, как если бы 
операции всех процессоров выполнялись последовательно и 
результат операции каждого отдельного процессора 
появлялся бы в этой последовательности в порядке, 
который определяется программой.” (Л. Лэмпорт, 1979) 
▪ Рассмотрим многопроцессорную систему, состоящую из 
нескольких последовательных процессоров. 
▪ Операции, выполняемые процессорами над некоторой 
областью памяти (страница, объект, адрес, ...), 
появляются в одном и том же порядке для всех 
процессоров, несмотря на то, что фактическая 
последовательность выполнения операций может 
быть другой. 
И это прекрасно! 
45
ˠ˿̂˼˶˵˿˳˱̃˶˼̍˾˱̐̂˿˴˼˱̂˿˳˱˾˾˿̂̃̍VHTXHQWLDOFRQVLVWHQF6
“Результат выполнения программы такой, как если бы 
операции всех процессоров выполнялись последовательно и 
результат операции каждого отдельного процессора 
появлялся бы в этой последовательности в порядке, 
который определяется программой.” (Л. Лэмпорт, 1979) 
▪ Рассмотрим многопроцессорную систему, состоящую из 
нескольких последовательных процессоров. 
▪ Операции, выполняемые процессорами над некоторой 
областью памяти (страница, объект, адрес, ...), 
появляются в одном и том же порядке для всех 
процессоров, несмотря на то, что фактическая 
последовательность выполнения операций может 
быть другой. 
И это прекрасно! 
но...46
ˠ˿̂˼˶˵˿˳˱̃˶˼̍˾˱̐̂˿˴˼˱̂˿˳˱˾˾˿̂̃̍VHTXHQWLDOFRQVLVWHQF6
… но вы этого не хотите! 
▪ Скорее всего очень нерационально выполнять в точности 
то, что вы написали. 
▪ Гораздо лучше выполнить нечто иное, которое бы работало 
так же, как и то, что вы написали, но выполнялось бы 
гораздо быстрее. 
Поэтому 
▪ Мы (программное обеспечение ПО: компилятор и 
аппаратное обеспечение АО: кэш, процессор) будем это 
делать! 
▪ А вы (программисты), в свою очередь, должны обеспечить 
возможность корректной трансформации и выполнения 
программы так, чтобы сохранялась иллюзия 
последовательной согласованности, включив в свою 
программу необходимые ограничения. 47
˝˿˵˶˼̍̀˱˽̐̃˹̎̃˿˵˿˴˿˳˿́ 
Вы обещаете 
Корректно 
реализовать 
синхронизацию 
в вашей 
программе 
(путём добавления 
необходимых 
инструкций в 
программу, 
делающих её 
безопасной 
относительно гонок) 
Система 
обещает 
Обеспечить 
иллюзию 
выполнения той 
программы, 
которую вы 
написали. 
(путём компиляции 
и выполнения) 
48
˝˿˵˶˼̍̀˱˽̐̃˹̎̃˿˵˿˴˿˳˿́ 
Вы обещаете 
Корректно 
реализовать 
синхронизацию 
в вашей 
программе 
(путём добавления 
необходимых 
инструкций в 
программу, 
делающих её 
безопасной 
относительно гонок) 
Система 
обещает 
Обеспечить 
иллюзию 
выполнения той 
программы, 
которую вы 
написали. 
(путём компиляции 
и выполнения) 
Модель памяти определяет, какие действия вы должны 
совершить и как должна отреагировать система, чтобы 
обеспечить выполнение операций с памятью в необходимой 
последовательности. 49
ˑ̀̀˱́˱̃˾˿˶ 
̀˶́˶̄̀˿́̐˵˿̈˹˳˱˾˹˶ 
˹˾̂̃́̄˻̇˹˺ 
50
ˠ˶́˶̄̀˿́̐˵˿̈˹˳˱˾˹˶˾˱̀́˹˽˶́˶˱˼˴˿́˹̃˽˱˕˶˻˻˶́˱ 
Алгоритм Деккера позволяет решать проблему взаимного 
исключения и был опубликован в 1965 г. 
Он не приводит к взаимным исключениям (deadlock) и 
свободен от голодания (starvation). 
Поток 1 
flag1 = 1; 
if (flag2 != 0) 
// ожидать освобождения 
// критической секции 
else 
// войти в критическую 
// секцию 
Поток 2 
flag2 = 1; 
if (flag1 != 0) 
// ожидать освобождения 
// критической секции 
else 
// войти в критическую 
// секцию 
51
ˠ˶́˶̄̀˿́̐˵˿̈˹˳˱˾˹˶˾˱̀́˹˽˶́˶˱˼˴˿́˹̃˽˱˕˶˻˻˶́˱ 
Процессор 1 Процессор 2 
flag2 = 1; 
if (flag1 != 0) 
{ … } 
flag1 = 1; 
if (flag2 != 0) 
{ … } 
Store Buffer Store Buffer 
Память: flag1 = 0, flag2 = 0 
52
ˠ˶́˶̄̀˿́̐˵˿̈˹˳˱˾˹˶˾˱̀́˹˽˶́˶˱˼˴˿́˹̃˽˱˕˶˻˻˶́˱ 
Процессор 1 Процессор 2 
flag1 = 1; 
if (flag2 != 0) 
{ … } 
Память: flag1 = 0, flag2 = 0 
flag2 = 1; 
if (flag1 != 0) 
{ … } 
Store Buffer 
flag1 = 1 
Store Buffer 
Сохранение 
1 в буфере 
53
ˠ˶́˶̄̀˿́̐˵˿̈˹˳˱˾˹˶˾˱̀́˹˽˶́˶˱˼˴˿́˹̃˽˱˕˶˻˻˶́˱ 
Процессор 1 Процессор 2 
flag1 = 1; 
if (flag2 != 0) 
{ … } 
Память: flag1 = 0, flag2 = 0 
flag2 = 1; 
if (flag1 != 0) 
{ … } 
Store Buffer 
flag1 = 1 
Сохранение 
1 в буфере 
Сохранение 
1 в буфере 
Store Buffer 
flag1 = 1 
54
ˠ˶́˶̄̀˿́̐˵˿̈˹˳˱˾˹˶˾˱̀́˹˽˶́˶˱˼˴˿́˹̃˽˱˕˶˻˻˶́˱ 
Процессор 1 Процессор 2 
flag1 = 1; 
if (flag2 != 0) 
{ … } 
Память: flag1 = 0, flag2 = 0 
flag2 = 1; 
if (flag1 != 0) 
{ … } 
Store Buffer 
flag1 = 1 
Сохранение 
1 в буфере 
Сохранение 
1 в буфере 
Store Buffer 
flag1 = 1 
Чтение 0 
для flag2 
Чтение 0 
для flag1 
StoreLoad 
55
ˠ˶́˶̄̀˿́̐˵˿̈˹˳˱˾˹˶˾˱̀́˹˽˶́˶˱˼˴˿́˹̃˽˱˕˶˻˻˶́˱ 
Процессор 1 Процессор 2 
flag1 = 1; 
if (flag2 != 0) 
{ … } 
Память: flag1 = 0, flag2 = 0 
flag2 = 1; 
if (flag1 != 0) 
{ … } 
Store Buffer 
flag1 = 1 
Сохранение 
1 в буфере 
Сохранение 
1 в буфере 
Store Buffer 
flag1 = 1 
Чтение 0 
для flag2 
Чтение 0 
для flag1 
56
ˠ˶́˶̄̀˿́̐˵˿̈˹˳˱˾˹˶˾˱̀́˹˽˶́˶˱˼˴˿́˹̃˽˱˕˶˻˻˶́˱ 
Процессор 1 Процессор 2 
flag1 = 1; 
if (flag2 != 0) 
{ … } 
Память: flag1 = 1, flag2 = 1 
flag2 = 1; 
if (flag1 != 0) 
{ … } 
Store Buffer 
flag1 = 1 
Сохранение 
1 в буфере 
Сохранение 
1 в буфере 
Store Buffer 
flag1 = 1 
Чтение 0 
для flag2 
Чтение 0 
для flag1 
Сброс буфера 
(flag1) 
Сброс буфера 
(flag2) 
57
ˠ˶́˶̄̀˿́̐˵˿̈˹˳˱˾˹˶˳́˱˸˼˹̈˾̌̆̀́˿̇˶̂̂˿́˱̆ 
ˣ˹̀ 
̀˶́˶̄̀˿́̐ 
˵˿̈˹˳˱˾˹̐ 
˱́̆˹̃˶˻̃̄́̌ 
$OSKD $50Y 32:(5 63$5 
502 
63$5 
362 
63$5 
762 
[ $0' ,$ 
/RDGV̀˿̂˼˶ 
ORDGV 
/RDGV̀˿̂˼˶ 
VWRUHV 
6WRUHV̀˿̂˼˶ 
VWRUHV 
6WRUHV̀˿̂˼˶ 
ORDGV 
ˑ˟̂ORDGV 
ˑ˟̂VWRUHV 
˘˱˳˹̂˹˽̌˶ 
ORDGV 
АО - атомарные операции 58
ˠ˶́˶̄̀˿́̐˵˿̈˹˳˱˾˹˶˳́˱˸˼˹̈˾̌̆̀́˿̇˶̂̂˿́˱̆ 
более сильное упорядочивание 
(strong model, sequential consistency) 
IBM Blue Gene 
Смартфоны 
DEC Alpha 
Xbox 360 
PowerPC 
ARM 
x86 / 64 
SPARC TSO 
более слабое упорядочивание 
(weak model, relaxed ordering) 
Как правило сильное 
упорядочивание 
Ослабленное упорядочивание 
с поддержкой зависимостей 
по данным 
Вполне 
ослабленное 
упорядочивание 
PowerMac 
59
ˠ˶́˶̄̀˿́̐˵˿̈˹˳˱˾˹˶˳́˱˸˼˹̈˾̌̆̀́˿̇˶̂̂˿́˱̆ 
более сильное упорядочивание 
(strong model, sequential consistency) 
IBM Blue Gene 
Смартфоны 
DEC Alpha 
Xbox 360 
PowerPC 
ARM 
x86 / 64 
SPARC TSO 
более слабое упорядочивание 
(weak model, relaxed ordering) 
Как правило сильное 
упорядочивание 
Ослабленное упорядочивание 
с поддержкой зависимостей 
по данным 
Вполне 
ослабленное 
упорядочивание 
PowerMac 
При сильном 
упорядочивании 
(сильная модель памяти) 
каждая инструкция 
неявно реализует 
семантику захвата и 
освобождения. 
При слабом 
упорядочивании 
(слабая модель памяти) 
не накладывается 
никаких ограничений на 
порядок выполнения 
инструкций. 
60
ˠ˶́˶̄̀˿́̐˵˿̈˹˳˱˾˹˶˳̀́˿̇˶̂̂˿́˱̆˱́̆˹̃˶˻̃̄́̌[ 
более сильное упорядочивание 
(strong model, sequential consistency) 
IBM Blue Gene 
Смартфоны 
DEC Alpha 
Xbox 360 
PowerPC 
ARM 
x86 / 64 
SPARC TSO 
более слабое упорядочивание 
(weak model, relaxed ordering) 
Как правило сильное 
упорядочивание 
Ослабленное упорядочивание 
с поддержкой зависимостей 
по данным 
Вполне 
ослабленное 
упорядочивание 
PowerMac 
61
ˠ˶́˶̄̀˿́̐˵˿̈˹˳˱˾˹˶˳̀́˿̇˶̂̂˿́˱̆˱́̆˹̃˶˻̃̄́̌[ 
Intel Architectures Software Developer’s Manual, Vol. 3: 
▪ Операции чтения не могут быть переупорядочены с другими 
операциями чтения 
▪ Операции записи не могут быть переупорядочены с другими 
операциями записями 
▪ Операции записи не могут быть переупорядочены с другими 
операциями записи, кроме следующих исключений: … 
▪ Операции чтения могут быть переупорядочены с более 
старыми операциями записи в другие области памяти, 
но не с операциями записи в ту же область. 
▪ Операции чтения не могут перейти раньше инструкций LFENCE, 
MFENCE, операции записи - раньше инструкций LFENCE, 
SFENCE, MFENCE. 
▪ LFENCE, SFENCE, MFENCE не могут выполниться раньше 
операций чтения, записи, того или другого соответственно. 
62
ˠ˶́˶̄̀˿́̐˵˿̈˹˳˱˾˹˶˳̀́˿̇˶̂̂˿́˱̆˱́̆˹̃˶˻̃̄́̌[ 
x = 0, y = 0 
Процессор 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 = 1 
▪ r1 = 1, r2 = 0 
▪ r1 = 1, r2 = 1 
▪ r1 = 0, r2 = 0 
63
ˠ˶́˶̄̀˿́̐˵˿̈˹˳˱˾˹˶˳̀́˿̇˶̂̂˿́˱̆˱́̆˹̃˶˻̃̄́̌[ 
x = 0, y = 0 
Процессор 1 
mov x, 1 ; запись (load) 
; 1 в x 
StoreLoad 
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. 
64
ˠ˶́˶̄̀˿́̐˵˿̈˹˳˱˾˹˶˳̀́˿̇˶̂̂˿́˱̆˱́̆˹̃˶˻̃̄́̌[ 
Поток 1 
x = 1; 
asm volatile( ::: 
memory); 
r1 = y; 
Программный барьер памяти: 
запретить переупорядочивание 
инструкций компилятором. 
65
ˠ˶́˶̄̀˿́̐˵˿̈˹˳˱˾˹˶˳̀́˿̇˶̂̂˿́˱̆˱́̆˹̃˶˻̃̄́̌[ 
Поток 1 
x = 1; 
asm volatile( ::: 
memory); 
r1 = y; 
Поток 3 
if (r1 == 0  r2 == 0) 
Поток 2 
y = 1; 
asm volatile( ::: 
printf(reordering happened!n); 
memory); 
r2 = x; 
66 
Переупорядочивание 
может произойти!
ˠ˶́˶̄̀˿́̐˵˿̈˹˳˱˾˹˶˳̀́˿̇˶̂̂˿́˱̆˱́̆˹̃˶˻̃̄́̌[ 
Поток 1 
x = 1; 
asm volatile( ::: 
memory); 
r1 = y; 
Поток 3 
if (r1 == 0  r2 == 0) 
Поток 2 
y = 1; 
asm volatile( ::: 
printf(reordering happened!n); 
memory); 
r2 = x; 
67
ˠ˶́˶̄̀˿́̐˵˿̈˹˳˱˾˹˶˳̀́˿̇˶̂̂˿́˱̆˱́̆˹̃˶˻̃̄́̌[ 
Поток 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); 
68
ˠ˶́˶̄̀˿́̐˵˿̈˹˳˱˾˹˶˳̀́˿̇˶̂̂˿́˱̆˱́̆˹̃˶˻̃̄́̌[ 
Поток 1 
x = 1; 
asm volatile(mfence ::: 
memory); 
asm volatile( ::: 
memory); 
r1 = y; 
Аппаратный барьер памяти: 
запретить переупорядочивание 
инструкций процессором. 
Программный барьер памяти: 
запретить переупорядочивание 
инструкций компилятором. 
69 
mov DWORD PTR X[rip], 1 
mfence 
mov eax, DWORD PTR Y[rip] 
... 
mov DWORD PTR r1[rip], eax 
полный барьер памяти
ˠ˶́˶̄̀˿́̐˵˿̈˹˳˱˾˹˶˳̀́˿̇˶̂̂˿́˱̆˱́̆˹̃˶˻̃̄́̌[ 
Поток 1 
x = 1; 
asm volatile(mfence ::: 
memory); 
asm volatile( ::: 
memory); 
r1 = y; 
Поток 3 
if (r1 == 0  r2 == 0) 
Поток 2 
y = 1; 
asm volatile(mfence ::: 
asm volatile( ::: 
r2 = x; 
printf(reordering happened!n); 
memory); 
memory); 
Переупорядочивание 
не произойдёт! 
70
ˠ˶́˶̄̀˿́̐˵˿̈˹˳˱˾˹˶˳̀́˿̇˶̂̂˿́˱̆̂˿̂˼˱˲˼˶˾˾̌˽̄̀˿́̐˵˿̈˹˳˱˾˹˶˽ 
более сильное упорядочивание 
(strong model, sequential consistency) 
IBM Blue Gene 
Смартфоны 
DEC Alpha 
Xbox 360 
PowerPC 
ARM 
x86 / 64 
SPARC TSO 
более слабое упорядочивание 
(weak model, relaxed ordering) 
Как правило сильное 
упорядочивание 
Ослабленное упорядочивание 
с поддержкой зависимостей 
по данным 
Вполне 
ослабленное 
упорядочивание 
PowerMac 
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++; 
} 
} 
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++; // выполняется под защитой мьютекса 
flag.store(0, std::memory_order_relaxed)) 
count++; 
} 
} StoreStore 
73
ˠ˶́˶̄̀˿́̐˵˿̈˹˳˱˾˹˶˳̀́˿̇˶̂̂˿́˱̆̂˿̂˼˱˲˼˶˾˾̌˽̄̀˿́̐˵˿̈˹˳˱˾˹˶˽ 
Для ослабленного упорядочивания характерно то, что одно ядро может 
видеть изменения в общей памяти в порядке, отличном от порядка, в 
котором другое ядро вносит изменения. 
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 
запрет компиляторного 
переупорядочивания 74
ˠ˶́˶̄̀˿́̐˵˿̈˹˳˱˾˹˶˳̀́˿̇˶̂̂˿́˱̆̂˿̂˼˱˲˼˶˾˾̌˽̄̀˿́̐˵˿̈˹˳˱˾˹˶˽ 
Поток 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++; 
} 
} 
75
ˠ˶́˶̄̀˿́̐˵˿̈˹˳˱˾˹˶˳̀́˿̇˶̂̂˿́˱̆̂˿̂˼˱˲˼˶˾˾̌˽̄̀˿́̐˵˿̈˹˳˱˾˹˶˽ 
Поток 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++; 
} 
} 
76
ˠ˶́˶̄̀˿́̐˵˿̈˹˳˱˾˹˶˳̀́˿̇˶̂̂˿́˱̆̂˿̂˼˱˲˼˶˾˾̌˽̄̀˿́̐˵˿̈˹˳˱˾˹˶˽ 
Поток 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++; 
} 
} 
77
ˠ˶́˶̄̀˿́̐˵˿̈˹˳˱˾˹˶˳̀́˿̇˶̂̂˿́˱̆̂˿̂˼˱˲˼˶˾˾̌˽̄̀˿́̐˵˿̈˹˳˱˾˹˶˽ 
Допустим N = 100000 
Каждый из 2 потоков выполняет 100000 операций 
инкремента разделяемой переменной sharedCount++. 
Ожидаемое значение: sharedVal = 200000 
В реальности: 
$ ./prog 100000 // N = 100000 
sharedCount = 199348 
sharedCount = 199034 
sharedCount = 199517 
sharedCount = 199829 
sharedCount = 199113 
sharedCount = 199566 
78
ˠ˶́˶̄̀˿́̐˵˿̈˹˳˱˾˹˶̀́˹˳̌̀˿˼˾˶˾˹˹˿̀˶́˱̇˹̐˸˱̆˳˱̃˱˿̂˳˿˲˿˷˵˶˾˹̐˳ 
̀́˿̇˶̂̂˿́˱̆˱́̆˹̃˶˻̃̄́̌32:(5 
Поток 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]); 
} 
} 
Буфер 1 Буфер 2 Буфер N 
L2-кэш 
... ... 
79
ˠ˶́˶̄̀˿́̐˵˿̈˹˳˱˾˹˶̀́˹˳̌̀˿˼˾˶˾˹˹˿̀˶́˱̇˹̐˸˱̆˳˱̃˱˿̂˳˿˲˿˷˵˶˾˹̐˳ 
̀́˿̇˶̂̂˿́˱̆˱́̆˹̃˶˻̃̄́̌32:(5 
Поток 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]); 
} 
} 
Буфер 1 Буфер 2 Буфер N 
L2-кэш 
... ... 
80
ˠ˶́˶̄̀˿́̐˵˿̈˹˳˱˾˹˶̀́˹˳̌̀˿˼˾˶˾˹˹˿̀˶́˱̇˹̐˸˱̆˳˱̃˱˿̂˳˿˲˿˷˵˶˾˹̐˳ 
̀́˿̇˶̂̂˿́˱̆˱́̆˹̃˶˻̃̄́̌32:(5 
Поток 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]); 
} 
} 
Буфер 1 Буфер 2 Буфер N 
L2-кэш 
... ... 
81
ˠ˶́˶̄̀˿́̐˵˿̈˹˳˱˾˹˶̀́˹˳̌̀˿˼˾˶˾˹˹˿̀˶́˱̇˹̐˸˱̆˳˱̃˱˿̂˳˿˲˿˷˵˶˾˹̐˳ 
̀́˿̇˶̂̂˿́˱̆˱́̆˹̃˶˻̃̄́̌32:(5 
Поток 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]); 
} 
} 
Буфер 1 Буфер 2 Буфер N 
L2-кэш 
... ... 
82
ˠ˶́˶̄̀˿́̐˵˿̈˹˳˱˾˹˶̀́˹˳̌̀˿˼˾˶˾˹˹˿̀˶́˱̇˹̐˸˱̆˳˱̃˱˿̂˳˿˲˿˷˵˶˾˹̐˳ 
̀́˿̇˶̂̂˿́˱̆˱́̆˹̃˶˻̃̄́̌32:(5 
Поток 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; 
83
ˠ˶́˶̄̀˿́̐˵˿̈˹˳˱˾˹˶̀́˹˳̌̀˿˼˾˶˾˹˹˿̀˶́˱̇˹̐˸˱̆˳˱̃˱˿̂˳˿˲˿˷˵˶˾˹̐˳ 
̀́˿̇˶̂̂˿́˱̆˱́̆˹̃˶˻̃̄́̌32:(5 
Поток 1 (процессор 1) 
// Подготавливаем данные 
// и устанавливаем StoreStore 
флаг 
// готовности 
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; 
84
ˠ́˿˴́˱˽˽˾˿˶ 
̀˶́˶̄̀˿́̐˵˿̈˹˳˱˾˹˶ 
˹˾̂̃́̄˻̇˹˺ 
85
ˠ́˿˴́˱˽˽˾˿˶̀˶́˶̄̀˿́̐˵˿̈˹˳˱˾˹˶˹˾̂̃́̄˻̇˹˺ 
Первое правило робототехники компиляторов и 
процессоров при упорядочивании доступа к памяти: 
Нельзя изменять поведение 
однопоточной программы. 
▪ В однопоточных программах переупорядочивания 
остаются незамеченными. 
▪ То же самое - при многопоточном программировании 
на основе мьютексов, семафоров и т.д. 
▪ Но не при использовании атомарных переменных 
и техник программирования без блокировок. 
86
ˠ́˿˴́˱˽˽˾˿˶̀˶́˶̄̀˿́̐˵˿̈˹˳˱˾˹˶˹˾̂̃́̄˻̇˹˺ 
int x, y; 
int main() { 
x = y + 111; 
y = 222; 
printf(%d%d, x, y); 
mov eax, DWORD PTR y[rip] 
add eax, 111 
mov DWORD PTR x[rip], eax 
mov DWORD PTR y[rip], 222 
gcc -S -masm=intel prog.c 
выполнение команды 
x = y + 111 завершено 
выполнение команды 
y = 222 завершено 
87
ˠ́˿˴́˱˽˽˾˿˶̀˶́˶̄̀˿́̐˵˿̈˹˳˱˾˹˶˹˾̂̃́̄˻̇˹˺ 
int x, y; 
int main() { 
x = y + 111; 
y = 222; 
printf(%d%d, x, y); 
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 
gcc -S -masm=intel -O2 prog.c 
y = 222 выполняется 
раньше x = y + 111 
выполнение команды 
y = 222 завершено 
выполнение команды 
x = y + 111 завершено 
88
ˠ́˿˴́˱˽˽˾˿˶̀˶́˶̄̀˿́̐˵˿̈˹˳˱˾˹˶˹˾̂̃́̄˻̇˹˺ 
int x, y; 
int main() { 
x = y + 111; 
asm volatile( ::: memory); 
y = 222; 
printf(%d%d, x, y); 
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 
явный барьер 
gcc -S -masm=intel -O2 prog.c 
89
ˠ́˿˴́˱˽˽˾˿˶̀˶́˶̄̀˿́̐˵˿̈˹˳˱˾˹˶˹˾̂̃́̄˻̇˹˺˾˱̀́˹˽˶́˶ 
̂̀̄˲˼˹˻˱̇˹˶˺˵˱˾˾̌̆˿̀˶́˱̇˹̐˸˱̆˳˱̃˱˿̂˳˿˲˿˷˵˶˾˹̐
int data; 
bool isReleased = false; // данные опубликованы? 
void releaseData(int val) // опубликовать данные 
{ 
data = val; // записать данные 
isReleased = true; // данные опубликованы, 
} // можно с ними работать 
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); 
} 
1. Данные проиницаилизрованы 
92
ˠ́˿˴́˱˽˽˾˿˶̀˶́˶̄̀˿́̐˵˿̈˹˳˱˾˹˶˹˾̂̃́̄˻̇˹˺˾˱̀́˹˽˶́˶ 
̂̀̄˲˼˹˻˱̇˹˶˺˵˱˾˾̌̆˿̀˶́˱̇˹̐˸˱̆˳˱̃˱˿̂˳˿˲˿˷˵˶˾˹̐
Поток 1 
int data; 
bool isReleased = false; 
void releaseData(int val) 
{ 
data = val; 
isReleased = true; 
} 
Поток 2 
void utilizeData() 
{ 
while (!isReleased); 
doSomething(data); 
} 
1. Данные проициализированы 
2. Ура! Второй поток может 
обрабатывать 
93
ˠ́˿˴́˱˽˽˾˿˶̀˶́˶̄̀˿́̐˵˿̈˹˳˱˾˹˶˹˾̂̃́̄˻̇˹˺˾˱̀́˹˽˶́˶ 
̂̀̄˲˼˹˻˱̇˹˶˺˵˱˾˾̌̆˿̀˶́˱̇˹̐˸˱̆˳˱̃˱˿̂˳˿˲˿˷˵˶˾˹̐
Поток 1 
int data; 
bool isReleased = false; 
void releaseData(int val) 
{ 
data = val; 
isReleased = true; 
} 
Поток 2 
void utilizeData() 
{ 
while (!isReleased); 
doSomething(data); 
} 
94
ˠ́˿˴́˱˽˽˾˿˶̀˶́˶̄̀˿́̐˵˿̈˹˳˱˾˹˶˹˾̂̃́̄˻̇˹˺˾˱̀́˹˽˶́˶ 
̂̀̄˲˼˹˻˱̇˹˶˺˵˱˾˾̌̆˿̀˶́˱̇˹̐˸˱̆˳˱̃˱˿̂˳˿˲˿˷˵˶˾˹̐
Поток 1 
int data; 
bool isReleased = false; 
void releaseData(int val) 
{ 
data = val; 
isReleased = true; 
} 
Поток 2 
void utilizeData() 
{ 
while (!isReleased); 
doSomething(data); 
} 
Из-за переупорядочивания 
инструкций компилятором флаг 
выставляется до того, как 
данные готовы. 
95
ˠ́˿˴́˱˽˽˾˿˶̀˶́˶̄̀˿́̐˵˿̈˹˳˱˾˹˶˹˾̂̃́̄˻̇˹˺˾˱̀́˹˽˶́˶ 
̂̀̄˲˼˹˻˱̇˹˶˺˵˱˾˾̌̆˿̀˶́˱̇˹̐˸˱̆˳˱̃˱˿̂˳˿˲˿˷˵˶˾˹̐
Поток 1 
StoreStore 
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; 
isReleased = true; 
} 
Поток 2 
void utilizeData() 
{ 
while (!isReleased); 
doSomething(data); 
} 
Из-за переупорядочивания 
инструкций компилятором флаг 
выставляется до того, как 
данные готовы. 
97
ˠ́˿˴́˱˽˽˾˿˶̀˶́˶̄̀˿́̐˵˿̈˹˳˱˾˹˶˹˾̂̃́̄˻̇˹˺˾˱̀́˹˽˶́˶ 
̂̀̄˲˼˹˻˱̇˹˶˺˵˱˾˾̌̆˿̀˶́˱̇˹̐˸˱̆˳˱̃˱˿̂˳˿˲˿˷˵˶˾˹̐
Поток 1 
int data; 
bool isReleased = false; 
void releaseData(int val) 
{ 
data = val; 
isReleased = true; 
} 
Поток 2 
void utilizeData() 
{ 
while (!isReleased); 
doSomething(data); 
} 
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); 
} 
99
ˠ́˿˴́˱˽˽˾˿˶̀˶́˶̄̀˿́̐˵˿̈˹˳˱˾˹˶˹˾̂̃́̄˻̇˹˺˾˱̀́˹˽˶́˶ 
̂̀̄˲˼˹˻˱̇˹˶˺˵˱˾˾̌̆˿̀˶́˱̇˹̐˸˱̆˳˱̃˱˿̂˳˿˲˿˷˵˶˾˹̐
Поток 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); 
} 
Операция записи- 
освобождения 
Операция чтения- 
захвата 
100
˟̀˶́˱̇˹˹̂˿̆́˱˾˶˾˹̐˹˸̈˹̂̃˿˴˿˳˿˸˵̄̆˱RXWRIWKLQDLUVWRUHV
if (x  0) 
y++; 
register int r = y; 
if (x  0) 
r++; 
y = r; 
Размещение переменной в регистре 
в процессе оптимизирующей 
компиляции 
создание новой регистровой 
переменной 
Новая операция сохранения 
(store) “из чистого воздуха” 
Создание операций сохранения “из чистого 
воздуха” не допустимо в соответствии с 
последним стандартом, однако... 101
˟̀˶́˱̇˹˹̂˿̆́˱˾˶˾˹̐˹˸̈˹̂̃˿˴˿˳˿˸˵̄̆˱RXWRIWKLQDLUVWRUHV
однако в С  PThreads такая оптимизация допускается: 
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++; 
... 
int trylock() { 
register int r = count; 
int rc; 
rc = pthread_mutex_trylock(mutex); 
if (rc == 0) 
count++; 
count = r; 
новый store 
и новая гонка данных! 102
˒˱˸˿˳˿˶˿̃˾˿̉˶˾˹˶ 
KDSSHQVEHIRUH 
̀́˿˹̂̆˿˵˹̃́˱˾̍̉˶
103
˟̃˾˿̉˶˾˹˶KDSSHQVEHIRUH̀́˿˹̂̆˿˵˹̃́˱˾̍̉˶
Отношение happens-before определяет, какие операции видят 
последствия других операций. 
Допустим A и В - операции в многопоточной программе. Тогда если 
А происходит-раньше В, тогда эффект (результат операции, 
который отразился в памяти) операции А становится видим для 
потока, выполняющего операцию В. 
x = 10; // A 
y = x + 1; // B 
104
˟̃˾˿̉˶˾˹˶KDSSHQVEHIRUH̀́˿˹̂̆˿˵˹̃́˱˾̍̉˶
Отношение happens-before определяет, какие операции видят 
последствия других операций. 
Допустим A и В - операции в многопоточной программе. Тогда если 
А происходит-раньше В, тогда эффект (результат операции, 
который отразился в памяти) операции А становится видим для 
потока, выполняющего операцию В. 
x = 10; 
y = x + 1; 
z = sqrt(x * y); 
Все соотношения 
happens-before для 
операции А (x = 10) 
105
˟̃˾˿̉˶˾˹˶KDSSHQVEHIRUH̀́˿˹̂̆˿˵˹̃́˱˾̍̉˶
Отношение happens-before определяет, какие операции видят 
последствия других операций. 
Допустим A и В - операции в многопоточной программе. Тогда если 
А происходит-раньше В, тогда эффект (результат операции, 
который отразился в памяти) операции А становится видим для 
потока, выполняющего операцию В. 
x = 10; 
y = x + 1; 
z = sqrt(x * y); 
m = k - 5; 
print(m) 
print(x) 
106
˟̃˾˿̉˶˾˹˶KDSSHQVEHIRUH̀́˿˹̂̆˿˵˹̃́˱˾̍̉˶
Отношение happens-before определяет, какие операции видят 
последствия других операций. 
Допустим A и В - операции в многопоточной программе. Тогда если 
А происходит-раньше В, тогда эффект (результат операции, 
который отразился в памяти) операции А становится видим для 
потока, выполняющего операцию В. 
Поток 1 Поток 2 
x = 10; 
y = x + 1; 
z = sqrt(x * y); 
m = k - 5; 
print(m) 
w = x + 2 
107
ˠ́˿˹̂̆˿˵˹̃́˱˾̍̉˶Ƨ̀́˿˹̂̆˿˵˹̃́˱˾̍̉˶ 
Отношение happens-before определяет, какие операции видят 
последствия других операций. 
Допустим A и В - операции в многопоточной программе. Тогда если 
А происходит-раньше В, тогда эффект (результат операции, 
который отразился в памяти) операции А становится видим для 
потока, выполняющего операцию В. 
1. Из того, что А происходит-раньше В не следует, что А 
происходит раньше В. 
2. Из того, что А происходит раньше В не следует, что А 
происходит-раньше В 
108
ˠ́˿˹̂̆˿˵˹̃́˱˾̍̉˶˾˶˿˸˾˱̈˱˶̃̀́˿˹̂̆˿˵˹̃́˱˾̍̉˶ 
1. Из того, что А происходит-раньше В не следует, что А 
происходит раньше В. 
int x, y; 
int main() { 
x = y + 111; // A 
y = 222; // B 
printf(%d%d, x, y); 
109
ˠ́˿˹̂̆˿˵˹̃́˱˾̍̉˶˾˶˿˸˾˱̈˱˶̃̀́˿˹̂̆˿˵˹̃́˱˾̍̉˶ 
1. Из того, что А происходит-раньше В не следует, что А 
int x, y; 
int main() { 
x = y + 111; // A 
y = 222; // B 
printf(%d%d, x, y); 
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 
gcc -S -masm=intel -O2 prog.c 
происходит раньше В. 
110
ˠ́˿˹̂̆˿˵˹̃́˱˾̍̉˶˾˶˿˸˾˱̈˱˶̃̀́˿˹̂̆˿˵˹̃́˱˾̍̉˶ 
1. Из того, что А происходит-раньше В не следует, что А 
int x, y; 
int main() { 
x = y + 111; 
y = 222; 
printf(%d%d, x, y); 
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 
gcc -S -masm=intel -O2 prog.c 
y = 222 выполняется 
раньше x = y + 111 
выполнение команды 
y = 222 завершено 
выполнение команды 
x = y + 111 завершено 
происходит раньше В. 
111
ˠ́˿˹̂̆˿˵˹̃́˱˾̍̉˶˾˶˿˸˾˱̈˱˶̃̀́˿˹̂̆˿˵˹̃́˱˾̍̉˶ 
2. Из того, что А происходит раньше В не следует, что А 
происходит-раньше В 
Поток 2 
if (ready) // B 
print(x); 
int x; 
bool ready = false; 
Поток 1 
x = 42 
ready = true; // A 
112
ˠ́˿˹̂̆˿˵˹̃́˱˾̍̉˶˾˶˿˸˾˱̈˱˶̃̀́˿˹̂̆˿˵˹̃́˱˾̍̉˶ 
2. Из того, что А происходит раньше В не следует, что А 
происходит-раньше В 
Поток 2 
if (ready) // B 
print(x); 
int x; 
bool ready = false; 
Поток 1 
x = 42 
ready = true; // A 
113
ˠ́˿˹̂̆˿˵˹̃́˱˾̍̉˶˾˶˿˸˾˱̈˱˶̃̀́˿˹̂̆˿˵˹̃́˱˾̍̉˶ 
2. Из того, что А происходит раньше В не следует, что А 
происходит-раньше В 
Поток 2 
if (ready) 
print(x); 
int x; 
bool ready = false; 
Поток 1 
x = 42 
ready = true; 
114
ˠ́˿˹̂̆˿˵˹̃́˱˾̍̉˶˾˶˿˸˾˱̈˱˶̃̀́˿˹̂̆˿˵˹̃́˱˾̍̉˶ 
2. Из того, что А происходит раньше В не следует, что А 
происходит-раньше В 
Поток 2 
if (ready) 
print(x); 
int x; 
bool ready = false; 
Поток 1 
x = 42 
ready = true; 
115
ˠ́˿˹̂̆˿˵˹̃́˱˾̍̉˶Ƨ̀́˿˹̂̆˿˵˹̃́˱˾̍̉˶ 
Отношение happens-before определяет, какие операции видят 
последствия других операций. 
Допустим A и В - операции в многопоточной программе. Тогда если 
А происходит-раньше В, тогда эффект (результат операции, 
который отразился в памяти) операции А становится видим для 
потока, выполняющего операцию В. 
1. Из того, что А происходит-раньше В не следует, что А 
происходит раньше В. 
2. Из того, что А происходит раньше В не следует, что А 
происходит-раньше В 
Отношение происходит-раньше имеет место тогда (и только 
тогда), когда это определёно стандартом языка. 
116
˓˹˵̌̄̀˿́̐˵˿̈˹˳˱˾˹̐ 
˹˾̂̃́̄˻̇˹˺˳ˢ 
117
ˠ˿̂˼˶˵˿˳˱̃˶˼̍˾˱̐̂˿˴˼˱̂˿˳˱˾˾˿̂̃̍VHTXHQWLDOFRQVLVWHQF
std::atomicint 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 не сработает 
118
ˠ˿̂˼˶˵˿˳˱̃˶˼̍˾˱̐̂˿˴˼˱̂˿˳˱˾˾˿̂̃̍VHTXHQWLDOFRQVLVWHQF
std::atomicint data{0}; 
std::atomicbool 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 
119
ˠ˿̂˼˶˵˿˳˱̃˶˼̍˾˱̐̂˿˴˼˱̂˿˳˱˾˾˿̂̃̍VHTXHQWLDOFRQVLVWHQF
std::atomicint z{0}; 
std::atomicbool 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 
120
˟̂˼˱˲˼˶˾˾˿˶̄̀˿́̐˵˿̈˶˾˹˶UHOD[HGRUGHULQJ
std::atomicint 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 может 
сработать 121
˟̂˼˱˲˼˶˾˾˿˶̄̀˿́̐˵˿̈˶˾˹˶UHOD[HGRUGHULQJ
std::atomicint data{0}; 
std::atomicbool 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 не гарантируется 
122
˟̂˼˱˲˼˶˾˾˿˶̄̀˿́̐˵˿̈˶˾˹˶UHOD[HGRUGHULQJ
std::atomicint z{0}; 
std::atomicbool 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 
123
˝˶̃˱̅˿́˱̈˶˼˿˳˶˻˱̂˲˼˿˻˾˿̃˿˽ 
Иван 
34 
9 
45 
-3 
21 
097 
11 
Иван 
Значение переменной x 
x.load 
(memory_order_relaxed) 
124
˝˶̃˱̅˿́˱̈˶˼˿˳˶˻˱̂˲˼˿˻˾˿̃˿˽ 
Иван 
34 
9 
45 
-3 
21 
097 
11 
Иван 
Значение переменной x 
125 
x.load 
(memory_order_relaxed)
˝˶̃˱̅˿́˱̈˶˼˿˳˶˻˱̂˲˼˿˻˾˿̃˿˽ 
Иван 
34 
9 
45 
-3 
21 
097 
11 
Иван 
Значение переменной x 
126 
x.load 
(memory_order_relaxed) 
encore!
˝˶̃˱̅˿́˱̈˶˼˿˳˶˻˱̂˲˼˿˻˾˿̃˿˽ 
Иван 
34 
9 
45 
-3 
21 
097 
11 
Значение переменной x 
x.load 
(memory_order_relaxed) 
Иван 
127
˝˶̃˱̅˿́˱̈˶˼˿˳˶˻˱̂˲˼˿˻˾˿̃˿˽ 
Иван 
34 
9 
45 
-3 
21 
097 
11 
-8 
Иван 
Значение переменной x 
x.store(-8, 
memory_order_relaxed) 
128
˝˶̃˱̅˿́˱̈˶˼˿˳˶˻˱̂˲˼˿˻˾˿̃˿˽ 
Иван 
34 
9 
45 
-3 
21 
097 
11 
-8 
Значение переменной x 
x.load 
(memory_order_relaxed) 
Иван 
129
˝˶̃˱̅˿́˱̈˶˼˿˳˶˻˱̂˲˼˿˻˾˿̃˿˽ 
Иван 
Сергей 
Анна 
... 
34 
9 
45 
-3 
21 
097 
11 
-8 
Иван 
Анна Сергей 
Значение переменной x 
130
˒˱́̍˶́̌̀˱˽̐̃˹ 
131
˓˹˵̌̀˶́˶̄̀˿́̐˵˿̈˹˳˱˾˹˺ 
LoadLoad LoadStore 
StoreLoad StoreStore 
132
˙˶́˱́̆˹̈˶̂˻˱̐̂̃́̄˻̃̄́˱̂˿˳́˶˽˶˾˾̌̆˽˾˿˴˿̐˵˶́˾̌̆̂˹̂̃˶˽ 
кэш 2 
кэш 1 
Память 
кэш 2 
кэш 1 кэш 1 кэш 1 
Процессорные 
ядра 
133
˝˶̃˱̅˿́˱́˶̀˿˸˹̃˿́˹̐ 
1 
кэш L1 
2 
кэш L1 
Память 
134
˝˶̃˱̅˿́˱́˶̀˿˸˹̃˿́˹̐ 
1 
кэш L1 
2 
кэш L1 
Память 
mov [x], 1 
mov r1, [y] 
mov [y], 1 
mov r2, [x] 
135
˝˶̃˱̅˿́˱́˶̀˿˸˹̃˿́˹̐ 
1 
2 
Рекс 
Мухтар 
Центральный 
репозиторий 
136
˝˶̃˱̅˿́˱́˶̀˿˸˹̃˿́˹̐ 
1 
2 
Рекс 
Мухтар 
Утечка данных из 
центрального 
репозитория в 
локальный и обратно 
Центральный 
репозиторий 
137
˝˶̃˱̅˿́˱́˶̀˿˸˹̃˿́˹̐ 
1 
2 
Рекс 
Мухтар 
Утечка данных из 
центрального 
репозитория в 
локальный и обратно 
Центральный 
репозиторий 
mov [x], 1 
x = 1 
x = 0 
x = 0 
138
˝˶̃˱̅˿́˱́˶̀˿˸˹̃˿́˹̐ 
1 
2 
Рекс 
Мухтар 
Утечка данных из 
центрального 
репозитория в 
локальный и обратно 
Центральный 
репозиторий 
mov [x], 1 
x = 1 
x = 1 
x = 0 
139
˝˶̃˱̅˿́˱́˶̀˿˸˹̃˿́˹̐ 
1 
2 
Рекс 
Мухтар 
Утечка данных из 
центрального 
репозитория в 
локальный и обратно 
Центральный 
репозиторий 
mov [x], 1 
x = 1 
x = 1 
x = 1 
140
˝˶̃˱̅˿́˱́˶̀˿˸˹̃˿́˹̐ 
1 
2 
Рекс 
Мухтар 
Центральный 
репозиторий 
mov [x], 1 
x = 1 
x = ? 
x = ? 
Неизвестно, когда 
изменения распространятся 
на другие потоки. 
141
˝˶̃˱̅˿́˱́˶̀˿˸˹̃˿́˹̐ 
1 
2 
x = 0 
y = 0 
Рекс 
Мухтар 
Центральный 
репозиторий 
mov [x], 1 
mov r1, [y] 
mov [y], 1 
mov r2, [x] 
x = 0 
y = 0 
x = 0 
y = 0 
142
˝˶̃˱̅˿́˱́˶̀˿˸˹̃˿́˹̐ 
1 
2 
x = 1 
y = 0 
Рекс 
Мухтар 
Центральный 
репозиторий 
mov [x], 1 
mov r1, [y] 
mov [y], 1 
mov r2, [x] 
x = 0 
y = 1 
x = 0 
y = 0 
143
˝˶̃˱̅˿́˱́˶̀˿˸˹̃˿́˹̐ 
1 
2 
x = 1 
y = ? {0,1} 
Рекс 
Мухтар 
Центральный 
репозиторий 
mov [x], 1 
mov r1, [y] 
mov [y], 1 
mov r2, [x] 
x = ? {0,1} 
y = 1 
x = ? {0,1} 
y = ? {0,1} 
144
˓˹˵̌˲˱́̍˶́˿˳ 
LoadLoad LoadStore 
StoreLoad StoreStore 
145
˒˱́̍˶́/RDG/RDG 
1 
Рекс 
Центральный 
репозиторий 
LoadLoad 
x = 1 
y = 0 
x = 1 
y = 0 
git pull, hg pull, 
svn update, 
cvs update 
Барьер LoadLoad: 
▪ Предотвращает переупорядочивания между 
загрузками до барьера и загрузками после 
барьера. 
▪ Гарантирует, что загруженные из центрального 
репозитория (памяти) в локальный репозиторий 
(кэш) значения будут по крайней мере такие же 
новые, как и последнее значение, которое 
“просочилось” из центрального репозитория. 146
˒˱́̍˶́/RDG/RDG 
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); 
} 147
˒˱́̍˶́6WRUH6WRUH 
1 
Рекс 
Центральный 
репозиторий 
StoreStore 
x = 1 
y = 0 
Барьер StoreStore: 
▪ Предотвращает переупорядочивания между 
сохранениями до барьера и сохранениями после 
барьера. 
▪ Гарантирует, что загруженные из локального 
репозитория (кэш) в локальный репозиторий 
(память) значения будут по крайней мере такие 
же новые, как и последнее значение, которое 
“просочилось” из локального репозитория. 
x = 1 
y = 0 
git push, hg push, 
svn commit, cvs 
commit 
148
˒˱́̍˶́6WRUH6WRUH 
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, 
// которые для него подготовил Рекс 149
˒˱́̍˶́/RDG6WRUH 
1 
mov r1, [y] 
mov r2, [x] 
mov [z], 42 
mov [w], r3 
mov [v], r4 
Рекс 
Операции загрузки 
load 
Операции сохранения 
store 
Переупорядочивание LoadStore 
1. Есть набор инструкций, состоящий из операций 
сохранения и загрузки. 
150
˒˱́̍˶́/RDG6WRUH 
1 
mov r1, [y] 
mov r2, [x] 
mov [z], 42 
mov [w], r3 
mov [v], r4 
Рекс 
Операции загрузки 
load - отложены 
Операции сохранения 
store - выполнены 
Переупорядочивание LoadStore 
1. Есть набор инструкций, состоящий из операций 
сохранения и загрузки. 
2. Если Рекс встречает операцию загрузки, то он 
просматривает следующие операции сохранения, 
и если они абсолютно не связаны с текущей 
операцией загрузки, то он откладывает 
выполнение операции загрузки и в первую 
очередь выполняет операции сохранения. 151
˒˱́̍˶́/RDG6WRUH 
1 
mov r1, [y] 
mov r2, [x] 
mov [z], 42 
mov [w], r3 
mov [v], r4 
Рекс 
Будут промахи по 
кэшу... 
Будут попадания в 
кэш... 
Переупорядочивание LoadStore 
1. Есть набор инструкций, состоящий из операций 
сохранения и загрузки. 
2. Если Рекс встречает операцию загрузки, которые 
промахиваются по кэшу, то он просматривает следующие 
операции сохранения, которые попадают в кэш, и если 
они абсолютно не связаны с текущей операцией загрузки, 
то он откладывает выполнение операции загрузки и в 
первую очередь выполняет операции сохранения. 152
˒˱́̍˶́/RDG6WRUH 
1 
mov r1, [y] 
mov r2, [x] 
LoadStore 
mov [z], 42 
mov [w], r3 
mov [v], r4 
Рекс 
Будут промахи по 
кэшу... 
Будут попадания в 
кэш... 
Переупорядочивание LoadStore 
1. Есть набор инструкций, состоящий из операций 
сохранения и загрузки. 
2. Если Рекс встречает операцию загрузки, которые 
промахиваются по кэшу, то он просматривает следующие 
операции сохранения, которые попадают в кэш, и если 
они абсолютно не связаны с текущей операцией загрузки, 
то он откладывает выполнение операции загрузки и в 
первую очередь выполняет операции сохранения. 153
˒˱́̍˶́6WRUH/RDG 
1 
mov [x], 1 
mov r1, [y] 
x = 1 
y = 0 
Рекс 
2 
mov [y], 1 
mov r2, [x] 
x = 0 
y = 1 
Мухтар 
r1 = 0 
r2 = 0 
154
˒˱́̍˶́6WRUH/RDG 
1 
mov [x], 1 
StoreLoad 
mov r1, [y] 
x = 1 
y = ? 
Рекс 
2 
mov [y], 1 
StoreLoad 
mov r2, [x] 
x = ? 
y = 1 
Мухтар 
Барьер StoreLoad: 
▪ Гарантирует видимость для других 
процессоров всех операций 
сохранения, выполненных до 
барьера. 
▪ Обеспечивает для всех операций 
загрузки, выполненных после 
барьера, получение результатов, 
которые имеют место во время 
барьера. 
▪ Барьер предотвращает r1 = r2 = 0 
▪ StoreLoad ≠ StoreStore + 
LoadLoad 
155
˒˱́̍˶́6WRUH/RDG 
1 
mov [x], 1 
StoreLoad 
mov r1, [y] 
x = 1 
y = ? 
Рекс 
Центральный 
репозиторий 
Барьер StoreLoad (≠ StoreStore + LoadLoad): 
1. Отправка (push) всех изменений в центральный репозиторий. 
156
˒˱́̍˶́6WRUH/RDG 
1 
mov [x], 1 
StoreLoad 
mov r1, [y] 
x = 1 
y = ? 
Рекс 
Центральный 
репозиторий 
Барьер StoreLoad (≠ StoreStore + LoadLoad): 
1. Отправка (push) всех изменений в центральный репозиторий. 
2. Ожидание завершения выполнения операции отправки (в 
отличие от StoreStore, который может выполняться с задержкой). 
157
˒˱́̍˶́6WRUH/RDG 
1 
mov [x], 1 
StoreLoad 
mov r1, [y] 
x = 1 
y = ? 
Рекс 
Центральный 
репозиторий 
Барьер StoreLoad (≠ StoreStore + LoadLoad): 
1. Отправка (push) всех изменений в центральный репозиторий. 
2. Ожидание завершения выполнения операции отправки (в 
отличие от StoreStore, который может выполняться с задержкой). 
3. Загрузка (pull) всех последних изменений из центрального 
репозитория (в отличие от LoadLoad, который не загружает 
абсолютно последние изменения) 
158
ˢ˶˽˱˾̃˹˻˱˸˱̆˳˱̃ 
˿̂˳˿˲˿˷˵˶˾˹˶DFTXLUH 
UHOHDVHVHPDQWLFV
159
ˢ˶˽˱˾̃˹˻˱˸˱̆˳˱̃˱˹˿̂˳˿˲˿˷˵˶˾˹̐ 
Семантика захвата (acquire) 
▪ Применяется к операциям чтения или чтения- 
модификации-записи, при этом такая операция 
становится операцией чтения-захвата (read-acquire). 
▪ Предотвращает переупорядочивание инструкции чтения- 
захвата и всех следующих в программе операций чтения 
или записи. 
▪ Применяется к операциям записи или чтения- 
модификации-записи, причём такая операция становится 
операцией записи-освобождения (write-release). 
▪ Предотвращает переупорядочивание инструкции записи- 
освобождения со всеми предшествующими в программе 
операциями чтения или записи. 
Семантика освобождения (release) 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 161
ˢ˶˽˱˾̃˹˻˱˸˱̆˳˱̃˱˹˿̂˳˿˲˿˷˵˶˾˹̐ 
read-acquire 
read / write 
read / write 
read / write 
read / write 
read / write 
read / write 
read / write 
read / write 
read / write 
read / write 
write-release 162
˘˱̆˳˱̃˹˿̂˳˿˲˿˷˵˶˾˹˶˳̃˶́˽˹˾˱̆˲˱́̍˶́˿˳ 
Acquire (захват) 
LoadLoadFence LoadStoreFence 
StoreLoadFence StoreStoreFence 
Release (освобождение) 
163
˘˱̆˳˱̃˹˿̂˳˿˲˿˷˵˶˾˹˶˳̃˶́˽˹˾˱̆˲˱́̍˶́˿˳ 
read-acquire 
LoadLoadFence + LoadStoreFence 
read / write 
read / write 
read / write 
read / write 
read / write 
read / write 
read / write 
read / write 
StoreStoreFence + LoadStoreFence 
write-release 164
˘˱̆˳˱̃˹˿̂˳˿˲˿˷˵˶˾˹˶˳̃˶́˽˹˾˱̆˲˱́̍˶́˿˳ 
read-acquire 
LoadLoadFence + LoadStoreFence 
read / write 
read / write 
read / write 
read / write 
read / write 
read / write 
read / write 
read / write 
StoreStoreFence + LoadStoreFence 
write-release 165
˘˱̆˳˱̃˹˿̂˳˿˲˿˷˵˶˾˹˶˳̃˶́˽˹˾˱̆˲˱́̍˶́˿˳ 
read-acquire 
LoadLoadFence + LoadStoreFence 
read / write 
read / write 
read / write 
read / write 
read / write 
read / write 
read / write 
read / write 
StoreStoreFence + LoadStoreFence 
write-release 166
˘˱̆˳˱̃˹˿̂˳˿˲˿˷˵˶˾˹˶˳̃˶́˽˹˾˱̆˲˱́̍˶́˿˳ 
read-acquire 
LoadLoadFence + LoadStoreFence 
read / write 
read / write 
read / write 
read / write 
read / write 
read / write 
read / write 
read / write 
StoreStoreFence + LoadStoreFence 
write-release 
1 
2 
167
˘˱̆˳˱̃˹˿̂˳˿˲˿˷˵˶˾˹˶˳̃˶́˽˹˾˱̆˲˱́̍˶́˿˳ 
read-acquire 
LoadLoadFence + LoadStoreFence 
read / write 
read / write 
read / write 
read / write 
read / write 
read / write 
read / write 
read / write 
StoreStoreFence + LoadStoreFence 
write-release 
2 
1 
1 
2 
168
˘˱̆˳˱̃˹˿̂˳˿˲˿˷˵˶˾˹˶˳̃˶́˽˹˾˱̆˲˱́̍˶́˿˳ 
read-acquire 
LoadLoadFence + LoadStoreFence 
read / write 
read / write 
read / write 
read / write 
read / write 
read / write 
read / write 
read / write 
LoadStoreFence + StoreStoreFence 
write-release 169
ˢ˶˽˱˾̃˹˻˱˸˱̆˳˱̃˿̂˳˿˲˿˷˵˶˾˹˶˾˱̀́˹˽˶́˶̂̀̄˲˼˹˻˱̇˹˶˺ 
Поток 2 
ready.load( 
memory_order_acquire); 
print(x); 
int x; 
bool ready = false; 
Поток 1 
x = 42 
ready.store(true, 
memory_order_release); 
170
ˢ˶˽˱˾̃˹˻˱˸˱̆˳˱̃˿̂˳˿˲˿˷˵˶˾˹˶˾˱̀́˹˽˶́˶̂̀̄˲˼˹˻˱̇˹˶˺ 
Поток 1 
x = 42 
ready.store(true, 
memory_order_release); 
Synchronizes-with 
Поток 2 
ready.load( 
memory_order_acquire); 
print(x); 
int x; 
bool ready = false; 
Синхронизируется-с 
Всё, что 
здесь... 
… будет 
видно здесь 
171
ˢ˶˽˱˾̃˹˻˱˸˱̆˳˱̃˿̂˳˿˲˿˷˵˶˾˹˶˾˱̀́˹˽˶́˶̂̀̄˲˼˹˻˱̇˹˶˺ 
Поток 1 
x = 42 
ready.store(true, 
memory_order_release); 
Synchronizes-with 
Поток 2 
ready.load( 
memory_order_acquire); 
print(x); 
int x; 
bool ready = false; 
Синхронизируется-с 
… будет 
видно здесь 
Всё, что 
здесь... 
Операция записи- 
освобождения 
Операция чтения- 
захвата 
172
ˢ˶˽˱˾̃˹˻˱˸˱̆˳˱̃˿̂˳˿˲˿˷˵˶˾˹˶˾˱̀́˹˽˶́˶̂̀̄˲˼˹˻˱̇˹˶˺ 
Поток 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; 
173
ˢ˶˽˱˾̃˹˻˱˸˱̆˳˱̃˿̂˳˿˲˿˷˵˶˾˹˶˾˱̀́˹˽˶́˶̂̀̄˲˼˹˻˱̇˹˶˺ 
Synchronizes-with 
Поток 2 
void useWidget() { 
while (!ready.load( 
memory_order_acquire)) 
{} 
doSomething(w); 
} 
widget w; 
bool ready = false; 
Поток 1 
void initWidget(x, y, z) { 
w.x = x; 
w.y = x; 
w.z = z; 
ready.store(true, 
memory_order_release); 
} 
Синхронизируется-с 
Всё, что 
здесь... 
… будет 
видно 
здесь 
Операция записи- 
освобождения 
Операция 
чтения-захвата 
174
ˢ˶˽˱˾̃˹˻˱˸˱̆˳˱̃˿̂˳˿˲˿˷˵˶˾˹˶˾˱̀́˹˽˶́˶̂̀̄˲˼˹˻˱̇˹˶˺ 
Synchronizes-with 
Поток 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); 
} 
Синхронизируется-с 
175
ˢ˶˽˱˾̃˹˻˱˸˱̆˳˱̃˿̂˳˿˲˿˷˵˶˾˹˶˾˱̀́˹˽˶́˶̂̀̄˲˼˹˻˱̇˹˶˺ 
Synchronizes-with 
Поток 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); 
} 
Синхронизируется-с 
Операция записи- 
освобождения 
Операция 
чтения-захвата 
176
ПВТ - осень 2014 - Лекция 6 - Атомарные операции. Внеочередное выполнение инструкций. Барьеры памяти. Семантика захвата-освобождения. Модель па
ПВТ - осень 2014 - Лекция 6 - Атомарные операции. Внеочередное выполнение инструкций. Барьеры памяти. Семантика захвата-освобождения. Модель па
ПВТ - осень 2014 - Лекция 6 - Атомарные операции. Внеочередное выполнение инструкций. Барьеры памяти. Семантика захвата-освобождения. Модель па
ПВТ - осень 2014 - Лекция 6 - Атомарные операции. Внеочередное выполнение инструкций. Барьеры памяти. Семантика захвата-освобождения. Модель па
ПВТ - осень 2014 - Лекция 6 - Атомарные операции. Внеочередное выполнение инструкций. Барьеры памяти. Семантика захвата-освобождения. Модель па
ПВТ - осень 2014 - Лекция 6 - Атомарные операции. Внеочередное выполнение инструкций. Барьеры памяти. Семантика захвата-освобождения. Модель па
ПВТ - осень 2014 - Лекция 6 - Атомарные операции. Внеочередное выполнение инструкций. Барьеры памяти. Семантика захвата-освобождения. Модель па
ПВТ - осень 2014 - Лекция 6 - Атомарные операции. Внеочередное выполнение инструкций. Барьеры памяти. Семантика захвата-освобождения. Модель па
ПВТ - осень 2014 - Лекция 6 - Атомарные операции. Внеочередное выполнение инструкций. Барьеры памяти. Семантика захвата-освобождения. Модель па
ПВТ - осень 2014 - Лекция 6 - Атомарные операции. Внеочередное выполнение инструкций. Барьеры памяти. Семантика захвата-освобождения. Модель па
ПВТ - осень 2014 - Лекция 6 - Атомарные операции. Внеочередное выполнение инструкций. Барьеры памяти. Семантика захвата-освобождения. Модель па
ПВТ - осень 2014 - Лекция 6 - Атомарные операции. Внеочередное выполнение инструкций. Барьеры памяти. Семантика захвата-освобождения. Модель па
ПВТ - осень 2014 - Лекция 6 - Атомарные операции. Внеочередное выполнение инструкций. Барьеры памяти. Семантика захвата-освобождения. Модель па
ПВТ - осень 2014 - Лекция 6 - Атомарные операции. Внеочередное выполнение инструкций. Барьеры памяти. Семантика захвата-освобождения. Модель па
ПВТ - осень 2014 - Лекция 6 - Атомарные операции. Внеочередное выполнение инструкций. Барьеры памяти. Семантика захвата-освобождения. Модель па
ПВТ - осень 2014 - Лекция 6 - Атомарные операции. Внеочередное выполнение инструкций. Барьеры памяти. Семантика захвата-освобождения. Модель па
ПВТ - осень 2014 - Лекция 6 - Атомарные операции. Внеочередное выполнение инструкций. Барьеры памяти. Семантика захвата-освобождения. Модель па
ПВТ - осень 2014 - Лекция 6 - Атомарные операции. Внеочередное выполнение инструкций. Барьеры памяти. Семантика захвата-освобождения. Модель па
ПВТ - осень 2014 - Лекция 6 - Атомарные операции. Внеочередное выполнение инструкций. Барьеры памяти. Семантика захвата-освобождения. Модель па
ПВТ - осень 2014 - Лекция 6 - Атомарные операции. Внеочередное выполнение инструкций. Барьеры памяти. Семантика захвата-освобождения. Модель па
ПВТ - осень 2014 - Лекция 6 - Атомарные операции. Внеочередное выполнение инструкций. Барьеры памяти. Семантика захвата-освобождения. Модель па
ПВТ - осень 2014 - Лекция 6 - Атомарные операции. Внеочередное выполнение инструкций. Барьеры памяти. Семантика захвата-освобождения. Модель па

Contenu connexe

Tendances

Использование юнит-тестов для повышения качества разработки
Использование юнит-тестов для повышения качества разработкиИспользование юнит-тестов для повышения качества разработки
Использование юнит-тестов для повышения качества разработки
victor-yastrebov
 
Лекция 8: Многопоточное программирование: Intel Threading Building Blocks
Лекция 8: Многопоточное программирование: Intel Threading Building BlocksЛекция 8: Многопоточное программирование: Intel Threading Building Blocks
Лекция 8: Многопоточное программирование: Intel Threading Building Blocks
Mikhail Kurnosov
 
разработка серверов и серверных приложений лекция №3
разработка серверов и серверных приложений лекция №3разработка серверов и серверных приложений лекция №3
разработка серверов и серверных приложений лекция №3
Eugeniy Tyumentcev
 

Tendances (20)

ПВТ - весна 2015 - Лекция 2. POSIX Threads. Основные понятия многопоточного п...
ПВТ - весна 2015 - Лекция 2. POSIX Threads. Основные понятия многопоточного п...ПВТ - весна 2015 - Лекция 2. POSIX Threads. Основные понятия многопоточного п...
ПВТ - весна 2015 - Лекция 2. POSIX Threads. Основные понятия многопоточного п...
 
ПВТ - весна 2015 - Лекция 4. Шаблоны многопоточного программирования
ПВТ - весна 2015 - Лекция 4. Шаблоны многопоточного программированияПВТ - весна 2015 - Лекция 4. Шаблоны многопоточного программирования
ПВТ - весна 2015 - Лекция 4. Шаблоны многопоточного программирования
 
ПВТ - весна 2015 - Лекция 7. Модель памяти С++. Внеочередное выполнение инстр...
ПВТ - весна 2015 - Лекция 7. Модель памяти С++. Внеочередное выполнение инстр...ПВТ - весна 2015 - Лекция 7. Модель памяти С++. Внеочередное выполнение инстр...
ПВТ - весна 2015 - Лекция 7. Модель памяти С++. Внеочередное выполнение инстр...
 
Parallel STL
Parallel STLParallel STL
Parallel STL
 
Дмитрий Кашицын, Троллейбус из буханки: алиасинг и векторизация в LLVM
Дмитрий Кашицын, Троллейбус из буханки: алиасинг и векторизация в LLVMДмитрий Кашицын, Троллейбус из буханки: алиасинг и векторизация в LLVM
Дмитрий Кашицын, Троллейбус из буханки: алиасинг и векторизация в LLVM
 
Юрий Ефимочев, Компилируемые в реальном времени DSL для С++
Юрий Ефимочев, Компилируемые в реальном времени DSL для С++ Юрий Ефимочев, Компилируемые в реальном времени DSL для С++
Юрий Ефимочев, Компилируемые в реальном времени DSL для С++
 
Модель памяти C++ - Андрей Янковский, Яндекс
Модель памяти C++ - Андрей Янковский, ЯндексМодель памяти C++ - Андрей Янковский, Яндекс
Модель памяти C++ - Андрей Янковский, Яндекс
 
Использование юнит-тестов для повышения качества разработки
Использование юнит-тестов для повышения качества разработкиИспользование юнит-тестов для повышения качества разработки
Использование юнит-тестов для повышения качества разработки
 
ПВТ - весна 2015 - Лекция 1. Актуальность параллельных вычислений. Анализ пар...
ПВТ - весна 2015 - Лекция 1. Актуальность параллельных вычислений. Анализ пар...ПВТ - весна 2015 - Лекция 1. Актуальность параллельных вычислений. Анализ пар...
ПВТ - весна 2015 - Лекция 1. Актуальность параллельных вычислений. Анализ пар...
 
Григорий Демченко, Универсальный адаптер
Григорий Демченко, Универсальный адаптерГригорий Демченко, Универсальный адаптер
Григорий Демченко, Универсальный адаптер
 
Полухин Антон, Как делать не надо: C++ велосипедостроение для профессионалов
Полухин Антон, Как делать не надо: C++ велосипедостроение для профессионаловПолухин Антон, Как делать не надо: C++ велосипедостроение для профессионалов
Полухин Антон, Как делать не надо: C++ велосипедостроение для профессионалов
 
Григорий Демченко, Асинхронность и неблокирующая синхронизация
Григорий Демченко, Асинхронность и неблокирующая синхронизацияГригорий Демченко, Асинхронность и неблокирующая синхронизация
Григорий Демченко, Асинхронность и неблокирующая синхронизация
 
ПВТ - весна 2015 - Лекция 0. Описание курса
ПВТ - весна 2015 - Лекция 0. Описание курсаПВТ - весна 2015 - Лекция 0. Описание курса
ПВТ - весна 2015 - Лекция 0. Описание курса
 
Дракон в мешке: от LLVM к C++ и проблемам неопределенного поведения
Дракон в мешке: от LLVM к C++ и проблемам неопределенного поведенияДракон в мешке: от LLVM к C++ и проблемам неопределенного поведения
Дракон в мешке: от LLVM к C++ и проблемам неопределенного поведения
 
Догнать и перегнать boost::lexical_cast
Догнать и перегнать boost::lexical_castДогнать и перегнать boost::lexical_cast
Догнать и перегнать boost::lexical_cast
 
Лекция 8. Intel Threading Building Blocks
Лекция 8. Intel Threading Building BlocksЛекция 8. Intel Threading Building Blocks
Лекция 8. Intel Threading Building Blocks
 
Антон Полухин, Немного о Boost
Антон Полухин, Немного о BoostАнтон Полухин, Немного о Boost
Антон Полухин, Немного о Boost
 
Для чего мы делали свой акторный фреймворк и что из этого вышло?
Для чего мы делали свой акторный фреймворк и что из этого вышло?Для чего мы делали свой акторный фреймворк и что из этого вышло?
Для чего мы делали свой акторный фреймворк и что из этого вышло?
 
Лекция 8: Многопоточное программирование: Intel Threading Building Blocks
Лекция 8: Многопоточное программирование: Intel Threading Building BlocksЛекция 8: Многопоточное программирование: Intel Threading Building Blocks
Лекция 8: Многопоточное программирование: Intel Threading Building Blocks
 
разработка серверов и серверных приложений лекция №3
разработка серверов и серверных приложений лекция №3разработка серверов и серверных приложений лекция №3
разработка серверов и серверных приложений лекция №3
 

Similaire à ПВТ - осень 2014 - Лекция 6 - Атомарные операции. Внеочередное выполнение инструкций. Барьеры памяти. Семантика захвата-освобождения. Модель па

Разработка высокопроизводительных серверных приложений для Linux/Unix (Алекса...
Разработка высокопроизводительных серверных приложений для Linux/Unix (Алекса...Разработка высокопроизводительных серверных приложений для Linux/Unix (Алекса...
Разработка высокопроизводительных серверных приложений для Linux/Unix (Алекса...
Ontico
 
20090222 parallel programming_lecture01-07
20090222 parallel programming_lecture01-0720090222 parallel programming_lecture01-07
20090222 parallel programming_lecture01-07
Computer Science Club
 
статический анализ кода
статический анализ кодастатический анализ кода
статический анализ кода
Andrey Karpov
 
Tech Talks @NSU: Как приручить дракона: введение в LLVM
Tech Talks @NSU: Как приручить дракона: введение в LLVMTech Talks @NSU: Как приручить дракона: введение в LLVM
Tech Talks @NSU: Как приручить дракона: введение в LLVM
Tech Talks @NSU
 
Девять кругов ада или PostgreSQL Vacuum / Алексей Лесовский (PostgreSQL-Consu...
Девять кругов ада или PostgreSQL Vacuum / Алексей Лесовский (PostgreSQL-Consu...Девять кругов ада или PostgreSQL Vacuum / Алексей Лесовский (PostgreSQL-Consu...
Девять кругов ада или PostgreSQL Vacuum / Алексей Лесовский (PostgreSQL-Consu...
Ontico
 
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
 

Similaire à ПВТ - осень 2014 - Лекция 6 - Атомарные операции. Внеочередное выполнение инструкций. Барьеры памяти. Семантика захвата-освобождения. Модель па (20)

Что особенного в СУБД для данных в оперативной памяти / Константин Осипов (Ta...
Что особенного в СУБД для данных в оперативной памяти / Константин Осипов (Ta...Что особенного в СУБД для данных в оперативной памяти / Константин Осипов (Ta...
Что особенного в СУБД для данных в оперативной памяти / Константин Осипов (Ta...
 
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 (Алекса...
 
20090222 parallel programming_lecture01-07
20090222 parallel programming_lecture01-0720090222 parallel programming_lecture01-07
20090222 parallel programming_lecture01-07
 
Lab5
Lab5Lab5
Lab5
 
статический анализ кода
статический анализ кодастатический анализ кода
статический анализ кода
 
Статический анализ кода
Статический анализ кода Статический анализ кода
Статический анализ кода
 
9 free rtos
9 free rtos9 free rtos
9 free rtos
 
ПВТ - осень 2014 - Лекция 3 - Стандарт POSIX Threads
ПВТ - осень 2014 - Лекция 3 - Стандарт POSIX ThreadsПВТ - осень 2014 - Лекция 3 - Стандарт POSIX Threads
ПВТ - осень 2014 - Лекция 3 - Стандарт POSIX Threads
 
Tech Talks @NSU: Как приручить дракона: введение в LLVM
Tech Talks @NSU: Как приручить дракона: введение в LLVMTech Talks @NSU: Как приручить дракона: введение в LLVM
Tech Talks @NSU: Как приручить дракона: введение в LLVM
 
Как приручить дракона: введение в LLVM
Как приручить дракона: введение в LLVMКак приручить дракона: введение в LLVM
Как приручить дракона: введение в LLVM
 
Оптимизация программ для современных процессоров и Linux, Александр Крижановс...
Оптимизация программ для современных процессоров и Linux, Александр Крижановс...Оптимизация программ для современных процессоров и Linux, Александр Крижановс...
Оптимизация программ для современных процессоров и Linux, Александр Крижановс...
 
C++ exceptions
C++ exceptionsC++ exceptions
C++ exceptions
 
Девять кругов ада или PostgreSQL Vacuum / Алексей Лесовский (PostgreSQL-Consu...
Девять кругов ада или PostgreSQL Vacuum / Алексей Лесовский (PostgreSQL-Consu...Девять кругов ада или PostgreSQL Vacuum / Алексей Лесовский (PostgreSQL-Consu...
Девять кругов ада или PostgreSQL Vacuum / Алексей Лесовский (PostgreSQL-Consu...
 
Лекция 6. Стандарт OpenMP
Лекция 6. Стандарт OpenMPЛекция 6. Стандарт OpenMP
Лекция 6. Стандарт OpenMP
 
Семинар 5. Многопоточное программирование на OpenMP (часть 5)
Семинар 5. Многопоточное программирование на OpenMP (часть 5)Семинар 5. Многопоточное программирование на OpenMP (часть 5)
Семинар 5. Многопоточное программирование на OpenMP (часть 5)
 
Дмитрий Прокопцев — R-ссылки в С++11
Дмитрий Прокопцев — R-ссылки в С++11Дмитрий Прокопцев — R-ссылки в С++11
Дмитрий Прокопцев — R-ссылки в С++11
 
2012 03 14_parallel_programming_lecture05
2012 03 14_parallel_programming_lecture052012 03 14_parallel_programming_lecture05
2012 03 14_parallel_programming_lecture05
 
Как не сделать врагами архитектуру и оптимизацию, Кирилл Березин, Mail.ru Group
Как не сделать врагами архитектуру и оптимизацию, Кирилл Березин, Mail.ru GroupКак не сделать врагами архитектуру и оптимизацию, Кирилл Березин, Mail.ru Group
Как не сделать врагами архитектуру и оптимизацию, Кирилл Березин, Mail.ru Group
 
Евгений Крутько — Опыт внедрения технологий параллельных вычислений для повыш...
Евгений Крутько — Опыт внедрения технологий параллельных вычислений для повыш...Евгений Крутько — Опыт внедрения технологий параллельных вычислений для повыш...
Евгений Крутько — Опыт внедрения технологий параллельных вычислений для повыш...
 

Plus de Alexey Paznikov

Plus de Alexey Paznikov (16)

Лекция 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. Параллельные алгоритмы случайного блуж...
 
Лекция 1. Основные понятия стандарта MPI. Дифференцированные обмены
Лекция 1. Основные понятия стандарта MPI. Дифференцированные обменыЛекция 1. Основные понятия стандарта MPI. Дифференцированные обмены
Лекция 1. Основные понятия стандарта MPI. Дифференцированные обмены
 
ПВТ - осень 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
 

ПВТ - осень 2014 - Лекция 6 - Атомарные операции. Внеочередное выполнение инструкций. Барьеры памяти. Семантика захвата-освобождения. Модель па

  • 2. ˜˶˻̇˹̐ˑ̃˿˽˱́˾̌˶˿̀˶́˱̇˹˹ ˓˾˶˿̈˶́˶˵˾˿˶˳̌̀˿˼˾˶˾˹˶˹˾̂̃́̄˻̇˹˺ ˒˱́̍˶́̌̀˱˽̐̃˹ˢ˶˽˱˾̃˹˻˱˸˱̆˳˱̃˱ ˿̂˳˿˲˿˷˵˶˾˹̐˝˿˵˶˼̍̀˱˽̐̃˹ ˠ˱˸˾˹˻˿˳ˑ˼˶˻̂˶˺ˑ˼˶˻̂˱˾˵́˿˳˹̈ ˛˱̅˶˵́˱˳̌̈˹̂˼˹̃˶˼̍˾̌̆̂˹̂̃˶˽ˢ˹˲˔ˤˣ˙ ˢ˱˺̃˻̄́̂˱KWWSFSFWVLEVXWLVUXaDSD]QLNRYWHDFKLQJ ˓˿̀́˿̂̌KWWSVSLD]]DFRPVLEVXWLVUXIDOOSFWKRPH
  • 4. ˑ̃˿˽˱́˾̌˶˿̀˶́˱̇˹˹ ▪ Операция над разделяемой переменной атомарная, если она выполняется потоком за один неделимый шаг. Ни один из других потоков не может обнаружить эту переменную в промежуточном состоянии. ▪ Если операции, которые совершают потоки над раздялемыми переменными, не атомарны, то это приведёт к гонкам данных. ▪ Гонки данных являются причиной неопредлённого поведения, поскольку они приводят к частичным (фрагментированным, “разорванным”) чтениям и записям переменных. 3
  • 5. ˥́˱˴˽˶˾̃˹́˿˳˱˾˾̌˶̈̃˶˾˹̐˹˸˱̀˹̂˹ uint64_t shared; int main() { shared = 0x100000002; ... gcc -m32 -S -masm=intel -O1 prog.c mov DWORD PTR shared, 2 mov DWORD PTR shared+4, 1 запись младших 32 бит запись старших 32 бит ▪ Выполнение присваивания 64-битного целого shared = 42 на 32-разрядной архитектуре выполняется за 2 инструкции. ▪ Операция записи не атомарная. 4
  • 6. ˥́˱˴˽˶˾̃˹́˿˳˱˾˾̌˶̈̃˶˾˹̐˹˸˱̀˹̂˹ uint64_t shared; int main() { shared = 0x100000002; ... gcc -m32 -S -masm=intel -O1 prog.c mov DWORD PTR shared, 2 mov DWORD PTR shared+4, 1 1 поток запись старших 32 бит ▪ Вытеснение потока после записи младших бит приведёт к тому, что эти биты останутся в памяти и будут использованы другими потоками. ▪ На многоядерных системах даже не требуется вытеснения. 5
  • 7. ˥́˱˴˽˶˾̃˹́˿˳˱˾˾̌˶̈̃˶˾˹̐˹˸˱̀˹̂˹ uint64_t shared; int main() { shared = 0x100000002; ... gcc -m32 -S -masm=intel -O1 prog.c mov DWORD PTR shared, 2 mov DWORD PTR shared+4, 1 1 поток 2 поток ▪ Вытеснение потока после записи младших бит приведёт к тому, что эти биты останутся в памяти, а старшие будут записаны другим потоком. ▪ На многоядерных системах даже не требуется вытеснения. 6
  • 8. ˥́˱˴˽˶˾̃˹́˿˳˱˾˾̌˶̈̃˶˾˹̐˹˸˱̀˹̂˹ uint64_t shared; uint64_t getShared() { return shared; } gcc -m32 -S -masm=intel -O1 prog.c mov eax, DWORD PTR shared mov edx, DWORD PTR shared+4 ret чтение младших 32 бит чтение старших 32 бит ▪ Выполнение чтения 64-битного целого shared на 32- разрядной архитектуре выполняется за 2 инструкции. ▪ Операция чтения не атомарная. 7
  • 9. ˥́˱˴˽˶˾̃˹́˿˳˱˾˾̌˶̈̃˶˾˹̐˹˸˱̀˹̂˹ ▪ Инструкции могут быть неатомарными, даже если выполняются одной процессорной инструкцией. Например, в ARMv7 инструкция для помещения содержимого двух 32-битных регистров в один 64-битный: strd r0, r1, [r2] На некоторых процессорах эта инструкция реализуются двумя отдельными операциями сохранения. 32-битная операция mov атомарна только для выравненных данных. В остальных случаях операция неатомарная ▪ Вытеснение потоков, выполняющих данные операции, или выполнение операций в двугих потоках в многоядерных системах приводит к неопределённому поведению. 8
  • 10. ˑ̃˿˽˱́˾˿̂̃̍˳ˢ˹ˢ ▪ В языках С и С++ предполагается, что все операции неатомарны. ▪ Операции могут быть атомарными в большинстве случаев, например, операция присваивания 32-битного целого значения. ▪ Тем не менее, все инструкции должны рассматриваться как неатомарные. ▪ К счастью, в С и С++ есть набор шаблонов атомарных типов данных. 9
  • 11. ˑ̃˿˽˱́˾˿̂̃̍˳ˢ˹ˢ ▪ Атомарные операции в С++ неделимы. Из любого потока нельзя обнаружить эту операцию выполненной частично - она либо выполнена, либо невыполнена. ▪ Это позволяет избежать гонок данных. ▪ Для атомарных типов определён метод is_lock_free, позволяющий определить, являются ли операции над ним напрямую с помощью атомарных инструкций, или они эмулируются. 10
  • 12. ˑ̃˿˽˱́˾̌˶̃˹̀̌˳ˢ˹ˢ ▪ std::atomic_flag - единственный тип, который не имеет функции is_lock_free. Он предельно простой, всецело атомарный и поддерживает одну операцию: test_and_set - проверить и установить. ▪ Остальные типы определяются специализацией шаблона std::atomic, например std::atomicint и std:: atomicvoid* 11
  • 13. ˑ̃˿˽˱́˾̌˶̃˹̀̌˳ˢ˹ˢ Атомарные тип Соответствующая специализация std::atomic_bool std::atomicbool std::atomic_char std::atomicchar std::atomic_schar std::atomicsigned char std::atomic_uchar std::atomicunsigned char std::atomic_short std::atomicshort std::atomic_ushort std::atomicunsigned short std::atomic_int std::atomicint std::atomic_uint std::atomicunsigned int std::atomic_long std::atomiclong ... + пользовательские типы 12
  • 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. 13
  • 15. ˑ̃˿˽˱́˾̌˺̅˼˱˴VWGDWRPLFBIODJ std::atomic_flag должен быть проиниализирован: std::atomic_flag flag = ATOMIC_FLAG_INIT Очистить флаг (операция сохранения): установить значение false: flag.clear(std::memory_order_release); Установить значение флага в true и вернуть предыдущее значение: bool x = flag.test_and_set(); Для атомарного флага запрещены операции копирования и присваивания. 14
  • 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. Можно использовать с lock_guard и unique_guard! 15
  • 17. ˟̀˶́˱̇˹˹̂˿̆́˱˾˶˾˹̐˹˸˱˴́̄˸˻˹˱̃˿˽˱́˾̌̆̃˹̀˿˳ // Объявить переменную и проициализировать true std::atomicbool 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); 16
  • 18. ˟̀˶́˱̇˹̐q̂́˱˳˾˹̃̍˹˿˲˽˶˾̐̃̍r Операция 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); 17
  • 19. ˟̀˶́˱̇˹̐q̂́˱˳˾˹̃̍˹˿˲˽˶˾̐̃̍r compare_exchange_weak() - сохранение может не произойти, даже если текущее значение совпадает с ожидаемым. Значение переменной не изменится, функция возвращает false. Последнее возможно при отсутствии аппаратной поддержки команды сравнить-и-обменять, из-за того, что поток может быть вытеснен в середине требуемой последовательности команд (ложный отказ). Из-за возможного ложного отказа функцию compare_exchange_weak() обычно вызывают в цикле: bool expected = false; extern atomicbool b; ... while (!b.compare_exchange_weak(expected, true); 18
  • 20. ˟̀˶́˱̇˹̐q̂́˱˳˾˹̃̍˹˿˲˽˶˾̐̃̍r compare_exchange_strong() гарантирует замену переменной в случае выполнения условия. ▪ compare_exchange_strong() выгодно использовать в случае однократном выполнении операции, т.е. при необходимости заменить значение на жалаемое, и в случае, если вычисление нового значения занимает длительное время. ▪ Если функция compare_exchange вызывается в цикле, тогда предпочтительнее использовать compare_exchange_weak, чтобы избежать двойного цикла (compare_exchange_strong реализован в виде цикла на системах, которые не поддерживают атомарной операции сравнения и замены). 19
  • 21. ˑ̃˿˽˱́˾̌˺̃˹̀DWRPLF7 ! Функции для типа atomicT*: ▪ 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::atomicC* ptr(arr); C* x = ptr.fetch_add(2); assert(x == arr); assert(p.load() == some_array[2]); p--; x = p; assert(x == arr[1]); операции чтения- модификации- записи 20
  • 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^= Отсутствуют операции: ▫ умножения, деления, сдвига 21
  • 23. ˠ˿˼̍˸˿˳˱̃˶˼̍̂˻˹˶˱̃˿˽˱́˾̌˶̃˹̀̌ В качестве шаблона std::atomic может выступать тип, он должен удовлетворять требованиям ▪ В нём должен присутствовать тривиальный оператор присваивания. Нет виртуальных функций и виртуальных базовых классов, а оператор присваивания генерируется автоматически (например, memcpy) ▪ Тип должен допускать побитовое сравнение на равенство (например, с помощью memcmp) 22
  • 25. ˠ́˹˽˶́̌̀˶́˶̄̀˿́̐˵˿̈˹˳˱˾˹̐˳ˢ x = y = v = w = 0 thread1() { x = 1 v = y } thread2() { y = 1 w = x } main() { start_threads(thread1, thread2) join_all_threads() assert(v != 0 || w != 0) assert может сработать! 24
  • 26. ˠ́˹˽˶́̌̀˶́˶̄̀˿́̐˵˿̈˹˳˱˾˹̐˳ˢ ready = false producer() { data = 42 ready = true } consumer() { while (!ready) { } print(data) } data = 42 не гарантируется 25
  • 27. ˠ́˹˽˶́̌̀˶́˶̄̀˿́̐˵˿̈˹˳˱˾˹̐˳ˢ x = y = false, z = 0 thread_store_x() { x = true } thread_store_y() { y = true } thread_read_x_then_y() { while (!x) { } if (y) z++ } thread_read_y_then_x() { while (!y) { } if (x) z++ } main { run_all_threads(store_x, store_y, read_x_then_y, read_y_then_x) join_all_threads() assert(z != 0) assert может сработать! 26
  • 29. ˟˵˹˳˾̌˺˾˿˳̌˺˹̀́˶˻́˱̂˾̌˺̀˱́˱˼˼˶˼̍˾̌˺˽˹́ Выполняет ли компьютер программу, которую вы написали? 28
  • 30. ˟˵˹˳˾̌˺˾˿˳̌˺˹̀́˶˻́˱̂˾̌˺̀˱́˱˼˼˶˼̍˾̌˺˽˹́ Выполняет ли компьютер программу, которую вы написали? НЕТ 29
  • 31. ˟˵˹˳˾̌˺˾˿˳̌˺˹̀́˶˻́˱̂˾̌˺̀˱́˱˼˼˶˼̍˾̌˺˽˹́ Выполняет ли компьютер программу, которую вы написали? НЕТ Просто дело в том что... иерархическая структура памяти, внеочередное выполнение команд процессора и компиляторная оптимизация. 30
  • 32. ˑ̀̀˱́˱̃˾˿˳̌˸˳˱˾˾̌˶̃́˱˾̂̅˿́˽˱̇˹˹̀́˿˴́˱˽˽̌ кэш 2 кэш 1 Память кэш 2 кэш 1 кэш 1 кэш 1 Процессорные ядра 31
  • 33. ˑ̀̀˱́˱̃˾˿˳̌˸˳˱˾˾̌˶̃́˱˾̂̅˿́˽˱̇˹˹̀́˿˴́˱˽˽̌ кэш 2 кэш 1 Память кэш 2 кэш 1 кэш 1 кэш 1 Когерентность кэша (MESI, MOESI, MESIF) Процессорные ядра 32
  • 34. ˑ̀̀˱́˱̃˾˿˳̌˸˳˱˾˾̌˶̃́˱˾̂̅˿́˽˱̇˹˹̀́˿˴́˱˽˽̌ кэш 2 кэш 1 Память кэш 2 кэш 1 кэш 1 кэш 1 Когерентность кэша (MESI, MOESI, MESIF) Внеочередное выполнение инструкций (out-of- order execution) Процессорные ядра 33
  • 35. ˑ̀̀˱́˱̃˾˿˳̌˸˳˱˾˾̌˶̃́˱˾̂̅˿́˽˱̇˹˹̀́˿˴́˱˽˽̌ Intel 64 CISC macro-instructions Instruction Fetch PreDecode Instruction Queue (IQ) Decode Rename/Allocate Retirement Unit (Re-Order Buffer) Scheduler Reservation Stations Execution Units Intel 64 CISC macro-instr. Execution Engine (out-of-order) ITLB Instruction Cache (32KiB) L2 TLB L2 Cache (256 KiB, 8-way) DTLB Data Cache (32KiB) L3 Cache Front-End Pipeline (in-order) Nehalem RISC micro-operations ,QWHO1HKDOHPRUH 3LSHOLQH 34
  • 36. ˑ̀̀˱́˱̃˾˿˳̌˸˳˱˾˾̌˶̃́˱˾̂̅˿́˽˱̇˹˹̀́˿˴́˱˽˽̌ 16 ITLB L1 I-cache (32 KiB, 4-way) byte/cycle Instruction Fetch Unit (IFU) Pre Decode, Prefetch Buffer, Instruction Length Decoder Instruction Queue (IQ) (18 entry – 18 instruction max.) Instruction Decoding Unit (IDU) 3 simple + 1 complex Simple Complex Simple Simple micro-cod Decoded Instruction Queue (DIQ, 28 uops. max) Loop Stream Detection, Micro-Fusion, Macro- Fusion Intel64 CISC macro-instr. Nehalem RISC micro-operations 4 micro-ops. /cycle Unified L2- Cache 6 instr./cycle Branch Prediction Unit (BPU) 5 instructions/cycle 4 uops./cycle 35
  • 37. ˛˿˽̀˹˼̐̃˿́˾˱̐˿̀̃˹˽˹˸˱̇˹̐˳˹˵̌˿̀̃˹˽˹˸˱̇˹˺ ▪ Peephole-оптимизация ▪ Локальная оптимизация ▪ Внутрипроцедурная оптимизация ▪ Оптимизация циклов ▪ Межпроцедурная оптимизация 36
  • 38. ˛˿˽̀˹˼̐̃˿́˾˱̐˿̀̃˹˽˹˸˱̇˹̐˳˹˵̌˿̀̃˹˽˹˸˱̇˹˺ ▪ Peephole-оптимизация ▪ Локальная оптимизация ▪ Внутрипроцедурная оптимизация ▪ Оптимизация циклов ▫ Анализ индуктивных переменных ▫ Деление цикла на части ▫ Объединение циклов ▫ Инверсия цикла ▫ Расщепление цикла ▪ Межпроцедурная оптимизация 37
  • 39. ˛˿˽̀˹˼̐̃˿́˾˱̐˿̀̃˹˽˹˸˱̇˹̐˳˹˵̌˿̀̃˹˽˹˸˱̇˹˺ -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 38
  • 40. ˛˿˽̀˹˼̐̃˿́˾˱̐˿̀̃˹˽˹˸˱̇˹̐̀́˹˽˶́̌˿̀̃˹˽˹˸˱̇˹˺ 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; 39
  • 41. ˛˿˽̀˹˼̐̃˿́˾˱̐˿̀̃˹˽˹˸˱̇˹̐̀́˹˽˶́̌˿̀̃˹˽˹˸˱̇˹˺ 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; } 40
  • 42. ˛˿˽̀˹˼̐̃˿́˾˱̐˿̀̃˹˽˹˸˱̇˹̐̀́˹˽˶́̌˿̀̃˹˽˹˸˱̇˹˺ Что компилятор знает: Все операции с памятью совершаются в текущем потоке, что они в точности означают, и какие существуют зависимости по данным. Что компилятор не знает: Какие области памяти доступны и изменяются в разных потоках. Как решить: Сказать! Как-то пометить операции, которые выполняются с разделяемыми переменными. 41
  • 43. ˮ̃˱̀̌̃́˱˾̂̅˿́˽˱̇˹˹̀́˿˴́˱˽˽̌ Исходный код Компилятор удаление подвыражений, свёртка констант, оптимизация циклов, ... Процессор предвыборка, спекулятивное выполнение инструкций, буферизация, HTM, ... Кэш частные и общие кэши, буферы записи, ... Реальное выполнение программы “Гораздо лучше выполнять другую программу - не ту, что вы написали. Вам на самом деле даже не хочется выполнять эту вашу чушь - вы хотите запускать другую программу! Всё это делается для вашего блага.” 42
  • 44. ˮ̃˱̀̌̃́˱˾̂̅˿́˽˱̇˹˹̀́˿˴́˱˽˽̌ ▪ Как правило нельзя определить, на каком уровне произошла трансформация. ▪ Трансформации на всех уровнях эквивалентны, что позволяет рассматривать их как переупорядочивание операций загрузки (loads) и записи (stores). ▪ Необходимое условие при выполнении трансформаций - сохранение иллюзии последовательно согласованного кода. 43 Исходный код Компилятор удаление подвыражений, свёртка констант, оптимизация циклов, ... Процессор предвыборка, спекулятивное выполнение инструкций, буферизация, HTM, ... Кэш частные и общие кэши, буферы записи, ... Реальное выполнение программы
  • 46. “Результат выполнения программы такой, как если бы операции всех процессоров выполнялись последовательно и результат операции каждого отдельного процессора появлялся бы в этой последовательности в порядке, который определяется программой.” (Л. Лэмпорт, 1979) ▪ Рассмотрим многопроцессорную систему, состоящую из нескольких последовательных процессоров. ▪ Операции, выполняемые процессорами над некоторой областью памяти (страница, объект, адрес, ...), появляются в одном и том же порядке для всех процессоров, несмотря на то, что фактическая последовательность выполнения операций может быть другой. 44
  • 48. “Результат выполнения программы такой, как если бы операции всех процессоров выполнялись последовательно и результат операции каждого отдельного процессора появлялся бы в этой последовательности в порядке, который определяется программой.” (Л. Лэмпорт, 1979) ▪ Рассмотрим многопроцессорную систему, состоящую из нескольких последовательных процессоров. ▪ Операции, выполняемые процессорами над некоторой областью памяти (страница, объект, адрес, ...), появляются в одном и том же порядке для всех процессоров, несмотря на то, что фактическая последовательность выполнения операций может быть другой. И это прекрасно! 45
  • 50. “Результат выполнения программы такой, как если бы операции всех процессоров выполнялись последовательно и результат операции каждого отдельного процессора появлялся бы в этой последовательности в порядке, который определяется программой.” (Л. Лэмпорт, 1979) ▪ Рассмотрим многопроцессорную систему, состоящую из нескольких последовательных процессоров. ▪ Операции, выполняемые процессорами над некоторой областью памяти (страница, объект, адрес, ...), появляются в одном и том же порядке для всех процессоров, несмотря на то, что фактическая последовательность выполнения операций может быть другой. И это прекрасно! но...46
  • 52. … но вы этого не хотите! ▪ Скорее всего очень нерационально выполнять в точности то, что вы написали. ▪ Гораздо лучше выполнить нечто иное, которое бы работало так же, как и то, что вы написали, но выполнялось бы гораздо быстрее. Поэтому ▪ Мы (программное обеспечение ПО: компилятор и аппаратное обеспечение АО: кэш, процессор) будем это делать! ▪ А вы (программисты), в свою очередь, должны обеспечить возможность корректной трансформации и выполнения программы так, чтобы сохранялась иллюзия последовательной согласованности, включив в свою программу необходимые ограничения. 47
  • 53. ˝˿˵˶˼̍̀˱˽̐̃˹̎̃˿˵˿˴˿˳˿́ Вы обещаете Корректно реализовать синхронизацию в вашей программе (путём добавления необходимых инструкций в программу, делающих её безопасной относительно гонок) Система обещает Обеспечить иллюзию выполнения той программы, которую вы написали. (путём компиляции и выполнения) 48
  • 54. ˝˿˵˶˼̍̀˱˽̐̃˹̎̃˿˵˿˴˿˳˿́ Вы обещаете Корректно реализовать синхронизацию в вашей программе (путём добавления необходимых инструкций в программу, делающих её безопасной относительно гонок) Система обещает Обеспечить иллюзию выполнения той программы, которую вы написали. (путём компиляции и выполнения) Модель памяти определяет, какие действия вы должны совершить и как должна отреагировать система, чтобы обеспечить выполнение операций с памятью в необходимой последовательности. 49
  • 56. ˠ˶́˶̄̀˿́̐˵˿̈˹˳˱˾˹˶˾˱̀́˹˽˶́˶˱˼˴˿́˹̃˽˱˕˶˻˻˶́˱ Алгоритм Деккера позволяет решать проблему взаимного исключения и был опубликован в 1965 г. Он не приводит к взаимным исключениям (deadlock) и свободен от голодания (starvation). Поток 1 flag1 = 1; if (flag2 != 0) // ожидать освобождения // критической секции else // войти в критическую // секцию Поток 2 flag2 = 1; if (flag1 != 0) // ожидать освобождения // критической секции else // войти в критическую // секцию 51
  • 57. ˠ˶́˶̄̀˿́̐˵˿̈˹˳˱˾˹˶˾˱̀́˹˽˶́˶˱˼˴˿́˹̃˽˱˕˶˻˻˶́˱ Процессор 1 Процессор 2 flag2 = 1; if (flag1 != 0) { … } flag1 = 1; if (flag2 != 0) { … } Store Buffer Store Buffer Память: flag1 = 0, flag2 = 0 52
  • 58. ˠ˶́˶̄̀˿́̐˵˿̈˹˳˱˾˹˶˾˱̀́˹˽˶́˶˱˼˴˿́˹̃˽˱˕˶˻˻˶́˱ Процессор 1 Процессор 2 flag1 = 1; if (flag2 != 0) { … } Память: flag1 = 0, flag2 = 0 flag2 = 1; if (flag1 != 0) { … } Store Buffer flag1 = 1 Store Buffer Сохранение 1 в буфере 53
  • 59. ˠ˶́˶̄̀˿́̐˵˿̈˹˳˱˾˹˶˾˱̀́˹˽˶́˶˱˼˴˿́˹̃˽˱˕˶˻˻˶́˱ Процессор 1 Процессор 2 flag1 = 1; if (flag2 != 0) { … } Память: flag1 = 0, flag2 = 0 flag2 = 1; if (flag1 != 0) { … } Store Buffer flag1 = 1 Сохранение 1 в буфере Сохранение 1 в буфере Store Buffer flag1 = 1 54
  • 60. ˠ˶́˶̄̀˿́̐˵˿̈˹˳˱˾˹˶˾˱̀́˹˽˶́˶˱˼˴˿́˹̃˽˱˕˶˻˻˶́˱ Процессор 1 Процессор 2 flag1 = 1; if (flag2 != 0) { … } Память: flag1 = 0, flag2 = 0 flag2 = 1; if (flag1 != 0) { … } Store Buffer flag1 = 1 Сохранение 1 в буфере Сохранение 1 в буфере Store Buffer flag1 = 1 Чтение 0 для flag2 Чтение 0 для flag1 StoreLoad 55
  • 61. ˠ˶́˶̄̀˿́̐˵˿̈˹˳˱˾˹˶˾˱̀́˹˽˶́˶˱˼˴˿́˹̃˽˱˕˶˻˻˶́˱ Процессор 1 Процессор 2 flag1 = 1; if (flag2 != 0) { … } Память: flag1 = 0, flag2 = 0 flag2 = 1; if (flag1 != 0) { … } Store Buffer flag1 = 1 Сохранение 1 в буфере Сохранение 1 в буфере Store Buffer flag1 = 1 Чтение 0 для flag2 Чтение 0 для flag1 56
  • 62. ˠ˶́˶̄̀˿́̐˵˿̈˹˳˱˾˹˶˾˱̀́˹˽˶́˶˱˼˴˿́˹̃˽˱˕˶˻˻˶́˱ Процессор 1 Процессор 2 flag1 = 1; if (flag2 != 0) { … } Память: flag1 = 1, flag2 = 1 flag2 = 1; if (flag1 != 0) { … } Store Buffer flag1 = 1 Сохранение 1 в буфере Сохранение 1 в буфере Store Buffer flag1 = 1 Чтение 0 для flag2 Чтение 0 для flag1 Сброс буфера (flag1) Сброс буфера (flag2) 57
  • 63. ˠ˶́˶̄̀˿́̐˵˿̈˹˳˱˾˹˶˳́˱˸˼˹̈˾̌̆̀́˿̇˶̂̂˿́˱̆ ˣ˹̀ ̀˶́˶̄̀˿́̐ ˵˿̈˹˳˱˾˹̐ ˱́̆˹̃˶˻̃̄́̌ $OSKD $50Y 32:(5 63$5 502 63$5 362 63$5 762 [ $0' ,$ /RDGV̀˿̂˼˶ ORDGV /RDGV̀˿̂˼˶ VWRUHV 6WRUHV̀˿̂˼˶ VWRUHV 6WRUHV̀˿̂˼˶ ORDGV ˑ˟̂ORDGV ˑ˟̂VWRUHV ˘˱˳˹̂˹˽̌˶ ORDGV АО - атомарные операции 58
  • 64. ˠ˶́˶̄̀˿́̐˵˿̈˹˳˱˾˹˶˳́˱˸˼˹̈˾̌̆̀́˿̇˶̂̂˿́˱̆ более сильное упорядочивание (strong model, sequential consistency) IBM Blue Gene Смартфоны DEC Alpha Xbox 360 PowerPC ARM x86 / 64 SPARC TSO более слабое упорядочивание (weak model, relaxed ordering) Как правило сильное упорядочивание Ослабленное упорядочивание с поддержкой зависимостей по данным Вполне ослабленное упорядочивание PowerMac 59
  • 65. ˠ˶́˶̄̀˿́̐˵˿̈˹˳˱˾˹˶˳́˱˸˼˹̈˾̌̆̀́˿̇˶̂̂˿́˱̆ более сильное упорядочивание (strong model, sequential consistency) IBM Blue Gene Смартфоны DEC Alpha Xbox 360 PowerPC ARM x86 / 64 SPARC TSO более слабое упорядочивание (weak model, relaxed ordering) Как правило сильное упорядочивание Ослабленное упорядочивание с поддержкой зависимостей по данным Вполне ослабленное упорядочивание PowerMac При сильном упорядочивании (сильная модель памяти) каждая инструкция неявно реализует семантику захвата и освобождения. При слабом упорядочивании (слабая модель памяти) не накладывается никаких ограничений на порядок выполнения инструкций. 60
  • 66. ˠ˶́˶̄̀˿́̐˵˿̈˹˳˱˾˹˶˳̀́˿̇˶̂̂˿́˱̆˱́̆˹̃˶˻̃̄́̌[ более сильное упорядочивание (strong model, sequential consistency) IBM Blue Gene Смартфоны DEC Alpha Xbox 360 PowerPC ARM x86 / 64 SPARC TSO более слабое упорядочивание (weak model, relaxed ordering) Как правило сильное упорядочивание Ослабленное упорядочивание с поддержкой зависимостей по данным Вполне ослабленное упорядочивание PowerMac 61
  • 67. ˠ˶́˶̄̀˿́̐˵˿̈˹˳˱˾˹˶˳̀́˿̇˶̂̂˿́˱̆˱́̆˹̃˶˻̃̄́̌[ Intel Architectures Software Developer’s Manual, Vol. 3: ▪ Операции чтения не могут быть переупорядочены с другими операциями чтения ▪ Операции записи не могут быть переупорядочены с другими операциями записями ▪ Операции записи не могут быть переупорядочены с другими операциями записи, кроме следующих исключений: … ▪ Операции чтения могут быть переупорядочены с более старыми операциями записи в другие области памяти, но не с операциями записи в ту же область. ▪ Операции чтения не могут перейти раньше инструкций LFENCE, MFENCE, операции записи - раньше инструкций LFENCE, SFENCE, MFENCE. ▪ LFENCE, SFENCE, MFENCE не могут выполниться раньше операций чтения, записи, того или другого соответственно. 62
  • 68. ˠ˶́˶̄̀˿́̐˵˿̈˹˳˱˾˹˶˳̀́˿̇˶̂̂˿́˱̆˱́̆˹̃˶˻̃̄́̌[ x = 0, y = 0 Процессор 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 = 1 ▪ r1 = 1, r2 = 0 ▪ r1 = 1, r2 = 1 ▪ r1 = 0, r2 = 0 63
  • 69. ˠ˶́˶̄̀˿́̐˵˿̈˹˳˱˾˹˶˳̀́˿̇˶̂̂˿́˱̆˱́̆˹̃˶˻̃̄́̌[ x = 0, y = 0 Процессор 1 mov x, 1 ; запись (load) ; 1 в x StoreLoad 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. 64
  • 70. ˠ˶́˶̄̀˿́̐˵˿̈˹˳˱˾˹˶˳̀́˿̇˶̂̂˿́˱̆˱́̆˹̃˶˻̃̄́̌[ Поток 1 x = 1; asm volatile( ::: memory); r1 = y; Программный барьер памяти: запретить переупорядочивание инструкций компилятором. 65
  • 71. ˠ˶́˶̄̀˿́̐˵˿̈˹˳˱˾˹˶˳̀́˿̇˶̂̂˿́˱̆˱́̆˹̃˶˻̃̄́̌[ Поток 1 x = 1; asm volatile( ::: memory); r1 = y; Поток 3 if (r1 == 0 r2 == 0) Поток 2 y = 1; asm volatile( ::: printf(reordering happened!n); memory); r2 = x; 66 Переупорядочивание может произойти!
  • 72. ˠ˶́˶̄̀˿́̐˵˿̈˹˳˱˾˹˶˳̀́˿̇˶̂̂˿́˱̆˱́̆˹̃˶˻̃̄́̌[ Поток 1 x = 1; asm volatile( ::: memory); r1 = y; Поток 3 if (r1 == 0 r2 == 0) Поток 2 y = 1; asm volatile( ::: printf(reordering happened!n); memory); r2 = x; 67
  • 73. ˠ˶́˶̄̀˿́̐˵˿̈˹˳˱˾˹˶˳̀́˿̇˶̂̂˿́˱̆˱́̆˹̃˶˻̃̄́̌[ Поток 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); 68
  • 74. ˠ˶́˶̄̀˿́̐˵˿̈˹˳˱˾˹˶˳̀́˿̇˶̂̂˿́˱̆˱́̆˹̃˶˻̃̄́̌[ Поток 1 x = 1; asm volatile(mfence ::: memory); asm volatile( ::: memory); r1 = y; Аппаратный барьер памяти: запретить переупорядочивание инструкций процессором. Программный барьер памяти: запретить переупорядочивание инструкций компилятором. 69 mov DWORD PTR X[rip], 1 mfence mov eax, DWORD PTR Y[rip] ... mov DWORD PTR r1[rip], eax полный барьер памяти
  • 75. ˠ˶́˶̄̀˿́̐˵˿̈˹˳˱˾˹˶˳̀́˿̇˶̂̂˿́˱̆˱́̆˹̃˶˻̃̄́̌[ Поток 1 x = 1; asm volatile(mfence ::: memory); asm volatile( ::: memory); r1 = y; Поток 3 if (r1 == 0 r2 == 0) Поток 2 y = 1; asm volatile(mfence ::: asm volatile( ::: r2 = x; printf(reordering happened!n); memory); memory); Переупорядочивание не произойдёт! 70
  • 76. ˠ˶́˶̄̀˿́̐˵˿̈˹˳˱˾˹˶˳̀́˿̇˶̂̂˿́˱̆̂˿̂˼˱˲˼˶˾˾̌˽̄̀˿́̐˵˿̈˹˳˱˾˹˶˽ более сильное упорядочивание (strong model, sequential consistency) IBM Blue Gene Смартфоны DEC Alpha Xbox 360 PowerPC ARM x86 / 64 SPARC TSO более слабое упорядочивание (weak model, relaxed ordering) Как правило сильное упорядочивание Ослабленное упорядочивание с поддержкой зависимостей по данным Вполне ослабленное упорядочивание PowerMac 71
  • 77. ˠ˶́˶̄̀˿́̐˵˿̈˹˳˱˾˹˶˳̀́˿̇˶̂̂˿́˱̆̂˿̂˼˱˲˼˶˾˾̌˽̄̀˿́̐˵˿̈˹˳˱˾˹˶˽ Для ослабленного упорядочивания характерно то, что одно ядро может видеть изменения в общей памяти в порядке, отличном от порядка, в котором другое ядро вносит изменения. 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++; } } 72
  • 78. ˠ˶́˶̄̀˿́̐˵˿̈˹˳˱˾˹˶˳̀́˿̇˶̂̂˿́˱̆̂˿̂˼˱˲˼˶˾˾̌˽̄̀˿́̐˵˿̈˹˳˱˾˹˶˽ Для ослабленного упорядочивания характерно то, что одно ядро может видеть изменения в общей памяти в порядке, отличном от порядка, в котором другое ядро вносит изменения. 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 73
  • 79. ˠ˶́˶̄̀˿́̐˵˿̈˹˳˱˾˹˶˳̀́˿̇˶̂̂˿́˱̆̂˿̂˼˱˲˼˶˾˾̌˽̄̀˿́̐˵˿̈˹˳˱˾˹˶˽ Для ослабленного упорядочивания характерно то, что одно ядро может видеть изменения в общей памяти в порядке, отличном от порядка, в котором другое ядро вносит изменения. 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 запрет компиляторного переупорядочивания 74
  • 80. ˠ˶́˶̄̀˿́̐˵˿̈˹˳˱˾˹˶˳̀́˿̇˶̂̂˿́˱̆̂˿̂˼˱˲˼˶˾˾̌˽̄̀˿́̐˵˿̈˹˳˱˾˹˶˽ Поток 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++; } } 75
  • 81. ˠ˶́˶̄̀˿́̐˵˿̈˹˳˱˾˹˶˳̀́˿̇˶̂̂˿́˱̆̂˿̂˼˱˲˼˶˾˾̌˽̄̀˿́̐˵˿̈˹˳˱˾˹˶˽ Поток 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++; } } 76
  • 82. ˠ˶́˶̄̀˿́̐˵˿̈˹˳˱˾˹˶˳̀́˿̇˶̂̂˿́˱̆̂˿̂˼˱˲˼˶˾˾̌˽̄̀˿́̐˵˿̈˹˳˱˾˹˶˽ Поток 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++; } } 77
  • 83. ˠ˶́˶̄̀˿́̐˵˿̈˹˳˱˾˹˶˳̀́˿̇˶̂̂˿́˱̆̂˿̂˼˱˲˼˶˾˾̌˽̄̀˿́̐˵˿̈˹˳˱˾˹˶˽ Допустим N = 100000 Каждый из 2 потоков выполняет 100000 операций инкремента разделяемой переменной sharedCount++. Ожидаемое значение: sharedVal = 200000 В реальности: $ ./prog 100000 // N = 100000 sharedCount = 199348 sharedCount = 199034 sharedCount = 199517 sharedCount = 199829 sharedCount = 199113 sharedCount = 199566 78
  • 84. ˠ˶́˶̄̀˿́̐˵˿̈˹˳˱˾˹˶̀́˹˳̌̀˿˼˾˶˾˹˹˿̀˶́˱̇˹̐˸˱̆˳˱̃˱˿̂˳˿˲˿˷˵˶˾˹̐˳ ̀́˿̇˶̂̂˿́˱̆˱́̆˹̃˶˻̃̄́̌32:(5 Поток 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]); } } Буфер 1 Буфер 2 Буфер N L2-кэш ... ... 79
  • 85. ˠ˶́˶̄̀˿́̐˵˿̈˹˳˱˾˹˶̀́˹˳̌̀˿˼˾˶˾˹˹˿̀˶́˱̇˹̐˸˱̆˳˱̃˱˿̂˳˿˲˿˷˵˶˾˹̐˳ ̀́˿̇˶̂̂˿́˱̆˱́̆˹̃˶˻̃̄́̌32:(5 Поток 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]); } } Буфер 1 Буфер 2 Буфер N L2-кэш ... ... 80
  • 86. ˠ˶́˶̄̀˿́̐˵˿̈˹˳˱˾˹˶̀́˹˳̌̀˿˼˾˶˾˹˹˿̀˶́˱̇˹̐˸˱̆˳˱̃˱˿̂˳˿˲˿˷˵˶˾˹̐˳ ̀́˿̇˶̂̂˿́˱̆˱́̆˹̃˶˻̃̄́̌32:(5 Поток 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]); } } Буфер 1 Буфер 2 Буфер N L2-кэш ... ... 81
  • 87. ˠ˶́˶̄̀˿́̐˵˿̈˹˳˱˾˹˶̀́˹˳̌̀˿˼˾˶˾˹˹˿̀˶́˱̇˹̐˸˱̆˳˱̃˱˿̂˳˿˲˿˷˵˶˾˹̐˳ ̀́˿̇˶̂̂˿́˱̆˱́̆˹̃˶˻̃̄́̌32:(5 Поток 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]); } } Буфер 1 Буфер 2 Буфер N L2-кэш ... ... 82
  • 88. ˠ˶́˶̄̀˿́̐˵˿̈˹˳˱˾˹˶̀́˹˳̌̀˿˼˾˶˾˹˹˿̀˶́˱̇˹̐˸˱̆˳˱̃˱˿̂˳˿˲˿˷˵˶˾˹̐˳ ̀́˿̇˶̂̂˿́˱̆˱́̆˹̃˶˻̃̄́̌32:(5 Поток 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; 83
  • 89. ˠ˶́˶̄̀˿́̐˵˿̈˹˳˱˾˹˶̀́˹˳̌̀˿˼˾˶˾˹˹˿̀˶́˱̇˹̐˸˱̆˳˱̃˱˿̂˳˿˲˿˷˵˶˾˹̐˳ ̀́˿̇˶̂̂˿́˱̆˱́̆˹̃˶˻̃̄́̌32:(5 Поток 1 (процессор 1) // Подготавливаем данные // и устанавливаем StoreStore флаг // готовности 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; 84
  • 91. ˠ́˿˴́˱˽˽˾˿˶̀˶́˶̄̀˿́̐˵˿̈˹˳˱˾˹˶˹˾̂̃́̄˻̇˹˺ Первое правило робототехники компиляторов и процессоров при упорядочивании доступа к памяти: Нельзя изменять поведение однопоточной программы. ▪ В однопоточных программах переупорядочивания остаются незамеченными. ▪ То же самое - при многопоточном программировании на основе мьютексов, семафоров и т.д. ▪ Но не при использовании атомарных переменных и техник программирования без блокировок. 86
  • 92. ˠ́˿˴́˱˽˽˾˿˶̀˶́˶̄̀˿́̐˵˿̈˹˳˱˾˹˶˹˾̂̃́̄˻̇˹˺ int x, y; int main() { x = y + 111; y = 222; printf(%d%d, x, y); mov eax, DWORD PTR y[rip] add eax, 111 mov DWORD PTR x[rip], eax mov DWORD PTR y[rip], 222 gcc -S -masm=intel prog.c выполнение команды x = y + 111 завершено выполнение команды y = 222 завершено 87
  • 93. ˠ́˿˴́˱˽˽˾˿˶̀˶́˶̄̀˿́̐˵˿̈˹˳˱˾˹˶˹˾̂̃́̄˻̇˹˺ int x, y; int main() { x = y + 111; y = 222; printf(%d%d, x, y); 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 gcc -S -masm=intel -O2 prog.c y = 222 выполняется раньше x = y + 111 выполнение команды y = 222 завершено выполнение команды x = y + 111 завершено 88
  • 94. ˠ́˿˴́˱˽˽˾˿˶̀˶́˶̄̀˿́̐˵˿̈˹˳˱˾˹˶˹˾̂̃́̄˻̇˹˺ int x, y; int main() { x = y + 111; asm volatile( ::: memory); y = 222; printf(%d%d, x, y); 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 явный барьер gcc -S -masm=intel -O2 prog.c 89
  • 96. int data; bool isReleased = false; // данные опубликованы? void releaseData(int val) // опубликовать данные { data = val; // записать данные isReleased = true; // данные опубликованы, } // можно с ними работать 90
  • 98. Поток 1 int data; bool isReleased = false; void releaseData(int val) { data = val; isReleased = true; } Поток 2 void utilizeData() { while (!isReleased); doSomething(data); } Данные должны быть проинициализированы в 1 потоке перед тем, как они будут использованы во 2 потоке. 91
  • 100. Поток 1 int data; bool isReleased = false; void releaseData(int val) { data = val; isReleased = true; } Поток 2 void utilizeData() { while (!isReleased); doSomething(data); } 1. Данные проиницаилизрованы 92
  • 102. Поток 1 int data; bool isReleased = false; void releaseData(int val) { data = val; isReleased = true; } Поток 2 void utilizeData() { while (!isReleased); doSomething(data); } 1. Данные проициализированы 2. Ура! Второй поток может обрабатывать 93
  • 104. Поток 1 int data; bool isReleased = false; void releaseData(int val) { data = val; isReleased = true; } Поток 2 void utilizeData() { while (!isReleased); doSomething(data); } 94
  • 106. Поток 1 int data; bool isReleased = false; void releaseData(int val) { data = val; isReleased = true; } Поток 2 void utilizeData() { while (!isReleased); doSomething(data); } Из-за переупорядочивания инструкций компилятором флаг выставляется до того, как данные готовы. 95
  • 108. Поток 1 StoreStore int data; bool isReleased = false; void releaseData(int val) { data = val; isReleased = true; } Поток 2 void utilizeData() { while (!isReleased); doSomething(data); } Из-за переупорядочивания инструкций компилятором флаг выставляется до того, как данные готовы. 96
  • 110. Поток 1 int data; bool isReleased = false; void releaseData(int val) { data = val; isReleased = true; } Поток 2 void utilizeData() { while (!isReleased); doSomething(data); } Из-за переупорядочивания инструкций компилятором флаг выставляется до того, как данные готовы. 97
  • 112. Поток 1 int data; bool isReleased = false; void releaseData(int val) { data = val; isReleased = true; } Поток 2 void utilizeData() { while (!isReleased); doSomething(data); } 98
  • 114. Поток 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); } 99
  • 116. Поток 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); } Операция записи- освобождения Операция чтения- захвата 100
  • 118. if (x 0) y++; register int r = y; if (x 0) r++; y = r; Размещение переменной в регистре в процессе оптимизирующей компиляции создание новой регистровой переменной Новая операция сохранения (store) “из чистого воздуха” Создание операций сохранения “из чистого воздуха” не допустимо в соответствии с последним стандартом, однако... 101
  • 120. однако в С PThreads такая оптимизация допускается: 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++; ... int trylock() { register int r = count; int rc; rc = pthread_mutex_trylock(mutex); if (rc == 0) count++; count = r; новый store и новая гонка данных! 102
  • 122. 103
  • 124. Отношение happens-before определяет, какие операции видят последствия других операций. Допустим A и В - операции в многопоточной программе. Тогда если А происходит-раньше В, тогда эффект (результат операции, который отразился в памяти) операции А становится видим для потока, выполняющего операцию В. x = 10; // A y = x + 1; // B 104
  • 126. Отношение happens-before определяет, какие операции видят последствия других операций. Допустим A и В - операции в многопоточной программе. Тогда если А происходит-раньше В, тогда эффект (результат операции, который отразился в памяти) операции А становится видим для потока, выполняющего операцию В. x = 10; y = x + 1; z = sqrt(x * y); Все соотношения happens-before для операции А (x = 10) 105
  • 128. Отношение happens-before определяет, какие операции видят последствия других операций. Допустим A и В - операции в многопоточной программе. Тогда если А происходит-раньше В, тогда эффект (результат операции, который отразился в памяти) операции А становится видим для потока, выполняющего операцию В. x = 10; y = x + 1; z = sqrt(x * y); m = k - 5; print(m) print(x) 106
  • 130. Отношение happens-before определяет, какие операции видят последствия других операций. Допустим A и В - операции в многопоточной программе. Тогда если А происходит-раньше В, тогда эффект (результат операции, который отразился в памяти) операции А становится видим для потока, выполняющего операцию В. Поток 1 Поток 2 x = 10; y = x + 1; z = sqrt(x * y); m = k - 5; print(m) w = x + 2 107
  • 131. ˠ́˿˹̂̆˿˵˹̃́˱˾̍̉˶Ƨ̀́˿˹̂̆˿˵˹̃́˱˾̍̉˶ Отношение happens-before определяет, какие операции видят последствия других операций. Допустим A и В - операции в многопоточной программе. Тогда если А происходит-раньше В, тогда эффект (результат операции, который отразился в памяти) операции А становится видим для потока, выполняющего операцию В. 1. Из того, что А происходит-раньше В не следует, что А происходит раньше В. 2. Из того, что А происходит раньше В не следует, что А происходит-раньше В 108
  • 132. ˠ́˿˹̂̆˿˵˹̃́˱˾̍̉˶˾˶˿˸˾˱̈˱˶̃̀́˿˹̂̆˿˵˹̃́˱˾̍̉˶ 1. Из того, что А происходит-раньше В не следует, что А происходит раньше В. int x, y; int main() { x = y + 111; // A y = 222; // B printf(%d%d, x, y); 109
  • 133. ˠ́˿˹̂̆˿˵˹̃́˱˾̍̉˶˾˶˿˸˾˱̈˱˶̃̀́˿˹̂̆˿˵˹̃́˱˾̍̉˶ 1. Из того, что А происходит-раньше В не следует, что А int x, y; int main() { x = y + 111; // A y = 222; // B printf(%d%d, x, y); 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 gcc -S -masm=intel -O2 prog.c происходит раньше В. 110
  • 134. ˠ́˿˹̂̆˿˵˹̃́˱˾̍̉˶˾˶˿˸˾˱̈˱˶̃̀́˿˹̂̆˿˵˹̃́˱˾̍̉˶ 1. Из того, что А происходит-раньше В не следует, что А int x, y; int main() { x = y + 111; y = 222; printf(%d%d, x, y); 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 gcc -S -masm=intel -O2 prog.c y = 222 выполняется раньше x = y + 111 выполнение команды y = 222 завершено выполнение команды x = y + 111 завершено происходит раньше В. 111
  • 135. ˠ́˿˹̂̆˿˵˹̃́˱˾̍̉˶˾˶˿˸˾˱̈˱˶̃̀́˿˹̂̆˿˵˹̃́˱˾̍̉˶ 2. Из того, что А происходит раньше В не следует, что А происходит-раньше В Поток 2 if (ready) // B print(x); int x; bool ready = false; Поток 1 x = 42 ready = true; // A 112
  • 136. ˠ́˿˹̂̆˿˵˹̃́˱˾̍̉˶˾˶˿˸˾˱̈˱˶̃̀́˿˹̂̆˿˵˹̃́˱˾̍̉˶ 2. Из того, что А происходит раньше В не следует, что А происходит-раньше В Поток 2 if (ready) // B print(x); int x; bool ready = false; Поток 1 x = 42 ready = true; // A 113
  • 137. ˠ́˿˹̂̆˿˵˹̃́˱˾̍̉˶˾˶˿˸˾˱̈˱˶̃̀́˿˹̂̆˿˵˹̃́˱˾̍̉˶ 2. Из того, что А происходит раньше В не следует, что А происходит-раньше В Поток 2 if (ready) print(x); int x; bool ready = false; Поток 1 x = 42 ready = true; 114
  • 138. ˠ́˿˹̂̆˿˵˹̃́˱˾̍̉˶˾˶˿˸˾˱̈˱˶̃̀́˿˹̂̆˿˵˹̃́˱˾̍̉˶ 2. Из того, что А происходит раньше В не следует, что А происходит-раньше В Поток 2 if (ready) print(x); int x; bool ready = false; Поток 1 x = 42 ready = true; 115
  • 139. ˠ́˿˹̂̆˿˵˹̃́˱˾̍̉˶Ƨ̀́˿˹̂̆˿˵˹̃́˱˾̍̉˶ Отношение happens-before определяет, какие операции видят последствия других операций. Допустим A и В - операции в многопоточной программе. Тогда если А происходит-раньше В, тогда эффект (результат операции, который отразился в памяти) операции А становится видим для потока, выполняющего операцию В. 1. Из того, что А происходит-раньше В не следует, что А происходит раньше В. 2. Из того, что А происходит раньше В не следует, что А происходит-раньше В Отношение происходит-раньше имеет место тогда (и только тогда), когда это определёно стандартом языка. 116
  • 142. std::atomicint 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 не сработает 118
  • 144. std::atomicint data{0}; std::atomicbool 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 119
  • 146. std::atomicint z{0}; std::atomicbool 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 120
  • 148. std::atomicint 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 может сработать 121
  • 150. std::atomicint data{0}; std::atomicbool 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 не гарантируется 122
  • 152. std::atomicint z{0}; std::atomicbool 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 123
  • 153. ˝˶̃˱̅˿́˱̈˶˼˿˳˶˻˱̂˲˼˿˻˾˿̃˿˽ Иван 34 9 45 -3 21 097 11 Иван Значение переменной x x.load (memory_order_relaxed) 124
  • 154. ˝˶̃˱̅˿́˱̈˶˼˿˳˶˻˱̂˲˼˿˻˾˿̃˿˽ Иван 34 9 45 -3 21 097 11 Иван Значение переменной x 125 x.load (memory_order_relaxed)
  • 155. ˝˶̃˱̅˿́˱̈˶˼˿˳˶˻˱̂˲˼˿˻˾˿̃˿˽ Иван 34 9 45 -3 21 097 11 Иван Значение переменной x 126 x.load (memory_order_relaxed) encore!
  • 156. ˝˶̃˱̅˿́˱̈˶˼˿˳˶˻˱̂˲˼˿˻˾˿̃˿˽ Иван 34 9 45 -3 21 097 11 Значение переменной x x.load (memory_order_relaxed) Иван 127
  • 157. ˝˶̃˱̅˿́˱̈˶˼˿˳˶˻˱̂˲˼˿˻˾˿̃˿˽ Иван 34 9 45 -3 21 097 11 -8 Иван Значение переменной x x.store(-8, memory_order_relaxed) 128
  • 158. ˝˶̃˱̅˿́˱̈˶˼˿˳˶˻˱̂˲˼˿˻˾˿̃˿˽ Иван 34 9 45 -3 21 097 11 -8 Значение переменной x x.load (memory_order_relaxed) Иван 129
  • 159. ˝˶̃˱̅˿́˱̈˶˼˿˳˶˻˱̂˲˼˿˻˾˿̃˿˽ Иван Сергей Анна ... 34 9 45 -3 21 097 11 -8 Иван Анна Сергей Значение переменной x 130
  • 162. ˙˶́˱́̆˹̈˶̂˻˱̐̂̃́̄˻̃̄́˱̂˿˳́˶˽˶˾˾̌̆˽˾˿˴˿̐˵˶́˾̌̆̂˹̂̃˶˽ кэш 2 кэш 1 Память кэш 2 кэш 1 кэш 1 кэш 1 Процессорные ядра 133
  • 163. ˝˶̃˱̅˿́˱́˶̀˿˸˹̃˿́˹̐ 1 кэш L1 2 кэш L1 Память 134
  • 164. ˝˶̃˱̅˿́˱́˶̀˿˸˹̃˿́˹̐ 1 кэш L1 2 кэш L1 Память mov [x], 1 mov r1, [y] mov [y], 1 mov r2, [x] 135
  • 165. ˝˶̃˱̅˿́˱́˶̀˿˸˹̃˿́˹̐ 1 2 Рекс Мухтар Центральный репозиторий 136
  • 166. ˝˶̃˱̅˿́˱́˶̀˿˸˹̃˿́˹̐ 1 2 Рекс Мухтар Утечка данных из центрального репозитория в локальный и обратно Центральный репозиторий 137
  • 167. ˝˶̃˱̅˿́˱́˶̀˿˸˹̃˿́˹̐ 1 2 Рекс Мухтар Утечка данных из центрального репозитория в локальный и обратно Центральный репозиторий mov [x], 1 x = 1 x = 0 x = 0 138
  • 168. ˝˶̃˱̅˿́˱́˶̀˿˸˹̃˿́˹̐ 1 2 Рекс Мухтар Утечка данных из центрального репозитория в локальный и обратно Центральный репозиторий mov [x], 1 x = 1 x = 1 x = 0 139
  • 169. ˝˶̃˱̅˿́˱́˶̀˿˸˹̃˿́˹̐ 1 2 Рекс Мухтар Утечка данных из центрального репозитория в локальный и обратно Центральный репозиторий mov [x], 1 x = 1 x = 1 x = 1 140
  • 170. ˝˶̃˱̅˿́˱́˶̀˿˸˹̃˿́˹̐ 1 2 Рекс Мухтар Центральный репозиторий mov [x], 1 x = 1 x = ? x = ? Неизвестно, когда изменения распространятся на другие потоки. 141
  • 171. ˝˶̃˱̅˿́˱́˶̀˿˸˹̃˿́˹̐ 1 2 x = 0 y = 0 Рекс Мухтар Центральный репозиторий mov [x], 1 mov r1, [y] mov [y], 1 mov r2, [x] x = 0 y = 0 x = 0 y = 0 142
  • 172. ˝˶̃˱̅˿́˱́˶̀˿˸˹̃˿́˹̐ 1 2 x = 1 y = 0 Рекс Мухтар Центральный репозиторий mov [x], 1 mov r1, [y] mov [y], 1 mov r2, [x] x = 0 y = 1 x = 0 y = 0 143
  • 173. ˝˶̃˱̅˿́˱́˶̀˿˸˹̃˿́˹̐ 1 2 x = 1 y = ? {0,1} Рекс Мухтар Центральный репозиторий mov [x], 1 mov r1, [y] mov [y], 1 mov r2, [x] x = ? {0,1} y = 1 x = ? {0,1} y = ? {0,1} 144
  • 174. ˓˹˵̌˲˱́̍˶́˿˳ LoadLoad LoadStore StoreLoad StoreStore 145
  • 175. ˒˱́̍˶́/RDG/RDG 1 Рекс Центральный репозиторий LoadLoad x = 1 y = 0 x = 1 y = 0 git pull, hg pull, svn update, cvs update Барьер LoadLoad: ▪ Предотвращает переупорядочивания между загрузками до барьера и загрузками после барьера. ▪ Гарантирует, что загруженные из центрального репозитория (памяти) в локальный репозиторий (кэш) значения будут по крайней мере такие же новые, как и последнее значение, которое “просочилось” из центрального репозитория. 146
  • 176. ˒˱́̍˶́/RDG/RDG 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); } 147
  • 177. ˒˱́̍˶́6WRUH6WRUH 1 Рекс Центральный репозиторий StoreStore x = 1 y = 0 Барьер StoreStore: ▪ Предотвращает переупорядочивания между сохранениями до барьера и сохранениями после барьера. ▪ Гарантирует, что загруженные из локального репозитория (кэш) в локальный репозиторий (память) значения будут по крайней мере такие же новые, как и последнее значение, которое “просочилось” из локального репозитория. x = 1 y = 0 git push, hg push, svn commit, cvs commit 148
  • 178. ˒˱́̍˶́6WRUH6WRUH 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, // которые для него подготовил Рекс 149
  • 179. ˒˱́̍˶́/RDG6WRUH 1 mov r1, [y] mov r2, [x] mov [z], 42 mov [w], r3 mov [v], r4 Рекс Операции загрузки load Операции сохранения store Переупорядочивание LoadStore 1. Есть набор инструкций, состоящий из операций сохранения и загрузки. 150
  • 180. ˒˱́̍˶́/RDG6WRUH 1 mov r1, [y] mov r2, [x] mov [z], 42 mov [w], r3 mov [v], r4 Рекс Операции загрузки load - отложены Операции сохранения store - выполнены Переупорядочивание LoadStore 1. Есть набор инструкций, состоящий из операций сохранения и загрузки. 2. Если Рекс встречает операцию загрузки, то он просматривает следующие операции сохранения, и если они абсолютно не связаны с текущей операцией загрузки, то он откладывает выполнение операции загрузки и в первую очередь выполняет операции сохранения. 151
  • 181. ˒˱́̍˶́/RDG6WRUH 1 mov r1, [y] mov r2, [x] mov [z], 42 mov [w], r3 mov [v], r4 Рекс Будут промахи по кэшу... Будут попадания в кэш... Переупорядочивание LoadStore 1. Есть набор инструкций, состоящий из операций сохранения и загрузки. 2. Если Рекс встречает операцию загрузки, которые промахиваются по кэшу, то он просматривает следующие операции сохранения, которые попадают в кэш, и если они абсолютно не связаны с текущей операцией загрузки, то он откладывает выполнение операции загрузки и в первую очередь выполняет операции сохранения. 152
  • 182. ˒˱́̍˶́/RDG6WRUH 1 mov r1, [y] mov r2, [x] LoadStore mov [z], 42 mov [w], r3 mov [v], r4 Рекс Будут промахи по кэшу... Будут попадания в кэш... Переупорядочивание LoadStore 1. Есть набор инструкций, состоящий из операций сохранения и загрузки. 2. Если Рекс встречает операцию загрузки, которые промахиваются по кэшу, то он просматривает следующие операции сохранения, которые попадают в кэш, и если они абсолютно не связаны с текущей операцией загрузки, то он откладывает выполнение операции загрузки и в первую очередь выполняет операции сохранения. 153
  • 183. ˒˱́̍˶́6WRUH/RDG 1 mov [x], 1 mov r1, [y] x = 1 y = 0 Рекс 2 mov [y], 1 mov r2, [x] x = 0 y = 1 Мухтар r1 = 0 r2 = 0 154
  • 184. ˒˱́̍˶́6WRUH/RDG 1 mov [x], 1 StoreLoad mov r1, [y] x = 1 y = ? Рекс 2 mov [y], 1 StoreLoad mov r2, [x] x = ? y = 1 Мухтар Барьер StoreLoad: ▪ Гарантирует видимость для других процессоров всех операций сохранения, выполненных до барьера. ▪ Обеспечивает для всех операций загрузки, выполненных после барьера, получение результатов, которые имеют место во время барьера. ▪ Барьер предотвращает r1 = r2 = 0 ▪ StoreLoad ≠ StoreStore + LoadLoad 155
  • 185. ˒˱́̍˶́6WRUH/RDG 1 mov [x], 1 StoreLoad mov r1, [y] x = 1 y = ? Рекс Центральный репозиторий Барьер StoreLoad (≠ StoreStore + LoadLoad): 1. Отправка (push) всех изменений в центральный репозиторий. 156
  • 186. ˒˱́̍˶́6WRUH/RDG 1 mov [x], 1 StoreLoad mov r1, [y] x = 1 y = ? Рекс Центральный репозиторий Барьер StoreLoad (≠ StoreStore + LoadLoad): 1. Отправка (push) всех изменений в центральный репозиторий. 2. Ожидание завершения выполнения операции отправки (в отличие от StoreStore, который может выполняться с задержкой). 157
  • 187. ˒˱́̍˶́6WRUH/RDG 1 mov [x], 1 StoreLoad mov r1, [y] x = 1 y = ? Рекс Центральный репозиторий Барьер StoreLoad (≠ StoreStore + LoadLoad): 1. Отправка (push) всех изменений в центральный репозиторий. 2. Ожидание завершения выполнения операции отправки (в отличие от StoreStore, который может выполняться с задержкой). 3. Загрузка (pull) всех последних изменений из центрального репозитория (в отличие от LoadLoad, который не загружает абсолютно последние изменения) 158
  • 189. 159
  • 190. ˢ˶˽˱˾̃˹˻˱˸˱̆˳˱̃˱˹˿̂˳˿˲˿˷˵˶˾˹̐ Семантика захвата (acquire) ▪ Применяется к операциям чтения или чтения- модификации-записи, при этом такая операция становится операцией чтения-захвата (read-acquire). ▪ Предотвращает переупорядочивание инструкции чтения- захвата и всех следующих в программе операций чтения или записи. ▪ Применяется к операциям записи или чтения- модификации-записи, причём такая операция становится операцией записи-освобождения (write-release). ▪ Предотвращает переупорядочивание инструкции записи- освобождения со всеми предшествующими в программе операциями чтения или записи. Семантика освобождения (release) 160
  • 191. ˢ˶˽˱˾̃˹˻˱˸˱̆˳˱̃˱˹˿̂˳˿˲˿˷˵˶˾˹̐ read-acquire read / write read / write read / write read / write read / write read / write read / write read / write read / write read / write write-release 161
  • 192. ˢ˶˽˱˾̃˹˻˱˸˱̆˳˱̃˱˹˿̂˳˿˲˿˷˵˶˾˹̐ read-acquire read / write read / write read / write read / write read / write read / write read / write read / write read / write read / write write-release 162
  • 193. ˘˱̆˳˱̃˹˿̂˳˿˲˿˷˵˶˾˹˶˳̃˶́˽˹˾˱̆˲˱́̍˶́˿˳ Acquire (захват) LoadLoadFence LoadStoreFence StoreLoadFence StoreStoreFence Release (освобождение) 163
  • 194. ˘˱̆˳˱̃˹˿̂˳˿˲˿˷˵˶˾˹˶˳̃˶́˽˹˾˱̆˲˱́̍˶́˿˳ read-acquire LoadLoadFence + LoadStoreFence read / write read / write read / write read / write read / write read / write read / write read / write StoreStoreFence + LoadStoreFence write-release 164
  • 195. ˘˱̆˳˱̃˹˿̂˳˿˲˿˷˵˶˾˹˶˳̃˶́˽˹˾˱̆˲˱́̍˶́˿˳ read-acquire LoadLoadFence + LoadStoreFence read / write read / write read / write read / write read / write read / write read / write read / write StoreStoreFence + LoadStoreFence write-release 165
  • 196. ˘˱̆˳˱̃˹˿̂˳˿˲˿˷˵˶˾˹˶˳̃˶́˽˹˾˱̆˲˱́̍˶́˿˳ read-acquire LoadLoadFence + LoadStoreFence read / write read / write read / write read / write read / write read / write read / write read / write StoreStoreFence + LoadStoreFence write-release 166
  • 197. ˘˱̆˳˱̃˹˿̂˳˿˲˿˷˵˶˾˹˶˳̃˶́˽˹˾˱̆˲˱́̍˶́˿˳ read-acquire LoadLoadFence + LoadStoreFence read / write read / write read / write read / write read / write read / write read / write read / write StoreStoreFence + LoadStoreFence write-release 1 2 167
  • 198. ˘˱̆˳˱̃˹˿̂˳˿˲˿˷˵˶˾˹˶˳̃˶́˽˹˾˱̆˲˱́̍˶́˿˳ read-acquire LoadLoadFence + LoadStoreFence read / write read / write read / write read / write read / write read / write read / write read / write StoreStoreFence + LoadStoreFence write-release 2 1 1 2 168
  • 199. ˘˱̆˳˱̃˹˿̂˳˿˲˿˷˵˶˾˹˶˳̃˶́˽˹˾˱̆˲˱́̍˶́˿˳ read-acquire LoadLoadFence + LoadStoreFence read / write read / write read / write read / write read / write read / write read / write read / write LoadStoreFence + StoreStoreFence write-release 169
  • 200. ˢ˶˽˱˾̃˹˻˱˸˱̆˳˱̃˿̂˳˿˲˿˷˵˶˾˹˶˾˱̀́˹˽˶́˶̂̀̄˲˼˹˻˱̇˹˶˺ Поток 2 ready.load( memory_order_acquire); print(x); int x; bool ready = false; Поток 1 x = 42 ready.store(true, memory_order_release); 170
  • 201. ˢ˶˽˱˾̃˹˻˱˸˱̆˳˱̃˿̂˳˿˲˿˷˵˶˾˹˶˾˱̀́˹˽˶́˶̂̀̄˲˼˹˻˱̇˹˶˺ Поток 1 x = 42 ready.store(true, memory_order_release); Synchronizes-with Поток 2 ready.load( memory_order_acquire); print(x); int x; bool ready = false; Синхронизируется-с Всё, что здесь... … будет видно здесь 171
  • 202. ˢ˶˽˱˾̃˹˻˱˸˱̆˳˱̃˿̂˳˿˲˿˷˵˶˾˹˶˾˱̀́˹˽˶́˶̂̀̄˲˼˹˻˱̇˹˶˺ Поток 1 x = 42 ready.store(true, memory_order_release); Synchronizes-with Поток 2 ready.load( memory_order_acquire); print(x); int x; bool ready = false; Синхронизируется-с … будет видно здесь Всё, что здесь... Операция записи- освобождения Операция чтения- захвата 172
  • 203. ˢ˶˽˱˾̃˹˻˱˸˱̆˳˱̃˿̂˳˿˲˿˷˵˶˾˹˶˾˱̀́˹˽˶́˶̂̀̄˲˼˹˻˱̇˹˶˺ Поток 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; 173
  • 204. ˢ˶˽˱˾̃˹˻˱˸˱̆˳˱̃˿̂˳˿˲˿˷˵˶˾˹˶˾˱̀́˹˽˶́˶̂̀̄˲˼˹˻˱̇˹˶˺ Synchronizes-with Поток 2 void useWidget() { while (!ready.load( memory_order_acquire)) {} doSomething(w); } widget w; bool ready = false; Поток 1 void initWidget(x, y, z) { w.x = x; w.y = x; w.z = z; ready.store(true, memory_order_release); } Синхронизируется-с Всё, что здесь... … будет видно здесь Операция записи- освобождения Операция чтения-захвата 174
  • 205. ˢ˶˽˱˾̃˹˻˱˸˱̆˳˱̃˿̂˳˿˲˿˷˵˶˾˹˶˾˱̀́˹˽˶́˶̂̀̄˲˼˹˻˱̇˹˶˺ Synchronizes-with Поток 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); } Синхронизируется-с 175
  • 206. ˢ˶˽˱˾̃˹˻˱˸˱̆˳˱̃˿̂˳˿˲˿˷˵˶˾˹˶˾˱̀́˹˽˶́˶̂̀̄˲˼˹˻˱̇˹˶˺ Synchronizes-with Поток 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); } Синхронизируется-с Операция записи- освобождения Операция чтения-захвата 176