В докладе Андрей расскажет о моделях памяти различных процессоров, о тонкостях реализации неблокирующих алгоритмов и о том, какое отношение всё это имеет к С++.
16. int data;
volatile bool ready = false;
void foo() { // CPU 0
data = 42;
ready = true;
}
void bar() { // CPU 1
if (ready) {
assert(data == 42);
}
}
16
Пример #2
17. int data;
volatile bool ready = false;
void foo() { // CPU 0
ready = true;
data = 42;
}
void bar() { // CPU 1
if (ready) {
assert(data == 42);
}
}
17
Пример #2
CPU
18. int data;
volatile bool ready = false;
void foo() { // CPU 0
data = 42;
ready = true;
}
void bar() { // CPU 1
int tmp = data;
if (ready) {
assert(tmp == 42);
}
}
18
Пример #2
19. int data;
volatile bool ready = false;
void foo() { // CPU 0
data = 42;
____________
ready = true;
}
void bar() { // CPU 1
if (ready) {
__________
assert(data == 42);
}
}
19
Пример #2
20. int data;
volatile bool ready = false;
void foo() { // CPU 0
data = 42;
STORE_______
ready = true;
}
void bar() { // CPU 1
if (ready) {
__________
assert(data == 42);
}
}
20
Пример #2
21. int data;
volatile bool ready = false;
void foo() { // CPU 0
ready = true;
data = 42;
STORE_______
ready = true;
}
void bar() { // CPU 1
if (ready) {
__________
assert(data == 42);
}
}
21
Пример #2
22. int data;
volatile bool ready = false;
void foo() { // CPU 0
data = 42;
STORE__STORE
ready = true;
}
void bar() { // CPU 1
if (ready) {
__________
assert(data == 42);
}
}
22
Пример #2
23. int data;
volatile bool ready = false;
void foo() { // CPU 0
data = 42;
STORE__STORE
ready = true;
}
void bar() { // CPU 1
if (ready) {
LOAD______
assert(data == 42);
}
}
23
Пример #2
24. int data;
volatile bool ready = false;
void foo() { // CPU 0
data = 42;
STORE__STORE
ready = true;
}
void bar() { // CPU 1
if (ready) {
LOAD__LOAD
assert(data == 42);
}
}
24
Пример #2
25. XX_YY — гарантирует, что все XX-операции до барьера будут
выполнены до того, как начнут выполняться YY-операции после
барьера.
25
Барьер памяти
26. XX_YY — гарантирует, что все XX-операции до барьера будут
выполнены до того, как начнут выполняться YY-операции после
барьера.
26
Барьер памяти
LoadLoad LoadStore
StoreLoad StoreStore
27. XX_YY — гарантирует, что все XX-операции до барьера будут
выполнены до того, как начнут выполняться YY-операции после
барьера.
27
Барьер памяти
LoadLoad LoadStore
StoreLoad StoreStore
Acquire
Release
28. Acquire — гарантирует, что любые операции после барьера будут
выполнены после того, как будут выполнены все Load-операции до
барьера.
28
Acquire
29. Acquire — гарантирует, что любые операции после барьера будут
выполнены после того, как будут выполнены все Load-операции до
барьера.
29
Acquire
mov [x], 1
mov eax, [y]
acquire_fence
mov ebx, [w]
mov ecx, [e]
mov [q], 3
30. Acquire — гарантирует, что любые операции после барьера будут
выполнены после того, как будут выполнены все Load-операции до
барьера.
30
Acquire
mov [x], 1
mov ebx, [w] <—
mov eax, [y]
acquire_fence
mov ebx, [w]
mov ecx, [e]
mov [q], 3
31. Acquire — гарантирует, что любые операции после барьера будут
выполнены после того, как будут выполнены все Load-операции до
барьера.
31
Acquire
mov [x], 1
mov eax, [y]
acquire_fence
mov [x], 1 <—
mov ebx, [w]
mov ecx, [e]
mov [q], 3
32. Release — гарантирует, что любые операции до барьера будут
выполнены до того, как начнут выполняться Store-операции после
барьера.
32
Release
33. Release — гарантирует, что любые операции до барьера будут
выполнены до того, как начнут выполняться Store-операции после
барьера.
33
Release
mov [x], 1
mov eax, [y]
mov [z], 2
release_fence
mov [w], 3
mov ebx, [k]
34. Release — гарантирует, что любые операции до барьера будут
выполнены до того, как начнут выполняться Store-операции после
барьера.
34
Release
mov [x], 1
mov eax, [y]
mov [z], 2
release_fence
mov [w], 3
mov [z], 2 <—
mov ebx, [k]
35. Release — гарантирует, что любые операции до барьера будут
выполнены до того, как начнут выполняться Store-операции после
барьера.
35
Release
mov [x], 1
mov eax, [y]
mov [z], 2
mov ebx, [k] <—
release_fence
mov [w], 3
mov ebx, [k]
36. void function_with_lock() {
...
if (can_enter) {
acquire_fence(); // LoadLoad + LoadStore
// all instructions
// stay between
// these fences
release_fence(); // StoreStore + LoadStore
can_enter = true;
}
...
}
36
Что такое acquire/release?
37. bool x = true;
bool y = true;
void foo() { // CPU 0
x = false;
assert(y);
}
void bar() { // CPU 1
y = false;
assert(x);
}
37
Пример #3
38. bool x = true;
bool y = true;
void foo() { // CPU 0
assert(y);
x = false;
}
void bar() { // CPU 1
y = false;
assert(x);
}
38
Пример #3
39. bool x = true;
bool y = true;
void foo() { // CPU 0
x = false;
___________
assert(y);
}
void bar() { // CPU 1
y = false;
___________
assert(x);
}
39
Пример #3
40. bool x = true;
bool y = true;
void foo() { // CPU 0
x = false;
Store______
assert(y);
}
void bar() { // CPU 1
y = false;
Store______
assert(x);
}
40
Пример #3
41. bool x = true;
bool y = true;
void foo() { // CPU 0
x = false;
Store__Load
assert(y);
}
void bar() { // CPU 1
y = false;
Store__Load
assert(x);
}
41
Пример #3
42. bool x = true;
bool y = true;
void foo() { // CPU 0
x = false;
Store__Load
assert(y);
}
void bar() { // CPU 1
y = false;
Store__Load
assert(x);
}
42
Пример #3
43. Это самый “тяжелый” из барьеров
StoreLoad означает полную синхронизацию всех кэшей
процессоров.
Ситуации, где он действительно нужен, довольно редки
43
Почему StoreLoad стоит отдельно?
46. Никаких memory reordering
Гарантируется, что каждый процессор видит все изменения в системе в
том же порядке, что и остальные процессоры.
46
Sequential consistency
47. Каждая операция имеет acquire и release семантику
Все процессоры видят последовательности чтений и записей в
одном порядке, но перестановки StoreLoad уже возможны
!
!
Пример: x86/64
47
Strongly-ordered
48. Гарантируется лишь упорядоченность для операций, зависимых по
данным
int* ptr = GlobalPointer;
if (ptr)
int value = *ptr;
!
!
Примеры: ARMv7, PowerPC, Itanium
48
Weakly-ordered
52. std::atomic<bool> ready;
!
void foo() { // CPU 0
data = 42;
ready.store(true);
}
void bar() { // CPU 1
if (ready.load()) {
assert(data == 42);
}
}
52
Барьеры памяти С++
53. std::atomic<bool> ready;
!
void foo() { // CPU 0
data = 42;
ready.store(true, std::memory_order_release);
}
void bar() { // CPU 1
if (ready.load(std::memory_order_acquire)) {
assert(data == 42);
}
}
53
Барьеры памяти С++
54. std::atomic<bool> ready;
void foo() { // CPU 0
data = 42;
std::atomic_thread_fence(std::memory_order_release);
ready.store(true, std::memory_order_relaxed);
}
void bar() { // CPU 1
if (ready.load(true, std::memory_order_relaxed)) {
std::atomic_thread_fence(std::memory_order_acquire);
assert(data == 42);
}
}
54
Барьеры памяти С++
55. std::atomic<Foo*> p;
Foo payload;
int value = 42;
void foo() { // CPU 0
payload.name = "Andy";
value = 314;
p.store(&payload, std::memory_order_release);
}
void bar() { // CPU 1
if (auto f = p.load(std::memory_order_consume)) {
std::cout << f->name << std::endl; // Andy
assert(value == 314); // can fire (but probably won't)
}
}
55
Зачем std::memory_order_consume?
56. std::atomic<Foo*> p;
Foo payload;
int value = 42;
void foo() { // CPU 0
payload.name = "Andy";
value = 314;
p.store(&payload, std::memory_order_release);
}
void bar() { // CPU 1
int tmp = value;
if (auto f = p.load(std::memory_order_consume)) {
std::cout << f->name << std::endl; // Andy
assert(tmp == 314);
}
}
56
Зачем std::memory_order_consume?
57. Компиляторы не поддерживают это поведение (поэтому на weak-
процессорах все равно скорее всего будет барьер)
x86/64 и без этого прекрасно работает
57
Почему probably won’t?