Конкурентные ассоциативные контейнеры

66
Concurrent mapS C++ Russia 2015 Maxim Khizhinsky

Upload: platonov-sergey

Post on 08-Aug-2015

425 views

Category:

Software


1 download

TRANSCRIPT

Concurrent mapS

C ++ Russia 2015Maxim Khizhinsky

Concurrent mapsConcurrent maps

LIBCDS

Максим Хижинский, C++ Russia 2015

Операции:● insert( key, value )● erase( key )● find( key )

План:● Concurrent maps изнутри● Управление памятью в lock-free структурах● Lock-free ordered list и concurrent maps на его основе● Немного о деревьях

Hash tableHash table

LIBCDS

Максим Хижинский, C++ Russia 2015

0 1 2 3 4 5 6

X X Xk1

k

k

k k

k

kk

k

k

2

3

4 5

6

7

8

9

T[7]

T[i] = std::hash( key ) % 7

Списокколлизий

Striped mapStriped map

LIBCDS

Максим Хижинский, C++ Russia 2015

0 1 2 3 4 5 6

X X X

k1

k

k

k k

k

kk

k

k

2

3

4 5

6

7

8

9

T[7]

T[i] = std::hash( key ) % 7

Striped mapStriped map

LIBCDS

Максим Хижинский, C++ Russia 2015

0 1 2 3 4 5 6

X X X

k1

k

k

k k

k

kk

k

k

2

3

4 5

6

7

8

9

T[7]

T[i] = std::hash( key ) % 7

Lock[7]

Striped map: rehashStriped map: rehash

LIBCDS

Максим Хижинский, C++ Russia 2015

1. for ( i = 0; i < L; i++ ) Lock[i].lock();

2. N := 2 * N;

3. rehash T

4. for ( i := L - 1; i >=0; --i ) Lock[i].unlock();

L — constInitial: L = N

Rehash: N := 2N

Striped map: rehashStriped map: rehash

LIBCDS

Максим Хижинский, C++ Russia 2015

0 1 2 3 4 5 6

X X

k1k

k

k k kkk

kk

2

3

4 5 67

89

T[8]

Lock[i] = std::hash( key ) % 4

Lock[4]

7

T[j] = std::hash( key ) % 8

Striped mapStriped map

LIBCDS

Максим Хижинский, C++ Russia 2015

Lock[i] = std::hash( key ) % L

Lock[L]

α β γ δ

Tree search

Lock-free hash tableLock-free hash table

LIBCDS

Максим Хижинский, C++ Russia 2015

0 1 2 3 4 5 6

X X Xk1

k

k

k k

k

kk

k

k

2

3

4 5

6

7

8

9

T[8]

Lock-freeсписок

коллизий

7

k10

Lock-free ordered listLock-free ordered list

LIBCDS

Максим Хижинский, C++ Russia 2015

Операции:

● insert( node )● erase( key )● find( key )

template <class T>struct node { std::atomic<node*> next_; T data_;};

H T52 8

Lock-free примитивы:● atomic load/store● atomic compare-and-swap (CAS)

CAS — compare-and-swapCAS — compare-and-swap

LIBCDS

Максим Хижинский, C++ Russia 2015

template <typename T>bool CAS( T * pAtomic, T expected, T desired )atomically { if ( *pAtomic == expected ) { *pAtomic = desired; return true;

} else return false;};

Lock-free list: insertLock-free list: insert

LIBCDS

Максим Хижинский, C++ Russia 2015

H T52 8

3

H T52 8

3

3. prev->next_.CAS( next, new_node )

1. find insert position for key 3

2. new_node.next_.store( next )

H T52 8prev next

new_node

Lock-free list: eraseLock-free list: erase

LIBCDS

Максим Хижинский, C++ Russia 2015

1. find key 3

2. prev->next_.CAS( found, next )

H T52 8

prev

3

found

H T52 83

Проблема: параллельный insert

next

Lock-free list: insert/eraseLock-free list: insert/erase

LIBCDS

Максим Хижинский, C++ Russia 2015

A: find key 3

H T52 8prev

3found

B: find insert pos for key 4

iprev inextA: erase key 3

H T52 83

prev->next_.CAS( found, next )

next

B: insert key 4

H T52 83

4

iprev->next_.CAS( inext, new_item )

local vars

Marked pointerMarked pointer

LIBCDS

Максим Хижинский, C++ Russia 2015

[ T.Harris, 2001 ]

Двухфазное удаление:● Логическое удаление — помечаем элемент● Физическое удаление — исключаем элемент

