Top Banner
Облаков Константин Разработчик группы обработки свежего контента Lock-free in practice: RealTime-Server
74

Практика Lock-free. RealTime-сервер

Aug 08, 2015

Download

Software

Platonov Sergey
Welcome message from author
This document is posted to help you gain knowledge. Please leave a comment to let me know what you think about it! Share it to your friends and learn new things together.
Transcript
Page 1: Практика Lock-free. RealTime-сервер

Облаков Константин

Разработчик группы обработки свежего контента

Lock-free in practice:

RealTime-Server

Page 2: Практика Lock-free. RealTime-сервер

• Введение

• Мотивировка

• Традиционный подход к lock-free

• Read Copy Update

• Версионный подход

• Большой пример

• Заключение

План

Page 3: Практика Lock-free. RealTime-сервер

• Введение

• Мотивировка

• Традиционный подход к lock-free

• Read Copy Update

• Версионный подход

• Большой пример

• Заключение

План

Page 4: Практика Lock-free. RealTime-сервер

• Mutex (global, grained, etc.)

• Atomic memory transactions (hardware)

• Lock-free / wait-free

Многие ядра = синхронизация:

Page 5: Практика Lock-free. RealTime-сервер

• Введение

• Мотивировка

• Традиционный подход к lock-free

• Read Copy Update

• Версионный подход

• Большой пример

• Заключение

План

Page 6: Практика Lock-free. RealTime-сервер

1.Свежесть бывает только одна

2.Нужно доносить информацию консистентно

3.Производительность нужна всем

4.Время ответа ограничено

5.Операций чтения намного больше записи

RealTime-Server

Page 7: Практика Lock-free. RealTime-сервер

• Введение

• Мотивировка

• Традиционный подход к lock-free

• Read Copy Update

• Версионный подход

• Большой пример

• Заключение

План

Page 8: Практика Lock-free. RealTime-сервер

1.Найти lock-free реализацию всех структур

2.Заменить стандартные

Традиционный подход к lock-free

Page 9: Практика Lock-free. RealTime-сервер

• Самодостаточные lock-free структуры

слишком сложны и специфичны

• Куча лишних операций

• Консистентность всё равно отсутствует

Не надо так!

Page 10: Практика Lock-free. RealTime-сервер

Куча лишних операций:

ЯДРО

Контроль памяти

Регистрация действий

Циклы CAS

Page 11: Практика Lock-free. RealTime-сервер

Куча лишних операций:

Page 12: Практика Lock-free. RealTime-сервер

Общая консистентность отсутсвует:

Не связаны!

Page 13: Практика Lock-free. RealTime-сервер

• Введение

• Мотивировка

• Традиционный подход к lock-free

• Read Copy Update

• Версионный подход

• Большой пример

• Заключение

План

Page 14: Практика Lock-free. RealTime-сервер

• Если данные только для чтения

• Если данные меняются атомарно

• Если данные в единоличном владении

Когда синхронизация не нужна

Page 15: Практика Lock-free. RealTime-сервер

Read: Всё до чего можно добраться по

указателям – читаемо и консистентно

Copy: Перед изменением данные копируются

Update: Откопированные данные можно

обновлять

Но только один пишущий поток

Строим на основе этого систему

Page 16: Практика Lock-free. RealTime-сервер

16

- Копировать всё?

- Да!

Page 17: Практика Lock-free. RealTime-сервер

17

- Это дорого!

- А вот и нет …

Page 18: Практика Lock-free. RealTime-сервер

18

• Введение

• Мотивировка

• Традиционный подход к lock-free

• Read Copy Update

• Версионный подход

• Большой пример

• Заключение

План

Page 19: Практика Lock-free. RealTime-сервер

19

• Копировать память всего процесса – дорого!

• Каждый процесс не владеет памятью

напрямую – есть прослойка PageTable

• Если откопировать только прослойку –

будет видимость полного копирования

• Надо применять copy-on-write для страниц

Как работает системный вызов fork()

Page 20: Практика Lock-free. RealTime-сервер

20

• В. дерево – переиспользуем поддеревья

• В. стэк – переиспользуем общую часть

• В. хэш – переиспользуем чанки – атомарно добавляем записи

• при открытой адресации сначала пишем данные, затем ключ и (опционально) флаг готовности, если ключ не атомарен

• при списковом разрешении коллизий используем атомарность указателей

– старые записи просто маркируем старыми, не удаляя

– при необходимости в запись добавляем номер версии хэша

– слишком заполненный хэш копируем в новое место

• Прочее …

