63

Модель памяти C++ - Андрей Янковский, Яндекс

  • Upload
    yandex

  • View
    1.018

  • Download
    5

Embed Size (px)

Citation preview

Page 1: Модель памяти C++ - Андрей Янковский, Яндекс
Page 2: Модель памяти C++ - Андрей Янковский, Яндекс

Memory model C++

Янковский Андрей, [email protected]

Page 3: Модель памяти C++ - Андрей Янковский, Яндекс

Phil Karlton

There are only two hard things in Computer Science: cache invalidation, naming things and off-by-one errors.

Page 4: Модель памяти C++ - Андрей Янковский, Яндекс

<thread>

<mutex>

<conditional_variable>

<future>

<atomic>

4

Многопоточность в C++11

Page 5: Модель памяти C++ - Андрей Янковский, Яндекс

Ура!

Теперь мы можем писать кросс-платформенный многопоточный код?

Page 6: Модель памяти C++ - Андрей Янковский, Яндекс

6

Ожидание

Page 7: Модель памяти C++ - Андрей Янковский, Яндекс

7

Реальность

Page 8: Модель памяти C++ - Андрей Янковский, Яндекс

Что пошло не так? Кто виноват?

Page 9: Модель памяти C++ - Андрей Янковский, Яндекс

Процесс

Поток

Разделяемая память

Атомарность операций

9

Компилятор С++ слишком умный

Page 10: Модель памяти C++ - Андрей Янковский, Яндекс

Singleton* Singleton::Get() { if (instance == nullptr) { std::lock_guard<std::mutex> guard(lock); if (instance == nullptr) { instance = new Singleton(); ! } } return instance; }

10

Пример #1

Page 11: Модель памяти C++ - Андрей Янковский, Яндекс