В качестве метки используем младший бит указателя

Lock-free list: marked pointerLock-free list: marked pointer

LIBCDS

Максим Хижинский, C++ Russia 2015

H T52 8prev

3foundiprev inext

nextA: eraseB: insert

A: Logical deletion - mark item found

H T52 83

found->next_.CAS( next, next | 1 )B: iprev->next_.CAS( inext, new_item ) - failed!!!

A: Physical deletion - remove item found

H T52 83

prev->next_.CAS( found, next )

Lock-free list: problemsLock-free list: problems

LIBCDS

Максим Хижинский, C++ Russia 2015

H T52 8

prev

3

foundiprev inext

nextA: eraseB: insert

iprev->next_.CAS( inext, new_item )

prev->next_.CAS( found, next )

local vars

Вдруг уже удалены?..

Lock-free list: problemsLock-free list: problems

LIBCDS

Максим Хижинский, C++ Russia 2015

Проблемы:

● Защита локальных данных — когда элемент можно безопасно удалить?

● ABA-проблема

ABA-проблемаABA-проблема

LIBCDS

Максим Хижинский, C++ Russia 2015

52

prev

3

found next

Thread A: erase(3) Thread B

52 3

erase(3); erase(5)

2 3insert(4)

Heap

new node(4) alloc

delete

42

preempted...

42

prev found next

5

addr(3) == addr(4)

prev->next_.CAS( found, next ) - success!!!

2 мусор

SMRSMR

LIBCDS

Максим Хижинский, C++ Russia 2015

Проблемы:● Защита локальных данных — когда элемент можно

безопасно удалить?● ABA-проблема

Решение: Safe memory reclamation (SMR)● Tagged pointers● Hazard Pointers● User-space RCU

Tagged pointersTagged pointers

LIBCDS

Максим Хижинский, C++ Russia 2015

pointer tag

prev->next_.dwCAS( found, <next.ptr, prev->next_.tag + 1> )

template <class T>struct tagged_ptr { T * ptr; uintptr_t tag;};

Требует dwCAS — не везде есть Решает только ABA-проблему

Освободить память нельзя, нужен free-list

[ boost.lock-free ]

H T52 8prev

3found next

Tagged pointers: historyTagged pointers: history

LIBCDS

Максим Хижинский, C++ Russia 2015

ABA-проблема характерна только для CAS

Архитектуры процессоров

LL/SC: ● IBM PowerPC● MIPS● ARM

➢ LL — load linked➢ SC — store conditional

bool weak_CAS( T * ptr, T expected, T desired ){ T cur = LL( ptr ); return cur == expected && SC( ptr, desired );}

Эмуляция LL/SC на CAS — намного труднее

CAS: ● x86, amd64● Sparc● Itanium

С++11 — только CAS

Hazard pointersHazard pointers

LIBCDS

Максим Хижинский, C++ Russia 2015

✔ Использует только атомарные чтение/запись Защищает только локальные ссылки✔ Размер массива отложенных (готовых к

удалению) элементов ограничен сверху Перед работой с указателем его следует

объявить как hazard

решает ABA-проблему Физическое удаление элементов

Hazard pointersHazard pointers

LIBCDS

Максим Хижинский, C++ Russia 2015

H T52 8prev

3found next

erase( Key k ) { hp_guard h1 = get_guard(); hp_guard h2 = get_guard();retry: node * prev = Head; do { node * found = h2.protect( prev->next_); if ( found->key == k ) if (prev->next_.CAS( found, found->next_)) { hp_retire( found ); return true; } else goto retry; h1 = h2; prev = found; } while ( found->key < k ); return false; }

Распределяем HP (TLS)

Защищаем элемент

Удаляем элемент

Hazard pointersHazard pointers

LIBCDS

Максим Хижинский, C++ Russia 2015

P – thread count

Thread 0

Thread HP Manager

0

1

K - 1

HP[K]0

1

2

R - 1

Retired[R]

Hazard Pointer Singleton

Thread 1

Thread P - 1

K = 4 R = 2 KP

<K,P, R> : R > K * P

Hazard PointersHazard Pointers

LIBCDS

Максим Хижинский, C++ Russia 2015

Объявление Hazard Pointer'а – защита локальной ссылки

class hp_guard {

void * hp;

// ...

};

T * hp_guard::protect(

std::atomic<T*>& what) {

T * t;

do {

hp = t = what.load(std::memory_order_relaxed);

} while (t != what.load(std::memory_order_acquire));

return t;

}

0

1

K - 1

HP[K]

