Concurrent mapS
C++ Russia 2015Maxim Khizhinsky
Lock-free hash tableLock-free hash table
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[8]
Lock-free
список
коллизий
7
k
10
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 8
prev 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 8
prev
3
found
B: find insert pos for key 4
iprev inext
A: 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 8
prev
3
found
iprev inext
nextA: erase
B: 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
found
iprev inext
nextA: erase
B: 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 3
insert(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 8
prev
3
found 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 8
prev
3
found 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();
} while (t != what.load());
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
0
1
2
…
R - 1
Retired[R]
0
1
…
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 X
k1
k
k
k k
k
kk
k
k
2
3
4 5
6
7
8
9
T[8]
Lock-free
cписок:
HP/RCU
+ marked
pointers
7
k
10
Hash table + Lock-free ordered list
No rehashing
Split-ordered listSplit-ordered list
LIBCDS
Максим Хижинский, C++ Russia 2015
0 8 2 1 9 13
T[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
Skip listSkip list
LIBCDS
Максим Хижинский, C++ Russia 2015
X
X
X
X
X
X
X
X10 15 23 34 5542
23
Tower
h = 3
Вероятностная структура данных:
P[ h == 1 ] = 1/2
P[ h == k ] = 1/2
k
, 0 < k < 32
h = lsb( rand() )
O(log(N))
PerformancePerformance
LIBCDS
Максим Хижинский, C++ Russia 2015
Intel Dual Xeon X5670 2.93 GHz 12 cores 24 threads / 24 GB RAM
Concurrent mapsConcurrent maps
Максим Хижинский, C++ Russia 2015
Спасибо за внимание!
libcds.dev@gmail.com
https://github.com/khizmax/libcds

Максим Хижинский Lock-free maps

  • 1.
    Concurrent mapS C++ Russia2015Maxim Khizhinsky
  • 2.
    Lock-free hash tableLock-freehash table 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[8] Lock-free список коллизий 7 k 10
  • 3.
    Lock-free ordered listLock-freeordered 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)
  • 4.
    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; };
  • 5.
    Lock-free list: insertLock-freelist: 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 8 prev next new_node
  • 6.
    Lock-free list: eraseLock-freelist: 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
  • 7.
    Lock-free list: insert/eraseLock-freelist: insert/erase LIBCDS Максим Хижинский, C++ Russia 2015 A: find key 3 H T52 8 prev 3 found B: find insert pos for key 4 iprev inext A: 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
  • 8.
    Marked pointerMarked pointer LIBCDS МаксимХижинский, C++ Russia 2015 [ T.Harris, 2001 ] Двухфазное удаление: ● Логическое удаление — помечаем элемент ● Физическое удаление — исключаем элемент В качестве метки используем младший бит указателя
  • 9.
    Lock-free list: markedpointerLock-free list: marked pointer LIBCDS Максим Хижинский, C++ Russia 2015 H T52 8 prev 3 found iprev inext nextA: erase B: 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 )
  • 10.
    Lock-free list: problemsLock-freelist: problems LIBCDS Максим Хижинский, C++ Russia 2015 H T52 8 prev 3 found iprev inext nextA: erase B: insert iprev->next_.CAS( inext, new_item ) prev->next_.CAS( found, next ) local vars Вдруг уже удалены?..
  • 11.
    Lock-free list: problemsLock-freelist: problems LIBCDS Максим Хижинский, C++ Russia 2015 Проблемы: ● Защита локальных данных — когда элемент можно безопасно удалить? ● ABA-проблема
  • 12.
    ABA-проблемаABA-проблема LIBCDS Максим Хижинский, C++Russia 2015 52 prev 3 found next Thread A: erase(3) Thread B 52 3 erase(3); erase(5) 2 3 insert(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 мусор
  • 13.
    SMRSMR LIBCDS Максим Хижинский, C++Russia 2015 Проблемы: ● Защита локальных данных — когда элемент можно безопасно удалить? ● ABA-проблема Решение: Safe memory reclamation (SMR) ● Tagged pointers ● Hazard Pointers ● User-space RCU
  • 14.
    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 8 prev 3 found next
  • 15.
    Tagged pointers: historyTaggedpointers: 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
  • 16.
    Hazard pointersHazard pointers LIBCDS МаксимХижинский, C++ Russia 2015 ✔ Использует только атомарные чтение/запись  Защищает только локальные ссылки ✔ Размер массива отложенных (готовых к удалению) элементов ограничен сверху  Перед работой с указателем его следует объявить как hazard решает ABA-проблему Физическое удаление элементов
  • 17.
    Hazard pointersHazard pointers LIBCDS МаксимХижинский, C++ Russia 2015 H T52 8 prev 3 found 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) Защищаем элемент Удаляем элемент
  • 18.
    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
  • 19.
    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(); } while (t != what.load()); return t; } 0 1 … K - 1 HP[K]
  • 20.
    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 0 1 2 … R - 1 Retired[R] 0 1 … K - 1 HP[K]
  • 21.
    User-space Read-Copy UpdateUser-spaceRead-Copy Update LIBCDS Максим Хижинский, C++ Russia 2015 ✔ RCU — метод синхронизации: RCU.lock() / RCU.unlock() ✔ Разработан для почти-read-only данных (map, set) ✔ Очень легкие read-side lock ✔ Удаление элемента — ожидание окончания эпохи решает ABA-проблему Физическое удаление элементов RCU.lock(): ничего не блокирует Объявляет, что поток входит в текущую RCU-эпоху
  • 22.
    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
  • 23.
    Lock-free hash tableLock-freehash table 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[8] Lock-free cписок: HP/RCU + marked pointers 7 k 10 Hash table + Lock-free ordered list No rehashing
  • 24.
    Split-ordered listSplit-ordered list LIBCDS МаксимХижинский, C++ Russia 2015 0 8 2 1 9 13 T[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
  • 25.
    Skip listSkip list LIBCDS МаксимХижинский, C++ Russia 2015 X X X X X X X X10 15 23 34 5542 23 Tower h = 3 Вероятностная структура данных: P[ h == 1 ] = 1/2 P[ h == k ] = 1/2 k , 0 < k < 32 h = lsb( rand() ) O(log(N))
  • 26.
    PerformancePerformance LIBCDS Максим Хижинский, C++Russia 2015 Intel Dual Xeon X5670 2.93 GHz 12 cores 24 threads / 24 GB RAM
  • 27.
    Concurrent mapsConcurrent maps МаксимХижинский, C++ Russia 2015 Спасибо за внимание! libcds.dev@gmail.com https://github.com/khizmax/libcds