Обобщение – версионные структуры

Page 21: Практика Lock-free. RealTime-сервер

21

Дерево

Page 22: Практика Lock-free. RealTime-сервер

22

Дерево

Page 23: Практика Lock-free. RealTime-сервер

23

Дерево

Page 24: Практика Lock-free. RealTime-сервер

24

Стек

Page 25: Практика Lock-free. RealTime-сервер

25

Стек

Page 26: Практика Lock-free. RealTime-сервер

26

Стек

Page 27: Практика Lock-free. RealTime-сервер

27

Хэш

Page 28: Практика Lock-free. RealTime-сервер

28

Хэш

Page 29: Практика Lock-free. RealTime-сервер

29

Хэш

Page 30: Практика Lock-free. RealTime-сервер

30

Объединение структур

Старая версия Новая версия

на 99.9% те же

Page 31: Практика Lock-free. RealTime-сервер

31

Указатель на корневую версию

Старая версия Новая версия

Page 32: Практика Lock-free. RealTime-сервер

32

Указатель на корневую версию

Старая версия Новая версия

Page 33: Практика Lock-free. RealTime-сервер

33

Указатель на корневую версию

Старая версия Новая версия

Когда удалить??

Page 34: Практика Lock-free. RealTime-сервер

34

1.Надо регистрировать читателей!

2.Надо просматривать список читающих

3.Никем не читаемую не текущую версию

можно чистить

4.Если всё владение идёт через умные

указатели со счётчиком ссылок – удаление

подструктур произойдёт автомагически!

5.Схема накладывает свой отпечаток на

читающий поток - нужен цикл переопроса

Когда удалить старую версию?

Page 35: Практика Lock-free. RealTime-сервер

35

Регистрация читателей

Зарезервированные ячейки

Счётчик занятых

Page 36: Практика Lock-free. RealTime-сервер

36

Регистрация читателей

Зарезервированные ячейки

Счётчик занятых Нет запрета на

очистку старых

версий!

Page 37: Практика Lock-free. RealTime-сервер

37

Регистрация читателей

Зарезервированные ячейки

Счётчик занятых Нет запрета на

очистку старых

версий!

Page 38: Практика Lock-free. RealTime-сервер

38

Регистрация читателей

C

Зарезервированные ячейки

Счётчик занятых Версию C и

более поздние

удалять нельзя!

Page 39: Практика Lock-free. RealTime-сервер

39

Регистрация читателей

D C

Зарезервированные ячейки

Счётчик занятых Версию C и

более поздние

удалять нельзя!

Page 40: Практика Lock-free. RealTime-сервер

40

Регистрация читателей

D C

Зарезервированные ячейки

Счётчик занятых Версию C и

более поздние

удалять нельзя!

Page 41: Практика Lock-free. RealTime-сервер

41

Регистрация читателей

D C F

Зарезервированные ячейки

Счётчик занятых Версию C и

более поздние

удалять нельзя!

Page 42: Практика Lock-free. RealTime-сервер

42

Регистрация читателей

C F

Зарезервированные ячейки

Счётчик занятых Версию C и

более поздние

удалять нельзя!

Page 43: Практика Lock-free. RealTime-сервер

43

Регистрация читателей

F

Зарезервированные ячейки

Счётчик занятых Версию F и

более поздние

удалять нельзя!

Page 44: Практика Lock-free. RealTime-сервер

44

Регистрация читателей

G F

Зарезервированные ячейки

Счётчик занятых Версию F и

более поздние

удалять нельзя!

Переиспользуем ячейки в потоке!

Page 45: Практика Lock-free. RealTime-сервер

45

1.Получаем ячейку, если пока нет

2.Читаем текущий указатель на версию

3.Записываем в свою ячейку

4.Снова читаем указатель

5.Если не совпал – на шаг 3

WARNING: Это не spin lock!

Если указатель меняется – в системе

происходит прогресс

Цикл переопроса при регистрации

Page 46: Практика Lock-free. RealTime-сервер

46

• Храним порядковый номер версии (>0)

• В ячейке хранить не указатель, а номер

• Читатетель записывает номер версии в

ячейку, затем читает указатель на версию

и сразу работает с ней

Надо потребовать, что переполнения

счётчика или не поисходит, или происходит

за “достаточно большое время”

Как можно сделать wait-free

Page 47: Практика Lock-free. RealTime-сервер

47

1.Пишем в ячейку “пусто”

И всё!

Освобождение версии

Page 48: Практика Lock-free. RealTime-сервер

48

• Введение

• Мотивировка

• Традиционный подход к lock-free