Hazard PointersHazard Pointers

LIBCDS

Максим Хижинский, C++ Russia 2015

Удаление элемента

void hp_retire( T * what ) {

push what to current_thread.Retired array

if ( current_thread.Retired is full )

hp.Scan( current_thread );

}

void hp::Scan() {

void * guarded[K*P] = union HP[K] for all P thread;

foreach ( p in current_thread.Retired[R] )

if ( p not in guarded[] )

delete p;

}

<K,P, R> : R > K * P

012…

R - 1

Retired[R]

01…

K - 1

HP[K]

User-space Read-Copy UpdateUser-space Read-Copy Update

LIBCDS

Максим Хижинский, C++ Russia 2015

✔ RCU — метод синхронизации: RCU.lock() / RCU.unlock()

✔ Разработан для почти-read-only данных (map, set)✔ Очень легкие read-side lock✔ Удаление элемента — ожидание окончания эпохи

решает ABA-проблему Физическое удаление элементов

RCU.lock(): ничего не блокируетОбъявляет, что поток входит в текущую RCU-эпоху

User-space RCUUser-space RCU

LIBCDS

Максим Хижинский, C++ Russia 2015

Map.find( ... );

Set.insert( ... );

Map.find( ... );

Map.erase( ... )...

Thread 1

Set.find( ... );

Map.insert( ... );

Set.find( ... );

Set.insert( ... );

Thread N

Эпоха 1

RCU.sync() - ждем, пока все потоки покинут эпоху 1

Set.find( ... );

Map.insert( ... );

Set.find( ... );

Set.insert( ... );

... Map.erase;

Map.find( ... );

Set.insert( ... );

Map.find( ... );

++Эпоха

Эпоха 2

Lock-free hash tableLock-free hash table

LIBCDS

Максим Хижинский, C++ Russia 2015

0 1 2 3 4 5 6

X X Xk1

k

k

k k

k

kk

k

k

2

3

4 5

6

7

8

9

T[8]

Lock-freecписок:HP/RCU

+ marked pointers

7

k10

Hash table + Lock-free ordered list

No rehashing

Split-ordered listSplit-ordered list

LIBCDS

Максим Хижинский, C++ Russia 2015

Rehashing приводит к перераспределению элементов между buckets

0 1

2

4

6

3

5

0 1 2 3

2 354

6

Key % 2 Key % 4

Вместо того, чтобы перемещать элементы между buckets, будем перемещать buckets между элементами

[ Nir Shavit, 2003 ]

Split-ordered listSplit-ordered list

LIBCDS

Максим Хижинский, C++ Russia 2015

0 8 2 1 9 13T[0]

T[1]

k iSentinel node Regular node

N = 2 size() = 4

Load factor L: size() / N ≤ L

Если L = 2, то вставка нового элемента приводит к увеличениюhash table

Hash table

Lock-free ordered list

Split-ordered listSplit-ordered list

LIBCDS

Максим Хижинский, C++ Russia 2015

0 8 2 1 9 13T[0]

T[1]

k iSentinel node Regular node

N = 4

Hash table

Insert

T[2] = null

T[3] = null

10

T[i]: i = hash % N

10 % 4 = 2 — T[2] не инициализирован

Нужно вставить новый sentinel node

Куда вставлять?..

2

10

Split-ordered listSplit-ordered list

LIBCDS

Максим Хижинский, C++ Russia 2015

0 8 2 1 9 13T[0]

T[1]

k iSentinel node Regular node

N = 4

Hash table

Insert

T[2] = null

T[3] = null

10 2

Split bucket

Bucket = hash % 2m + 1

Parent_bucket Parent_bucket + 2m

= 10 % 4

10 % 4, m = 12

Parent_bucket = 0

Split-ordered listSplit-ordered list

LIBCDS

Максим Хижинский, C++ Russia 2015

0 8 2 1 9 13T[0]

T[1]

k iSentinel node Regular node

N = 4

Hash table

Insert

T[2] = null

T[3] = null

10 2

Split bucket

Bucket = hash % 2m + 1

Parent_bucket Parent_bucket + 2m

= 10 % 4

10 % 4, m = 12

Parent_bucket = 0

0000 1000 0010 0001 1001 1101

0010

Арифметикапо модулю

2p

Split-ordered listSplit-ordered list

LIBCDS

Максим Хижинский, C++ Russia 2015

0 8 2 1 9 13T[0]

T[1]

k iSentinel node Regular node

N = 4

Hash table

Insert

T[2] = null

T[3] = null

10 2

Split bucket