Singleton* Singleton::Get() { if (instance == nullptr) { std::lock_guard<std::mutex> guard(lock); if (instance == nullptr) { instance = operator new(sizeof(Singleton)); // 1 new (instance) Singleton; // 2 } } return instance; }

11

Пример #1 (продолжение)

Page 12: Модель памяти C++ - Андрей Янковский, Яндекс

Memory reordering

Page 13: Модель памяти C++ - Андрей Янковский, Яндекс

13

Как работает процессор

CPU

Memory

Page 14: Модель памяти C++ - Андрей Янковский, Яндекс

14

Как работает процессор

CPU

Memory

Cache

Page 15: Модель памяти C++ - Андрей Янковский, Яндекс

15

Как работает процессор

CPU

Memory

Cache

CPU

Cache

Page 16: Модель памяти C++ - Андрей Янковский, Яндекс

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

Page 17: Модель памяти C++ - Андрей Янковский, Яндекс

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

Page 18: Модель памяти C++ - Андрей Янковский, Яндекс

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

Page 19: Модель памяти C++ - Андрей Янковский, Яндекс

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

Page 20: Модель памяти C++ - Андрей Янковский, Яндекс

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

Page 21: Модель памяти C++ - Андрей Янковский, Яндекс

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

Page 22: Модель памяти C++ - Андрей Янковский, Яндекс

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

Page 23: Модель памяти C++ - Андрей Янковский, Яндекс

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

Page 24: Модель памяти C++ - Андрей Янковский, Яндекс

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

Page 25: Модель памяти C++ - Андрей Янковский, Яндекс

XX_YY — гарантирует, что все XX-операции до барьера будут выполнены до того, как начнут выполняться YY-операции после барьера.

25

Барьер памяти

Page 26: Модель памяти C++ - Андрей Янковский, Яндекс

XX_YY — гарантирует, что все XX-операции до барьера будут выполнены до того, как начнут выполняться YY-операции после барьера.

26

Барьер памяти

LoadLoad LoadStore

StoreLoad StoreStore

Page 27: Модель памяти C++ - Андрей Янковский, Яндекс

XX_YY — гарантирует, что все XX-операции до барьера будут выполнены до того, как начнут выполняться YY-операции после барьера.

27

Барьер памяти

LoadLoad LoadStore

StoreLoad StoreStore

Acquire

Release

Page 28: Модель памяти C++ - Андрей Янковский, Яндекс

Acquire — гарантирует, что любые операции после барьера будут выполнены после того, как будут выполнены все Load-операции до барьера.

28

Acquire

Page 29: Модель памяти C++ - Андрей Янковский, Яндекс

Acquire — гарантирует, что любые операции после барьера будут выполнены после того, как будут выполнены все Load-операции до барьера.

29

Acquire

mov [x], 1 mov eax, [y] acquire_fence mov ebx, [w] mov ecx, [e] mov [q], 3

Page 30: Модель памяти C++ - Андрей Янковский, Яндекс

Acquire — гарантирует, что любые операции после барьера будут выполнены после того, как будут выполнены все Load-операции до барьера.

30

Acquire

mov [x], 1 mov ebx, [w] <— mov eax, [y] acquire_fence mov ebx, [w] mov ecx, [e] mov [q], 3

Page 31: Модель памяти C++ - Андрей Янковский, Яндекс

Acquire — гарантирует, что любые операции после барьера будут выполнены после того, как будут выполнены все Load-операции до барьера.

31

Acquire

mov [x], 1 mov eax, [y] acquire_fence mov [x], 1 <— mov ebx, [w] mov ecx, [e] mov [q], 3

Page 32: Модель памяти C++ - Андрей Янковский, Яндекс

Release — гарантирует, что любые операции до барьера будут выполнены до того, как начнут выполняться Store-операции после барьера.

32

Release

Page 33: Модель памяти C++ - Андрей Янковский, Яндекс

Release — гарантирует, что любые операции до барьера будут выполнены до того, как начнут выполняться Store-операции после барьера.

33

Release

mov [x], 1 mov eax, [y] mov [z], 2 release_fence mov [w], 3 mov ebx, [k]

Page 34: Модель памяти C++ - Андрей Янковский, Яндекс

Release — гарантирует, что любые операции до барьера будут выполнены до того, как начнут выполняться Store-операции после барьера.

34

Release

mov [x], 1 mov eax, [y] mov [z], 2 release_fence mov [w], 3 mov [z], 2 <— mov ebx, [k]

Page 35: Модель памяти C++ - Андрей Янковский, Яндекс

Release — гарантирует, что любые операции до барьера будут выполнены до того, как начнут выполняться Store-операции после барьера.

35

Release

mov [x], 1 mov eax, [y] mov [z], 2 mov ebx, [k] <— release_fence mov [w], 3 mov ebx, [k]

Page 36: Модель памяти C++ - Андрей Янковский, Яндекс

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?

Page 37: Модель памяти C++ - Андрей Янковский, Яндекс

bool x = true; bool y = true;   void foo() { // CPU 0 x = false; assert(y); }   void bar() { // CPU 1 y = false; assert(x); }

37

Пример #3

Page 38: Модель памяти C++ - Андрей Янковский, Яндекс

bool x = true; bool y = true;   void foo() { // CPU 0 assert(y); x = false; }   void bar() { // CPU 1 y = false; assert(x); }

38

Пример #3

Page 39: Модель памяти C++ - Андрей Янковский, Яндекс

bool x = true; bool y = true;   void foo() { // CPU 0 x = false; ___________ assert(y); }   void bar() { // CPU 1 y = false; ___________ assert(x); }

39

Пример #3

Page 40: Модель памяти C++ - Андрей Янковский, Яндекс

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

Page 41: Модель памяти C++ - Андрей Янковский, Яндекс

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

Page 42: Модель памяти C++ - Андрей Янковский, Яндекс

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

Page 43: Модель памяти C++ - Андрей Янковский, Яндекс

Это самый “тяжелый” из барьеров

StoreLoad означает полную синхронизацию всех кэшей процессоров.

Ситуации, где он действительно нужен, довольно редки

43

Почему StoreLoad стоит отдельно?

Page 44: Модель памяти C++ - Андрей Янковский, Яндекс

Memory models

Page 45: Модель памяти C++ - Андрей Янковский, Яндекс

Sequential consistency

Strongly-ordered

Weakly-ordered (data dependency ordering)

Super-weak

45

Модель памяти

Page 46: Модель памяти C++ - Андрей Янковский, Яндекс

Никаких memory reordering

Гарантируется, что каждый процессор видит все изменения в системе в том же порядке, что и остальные процессоры.

46

Sequential consistency

Page 47: Модель памяти C++ - Андрей Янковский, Яндекс

Каждая операция имеет acquire и release семантику

Все процессоры видят последовательности чтений и записей в одном порядке, но перестановки StoreLoad уже возможны !!

Пример: x86/64

47

Strongly-ordered

Page 48: Модель памяти C++ - Андрей Янковский, Яндекс

Гарантируется лишь упорядоченность для операций, зависимых по данным

int* ptr = GlobalPointer; if (ptr) int value = *ptr; !!

Примеры: ARMv7, PowerPC, Itanium

48

Weakly-ordered

Page 49: Модель памяти C++ - Андрей Янковский, Яндекс

Никаких гарантий: процессор может делать, что угодно

Примеры: Alpha

49

Super-weak (no guaranties)

Page 50: Модель памяти C++ - Андрей Янковский, Яндекс

enum memory_order { memory_order_relaxed, memory_order_consume, memory_order_acquire, memory_order_release, memory_order_acq_rel, memory_order_seq_cst };

50

#include <atomic>

Page 51: Модель памяти C++ - Андрей Янковский, Яндекс

#include <atomic> !!!T load(memory_order = memory_order_seq_cst) const; void store(T desired, memory_order = memory_order_seq_cst);   void atomic_thread_fence(memory_order order);

51

Барьеры памяти С++

Page 52: Модель памяти C++ - Андрей Янковский, Яндекс

std::atomic<bool> ready; !void foo() { // CPU 0 data = 42; ready.store(true); }   void bar() { // CPU 1 if (ready.load()) { assert(data == 42); } }

52

Барьеры памяти С++

Page 53: Модель памяти C++ - Андрей Янковский, Яндекс

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

Барьеры памяти С++

Page 54: Модель памяти C++ - Андрей Янковский, Яндекс

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

Барьеры памяти С++

Page 55: Модель памяти C++ - Андрей Янковский, Яндекс

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?

Page 56: Модель памяти C++ - Андрей Янковский, Яндекс

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?

Page 57: Модель памяти C++ - Андрей Янковский, Яндекс

Компиляторы не поддерживают это поведение (поэтому на weak-процессорах все равно скорее всего будет барьер)

x86/64 и без этого прекрасно работает

57

Почему probably won’t?

Page 58: Модель памяти C++ - Андрей Янковский, Яндекс

Зачем мне все это знать? Я не пишу lock-free код!

Page 59: Модель памяти C++ - Андрей Янковский, Яндекс

Singleton* Singleton::Get() { if (instance.load() == nullptr) { std::lock_guard<std::mutex> guard(lock); if (instance.load() == nullptr) { instance.store(new Singleton()); } } return instance.load(); }

59

Пример #1 (almost fixed)

Page 60: Модель памяти C++ - Андрей Янковский, Яндекс

Singleton* Singleton::Get() { auto tmp = instance.load(std::memory_order_relaxed); std::atomic_thread_fence(std::memory_order_acquire); if (tmp == nullptr) { std::lock_guard<std::mutex> guard(lock); tmp = instance.load(std::memory_order_relaxed); if (tmp == nullptr) { tmp = new Singleton(); std::atomic_thread_fence(std::memory_order_release); instance.store(tmp, std::memory_order_relaxed); } } return tmp; }

60

Пример #1 (fixed)

Page 61: Модель памяти C++ - Андрей Янковский, Яндекс

Все это проявляется только в многопроцессорной/многоядерной среде

И только в lock-free коде

61

Но как мы жили раньше?

Page 62: Модель памяти C++ - Андрей Янковский, Яндекс

Спасибо за внимание, пишите хороший код :)

Page 63: Модель памяти C++ - Андрей Янковский, Яндекс

Янковский Андрей

Разработчик

@[email protected]