• Read Copy Update

• Версионный подход

• Большой пример

• Заключение

План

Page 49: Практика Lock-free. RealTime-сервер

49

• “Документ” – набор 1000 случайных чисел

• “Поиск” – ищем количество уникальных

документов, содержащих число из

заданного диапазона [from, to)

• Запросы на добавление документов

• Запросы на поиск (не более 1000 тредов)

• Wait-free!

Условия примера RealTime-Server

Page 50: Практика Lock-free. RealTime-сервер

50

Класс элемента RCU-стека

class Item {

int Data;

shared_ptr<Item> NextItem;

public:

Item(int data, shared_ptr<Item> nextItem)

: Data(data), NextItem(nextItem) { }

const Item* Next() const {

return NextItem.get();

}

int GetData() const {

return Data;

}

};

Page 51: Практика Lock-free. RealTime-сервер

51

Класс итератора по RCU-стеку

class const_iterator {

const Item* Current;

public:

const_iterator(const Item* current = nullptr)

: Current(current) { }

const_iterator& operator++() {

Current = Current->Next();

return *this;

}

bool operator!=(const const_iterator& other) const {

return Current != other.Current;

}

int operator*() const {

return Current->GetData();

}

};

Page 52: Практика Lock-free. RealTime-сервер

52

Класс RCU-стека

class RCU_Stack {

class Item;

shared_ptr<Item> First;

public:

void push(int data) {

shared_ptr<Item>(

new Item(data, First)).swap(First);

}

class const_iterator;

const_iterator begin() const {return First.get();}

const_iterator end() const { return nullptr; }

};

Page 53: Практика Lock-free. RealTime-сервер

53

Класс ноды дерева

class Node {

int Key;

shared_ptr<Node> Left;

shared_ptr<Node> Right;

RCU_Stack Stack;

public:

Node(int key, int data) : Key(key) {

Stack.push(data);

}

void add_node_data(int i, int data);

void find_docs(int from, int to, set<int>* docs);

static void make_copy(

shared_ptr<Node>& node

);

};

Page 54: Практика Lock-free. RealTime-сервер

54

Класс ноды дерева

void Node::add_node_data(int i, int data) {

if (i == Key) {

Stack.push(data);

} else if (i < Key && Left) {

make_copy(Left);

Left->add_node(i);

} else if (i > Key && Right) {

make_copy(Right);

Right->add_node(i);

} else {

shared_ptr<Node> new_child(new Node(i));

if (i < Key) Left = new_child;

else Right = new_child;

}

}

Page 55: Практика Lock-free. RealTime-сервер

55

Класс ноды дерева

void Node::find_docs(

int from, int to, set<int>* docs) {

if (from <= Key && Key < to) {

for (auto doc : Stack)

docs->insert(doc);

}

if (from < Key && Left) {

Left->find_docs(from, to, docs);

}

if (Key < to && Right) {

Right->find_docs(from, to, docs);

}

}

Page 56: Практика Lock-free. RealTime-сервер

56

Класс ноды дерева

void Node::make_copy(shared_ptr<Node>& node) {

if (node.use_count() > 1) {

shared_ptr<Node> new_node(new Node(*node));

node = new_node;

}

}

Page 57: Практика Lock-free. RealTime-сервер

57

Класс RCU-дерева

class RCU_Tree {

class Node;

shared_ptr<Node> Root;

public:

void add_node_data(int i, int data);

void find_docs(

int from, int to, set<int>* docs);

};

Page 58: Практика Lock-free. RealTime-сервер

58

Класс RCU-дерева

void RCU_Tree::add_node_data(int i, int data) {

Node::make_copy(Root);

if (Root) {

Root->add_node_data(i, data);

return;

}

shared_ptr<Node> new_root(

new Node(i, data));

Root = new_root;

}

Page 59: Практика Lock-free. RealTime-сервер

59

Класс RCU-дерева

void RCU_Tree::find_docs(

int from, int to, set<int>* docs)

{

if (Root) {

Root->find_docs(from, to, docs);

}

}

Page 60: Практика Lock-free. RealTime-сервер

60

Класс версии

struct Version {

RCU_Tree Tree;

};

Page 61: Практика Lock-free. RealTime-сервер

61

Класс очереди версий (part 1)

class Versions_queue {

using version_ptr = shared_ptr<Version>;

using version_info = pair<version_ptr, int>;

atomic<int> Client_id;

atomic<Version*> Current;

atomic<int> Current_version_id;

array<atomic<int>, 1000> Client_version_ids;

queue<version_info> Queue;

...

};