Bucket = hash % 2m + 1

Parent_bucket Parent_bucket + 2m

= 10 % 4

10 % 4, m = 12

Parent_bucket = 0

0000 0001 0100 1000 1001 1011

0100

Инвертируем

порядок бит!

Отсортирован!

Split-ordered listSplit-ordered list

LIBCDS

Максим Хижинский, C++ Russia 2015

template <class Key, class T>struct split_list_node { Key key; uint key_hash; // = std::hash(key) uint shah; // = invert( key_hash ) - для сортировки T data;};

Уже есть!!!

parent_bucket

Split-ordered listSplit-ordered list

LIBCDS

Максим Хижинский, C++ Russia 2015

0 8 2 1 9 13T[0]

T[1]

k iSentinel node Regular node

Hash table

Insert

T[2] = null

T[3] = null

2

0000 0 0001 1 0100 1 1000 0 1001 1 1011 1

Надо различать sentinel и regular node.

msb(key_hash) = 1 — regular → lsb( shah ) = 1

msb(key_hash) = 0 — sentinel →lsb( shah ) = 0

0100 0

parent_bucket

Split-ordered listSplit-ordered list

LIBCDS

Максим Хижинский, C++ Russia 2015

0 8 2 1 9 13

T[0]

T[1]

k iSentinel node Regular node

Hash table

Insert

T[2]

T[3] = null

2

0000 0 0001 1 0100 1 1000 0 1001 1 1011 1

0100 0

2

Split-ordered listSplit-ordered list

LIBCDS

Максим Хижинский, C++ Russia 2015

0 8 2 1 9 13

T[0]

T[1]

k iSentinel node Regular node

Hash table

Insert

T[2]

T[3] = null

0000 0 0001 1 0100 1 1000 0 1001 1 1011 10100 0

2

10

0101 1

0101 1

10

Bucket = 10 % 4 = 2

Split-ordered listSplit-ordered list

LIBCDS

Максим Хижинский, C++ Russia 2015

Удаление — как из обычного lock-free списка

Sentinel node никогда не удаляются

1: Logical deletion - mark item

52 83

found->next_.CAS( next, next | 1 )

2: Physical deletion - unlink item

52 83

Split-ordered listSplit-ordered list

LIBCDS

Максим Хижинский, C++ Russia 2015

T[0]

T[1]

. . . .

T[N-1]

T[N]

seg0

seg1

. . . .

null

Segment[M]

T[0]

T[1]

. . . .

T[N-1]

T[N]

T[0]

T[1]

. . . .

T[N-1]

T[N]

Статический массив

сегментов

Сегментная организация hash table

Сегмент – старшие биты хеша T[i] – младшие N бит хеша

Динамическое расширение

Lock-free ordered listLock-free ordered list

LIBCDS

Максим Хижинский, C++ Russia 2015

X10 15 23 34 5542

Complexity O(N)

Что ещё можно сделать из lock-free ordered list?

✔ Простой hash map — без расширения ✔ Split-ordered list

find(34)

Lock-free ordered listLock-free ordered list

LIBCDS

Максим Хижинский, C++ Russia 2015

X10 15 23 34 5542

Complexity O(N/2)

X

Lock-free ordered listLock-free ordered list

LIBCDS

Максим Хижинский, C++ Russia 2015

X10 15 23 34 5542

Complexity O(N/4)

X

X

Skip listSkip list

LIBCDS

Максим Хижинский, C++ Russia 2015

X

X

X

X

X

X

X

X10 15 23 34 5542

Head Tail

[ W.Pugh, 1990 ]

Skip listSkip list

LIBCDS

Максим Хижинский, C++ Russia 2015

XXXXXXXX10 15 23 34 5542

23

Tower

h = 3

Вероятностная структура данных:

P[ h == 1 ] = 1/2

P[ h == k ] = 1/2k, 0 < k < 32

h = lsb( rand() )

O(log(N))

Skip list: insertSkip list: insert

LIBCDS

Максим Хижинский, C++ Russia 2015

X

X

X

X

X

X

X

X10 15 23 34 5542

Head Tail

Поиск позиции: формируем массив prev[]

Skip list: insertSkip list: insert

LIBCDS

Максим Хижинский, C++ Russia 2015

X

X

X

X

X

X

X

X10 15 23 34 5542

Head Tail

Связываем в список на уровне 0

Skip list: insertSkip list: insert

LIBCDS

Максим Хижинский, C++ Russia 2015

X

X

X

X

X

X

X

X10 15 23 34 5542

Head Tail

Связываем в список на уровне 1, 2, …снизу вверх

Skip list: eraseSkip list: erase

LIBCDS

Максим Хижинский, C++ Russia 2015

X

X

X

X

X

X

X

X10 15 23 34 5542

Head Tail

Двухфазное: 1. Logical deletion — markсверху вниз

Skip list: eraseSkip list: erase

LIBCDS

Максим Хижинский, C++ Russia 2015

X

X

X

X

X

X

X

X10 15 23 34 5542

Head Tail

Фаза 2. Physical deletion — unlinkсверху вниз

Skip listSkip list

LIBCDS

Максим Хижинский, C++ Russia 2015

Особенности:

Отсортированный контейнер get_max(), get_min() Можно использовать как priority queue Сложность поиска: O(log N)

Hazard Pointer: для max height = 32 требует min 64 hazard pointer'ов Требует эффективной реализации tower

Binary search treeBinary search tree

LIBCDS

Максим Хижинский, C++ Russia 2015

«Прямой» lock-free подход приводит к очень сложнымалгоритмам.Non-blocking подход дает менее сложные алгоритмы

Routing nodes - только ключи

Leaf nodes - данные

Leaf-oriented tree

Основная цель: lock-free / wait-free find()

Binary search treeBinary search tree

LIBCDS

Максим Хижинский, C++ Russia 2015

10

5 20

3015

27 45

Thread A: erase(15)Thread B: erase(27)

10

5 20

3015

27 45

Error!!!Достижимый

узел

Конкурентное CAS-based удаление

CAS

CAS

Должно быть

Binary search treeBinary search tree

LIBCDS

Максим Хижинский, C++ Russia 2015

10

5 20

3015

27 45

Thread A: erase(27)Thread B: insert(29)

10

5 20

3015

27

45

Error!!!Недостижимые

узлы

Конкурентное CAS-based удаление + вставка

CAS

29

29

Binary search treeBinary search tree

LIBCDS

Максим Хижинский, C++ Russia 2015

Routing node

Left child Right child

State

Key

IFlag DFlag

MarkClean (default)

Binary search treeBinary search tree

LIBCDS

Максим Хижинский, C++ Russia 2015

10

5 20

3015

27 45

Insert 291 30.State.CAS( Clean, IFlag)

10

5 20

3015

27

4527

29

2 Insert30.left.CAS( ) 27 27

10

5 20

3015

27

4527

29

3 30.State.CAS( IFlag, Clean )

Binary search treeBinary search tree

LIBCDS

Максим Хижинский, C++ Russia 2015

10

5 20

3015

27 45

Erase 27

1 20.State.CAS( Clean, DFlag)

10

5 20

3015

27 45

2 Mark 30 30.State.CAS( Clean, Mark)

10

5 20

3015

27 45

3 Delete 20.right.CAS( ) 30 45

10

5 20

3015

27 45

4 20.State.CAS(Dflag, Clean)

Binary search treeBinary search tree

LIBCDS

Максим Хижинский, C++ Russia 2015

10

5 20

3015

27 45

Thread A: erase(15)Thread B: erase(27)

10

5 20

3015

27 45

Error!!!Достижимый

узел

Конкурентное CAS-based удаление

CAS

CAS

Должно быть

Конкуренция

Binary search treeBinary search tree

LIBCDS

Максим Хижинский, C++ Russia 2015

10

5 20

3015

27 45

Thread A: erase(15)Thread B: erase(27)

Конкурентное CAS-based удаление

A

BA

10

5 20

3015

27 45

A

A

Выиграл A

10

5 20

3015

27 45

AВыиграл B

B

B

B: повтор поиска 27

A: unflag + повтор поиска 15

Binary search treeBinary search tree

LIBCDS

Максим Хижинский, C++ Russia 2015

find() - lock-freeinsert(), erase() - non-blocked

Характеристики:

Сложность:

O(log(N)) — для случайных ключейO(N) — в худшем случае

libcdslibcds

LIBCDS

Максим Хижинский, C++ Russia 2015

https://github.com/khizmax/libcds

Все это, а также многое другое, можно найти здесь:

PerformancePerformance

LIBCDS

Максим Хижинский, C++ Russia 2015

Intel Dual Xeon X5670 2.93 GHz 12 cores 24 threads / 24 GB RAM

Questions?..Questions?..

Максим Хижинский, C++ Russia 2015

Concurrent mapsConcurrent maps

Максим Хижинский, C++ Russia 2015

Спасибо за внимание[email protected]

https://github.com/khizmax/libcds