Page 62: Практика Lock-free. RealTime-сервер

62

Класс очереди версий (part 2)

class Versions_queue {

...

void cleanup_unused_versions() {

int max_client_id = Client_id;

auto begin = Client_version_ids.begin();

auto end = begin + max_client_id;

int min_ver_id = Current_version_id;

for (auto it = begin; it != end; ++it) {

int client_ver_id = *it;

if (client_ver_id && min_ver_id > cient_ver_id)

min_ver_id = client_ver_id;

}

while (!Queue.empty() &&

Queue.front().second < min_ver_id) Queue.pop();

}

...

};

Page 63: Практика Lock-free. RealTime-сервер

63

Класс очереди версий (part 3)

class Versions_queue {

...

public:

Versions_queue()

: Client_id(0), Current_version_id(1) {

for (auto& version : Client_version_ids)

version = 0;

version_ptr new_version(new Version);

Queue.push(new_version);

Current = new_version.get();

}

int get_client_id() {

return Client_id++;

}

...

};

Page 64: Практика Lock-free. RealTime-сервер

64

Класс очереди версий (part 4)

class Versions_queue {

...

public:

...

Version* get_current_version(int client_id) {

int version_id = Current_version_id;

Client_version_ids[client_id] = version_id;

return Current;

}

...

};

Page 65: Практика Lock-free. RealTime-сервер

65

Класс очереди версий (part 5)

class Versions_queue {

...

public:

...

Version* create_new_version() {

cleanup_unused_versions();

version_ptr ret(new Version(*Current));

Queue.push(

version_info(ret, Current_version_id + 1));

return ret.get();

}

...

};

Page 66: Практика Lock-free. RealTime-сервер

66

Класс очереди версий (part 6)

class Versions_queue {

...

public:

...

void release_current_version(int client_id) {

Client_version_ids[client_id] = 0;

}

void release_new_version(Version* version) {

Current = version;

++Current_version_id;

}

};

Page 67: Практика Lock-free. RealTime-сервер

67

Класс сессии на запись

class Write_session {

Versions_queue* Queue;

Version* New_version;

public:

Write_session(Versions_queue* queue)

: Queue(queue)

, New_version(Queue->create_new_version()) { }

~Write_session() {

Queue->release_new_version(New_version);

}

Version* operator->() {

return New_version;

}

};

Page 68: Практика Lock-free. RealTime-сервер

68

Класс сессии на чтение

class Read_session {

Versions_queue* Queue;

int Client_id;

Version* Current_version;

public:

Read_session(Versions_queue* queue, int client_id)

: Queue(queue)

, Client_id(client_id)

, Current_version(Queue->get_current_version(Client_id))

{ }

~Read_session() {

Queue->release_current_version(Client_id);

}

Version* operator->() {

return Current_version;

}

};

Page 69: Практика Lock-free. RealTime-сервер

69

Использование сессии на запись

for (int doc = 0; doc < 1000; ++doc) {

Write_session session(&Queue);

for (int j = 0; j < 1000; ++j) {

session->Tree.add_node_data(

random_number(), doc);

}

}

Page 70: Практика Lock-free. RealTime-сервер

70

Использование сессии на чтение

int sum = 0;

int client_id = Queue.get_client_id();

for (int i = 0; i < 7200; ++i) {

set<int> docs;

Read_session session(&Queue, client_id);

for (int j = 0; j < 1000; ++j) {

int from = random_number();

int to = from + 10;

session->Tree.check_node(from, to, &docs);

}

sum += docs.size();

}

Page 71: Практика Lock-free. RealTime-сервер

71

Замеряем время работы треда

0

5

10

15

20

25

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30

Write

Read

Количество ядер

Page 72: Практика Lock-free. RealTime-сервер

72

• Введение

• Мотивировка

• Традиционный подход к lock-free

• Read Copy Update

• Версионный подход

• Большой пример

• Заключение

План

Page 73: Практика Lock-free. RealTime-сервер

73

• Lock-free позволяет достичь максимальной

производительности на многих ядрах

• Общие алгоритмы lock-free пока ещё сложны

• Конкретные подслучаи доступны для

реализации любому разработчику

• Предложенный подход позволяет легко

сделать lock-free RealTime-Server в модели

Writer + N x Reader

• Подход разграничивает Write / Read – можно

использовать message passing для Write и т.д.

Заключение

Page 74: Практика Lock-free. RealTime-сервер

Константин Облаков

Разработчик группы

обработки свежего контента

[email protected]

Спасибо за

внимание!