This commit is contained in:
Marko Budiselic 2015-12-07 21:52:08 +01:00
commit 36856f3bfb
7 changed files with 484 additions and 124 deletions

View File

@ -2,22 +2,26 @@
#include <algorithm>
#include <memory>
#include <cassert>
#include "threading/hazard_ptr.hpp"
#include "threading/sync/lockable.hpp"
#include "threading/sync/spinlock.hpp"
#include "utils/random/fast_binomial.hpp"
#include "memory/lazy_gc.hpp"
// concurrent skiplist based on the implementation described in
// "A Provably Correct Scalable Concurrent Skip List"
// https://www.cs.tau.ac.il/~shanir/nir-pubs-web/Papers/OPODIS2006-BA.pdf
template <class K, class T, size_t H=32, class lock_t=SpinLock>
class SkipList
class SkipList : LazyGC<SkipList<K, T, H, lock_t>>, Lockable<lock_t>
{
public:
static thread_local FastBinomial<H> rnd;
using data_t = std::pair<const K, T>;
struct Flags
{
enum node_flags : uint8_t {
@ -51,62 +55,20 @@ class SkipList
class Node : Lockable<lock_t>
{
public:
friend class SkipList;
public:
class reference
{
public:
reference() {}
reference(Node* node, bool store_hazard = true)
: node(node), hazard(store_hazard ? node : nullptr) {}
reference(const reference&) = delete;
reference(reference&& other)
: node(other.node), hazard(std::move(other.hazard))
{
other.node = nullptr;
}
reference& operator=(const reference& other) = delete;
reference& operator=(const reference&& other)
{
node = other.node;
other.node = nullptr;
hazard = std::move(other.hazard);
return *this;
}
T& operator*() { return *node; }
const T& operator*() const { return *node; }
T* operator->() { return node; }
const T* operator->() const { return node; }
operator T*() { return node; }
const operator T*() const { return node; }
const hazard_ptr& get_hazard() const
{
return hazard;
}
private:
hazard_ptr hazard;
Node* node;
};
K key;
T item;
data_t data;
const uint8_t height;
Flags flags;
static Node* create(K key, T item, uint8_t height)
static Node* create(const K& key, const T& item, uint8_t height)
{
return create({key, item}, height);
}
static Node* create(data_t&& data, uint8_t height)
{
// [ Node ][Node*][Node*][Node*]...[Node*]
// | | | | |
@ -120,7 +82,7 @@ class SkipList
// we have raw memory and we need to construct an object
// of type Node on it
return new (node) Node(key, item, height);
return new (node) Node(std::move(data), height);
}
static void destroy(Node* node)
@ -129,20 +91,19 @@ class SkipList
free(node);
}
Node::reference forward(size_t level) const
Node* forward(size_t level) const
{
while(true)
{
auto ref = Node::reference(load_tower(level));
return tower[level].load(std::memory_order_acquire);
}
if(ref.get_hazard() == load_tower(level))
return std::move(ref);
}
void forward(size_t level, Node* next)
{
tower[level].store(next, std::memory_order_release);
}
private:
Node(K key, T item, uint8_t height)
: key(key), item(item), height(height)
Node(data_t data, uint8_t height)
: data(data), height(height)
{
// here we assume, that the memory for N towers (N = height) has
// been allocated right after the Node structure so we need to
@ -157,16 +118,6 @@ class SkipList
tower[i].~atomic();
}
void forward(size_t level, Node* next)
{
tower[level].store(next, std::memory_order_release);
}
Node* load_tower(size_t level)
{
return tower[level].load(std::memory_order_acquire);
}
// this creates an array of the size zero. we can't put any sensible
// value here since we don't know what size it will be untill the
// node is allocated. we could make it a Node** but then we would
@ -176,52 +127,254 @@ class SkipList
std::atomic<Node*> tower[0];
};
using node_ref_t = typename Node::reference;
public:
class iterator
class ConstIterator
{
friend class SkipList;
ConstIterator(Node* node) : node(node) {}
public:
iterator() = default;
iterator(node_ref_t&& node) : node(std::move(node)) {}
iterator(const iterator&) = delete;
ConstIterator() = default;
ConstIterator(const ConstIterator&) = default;
T& operator*() { return node; }
T* operator->() { return node; }
operator T*() { return node; }
iterator& operator++()
const data_t& operator*()
{
assert(node != nullptr);
return node->data;
}
const data_t* operator->()
{
assert(node != nullptr);
return &node->data;
}
operator const data_t()
{
assert(node != nullptr);
return node->data;
}
ConstIterator& operator++()
{
assert(node != nullptr);
node = node->forward(0);
return *this;
}
iterator& operator++(int)
ConstIterator& operator++(int)
{
return operator++();
}
friend bool operator==(const ConstIterator& a, const ConstIterator& b)
{
return a.node == b.node;
}
friend bool operator!=(const ConstIterator& a, const ConstIterator& b)
{
return !(a == b);
}
private:
node_ref_t node;
Node* node {nullptr};
};
class Iterator
{
friend class SkipList;
Iterator(Node* node) : node(node) {}
public:
Iterator() = default;
Iterator(const Iterator&) = default;
data_t& operator*()
{
assert(node != nullptr);
return node->data;
}
data_t* operator->()
{
assert(node != nullptr);
return &node->data;
}
operator data_t()
{
assert(node != nullptr);
return node->data;
}
Iterator& operator++()
{
assert(node != nullptr);
node = node->forward(0);
return *this;
}
Iterator& operator++(int)
{
return operator++();
}
friend bool operator==(const Iterator& a, const Iterator& b)
{
return a.node == b.node;
}
friend bool operator!=(const Iterator& a, const Iterator& b)
{
return !(a == b);
}
private:
Node* node {nullptr};
};
SkipList() : header(Node::create(K(), T(), H)) {}
auto begin() { return iterator(header->forward(0)); }
auto end() { return iterator(); }
friend class Accessor;
class Accessor
{
friend class SkipList;
Accessor(SkipList& skiplist) : skiplist(skiplist) {}
public:
Accessor(const Accessor&) = delete;
Accessor(Accessor&&) = default;
Iterator begin()
{
return skiplist.begin();
}
ConstIterator begin() const
{
return skiplist.cbegin();
}
ConstIterator cbegin() const
{
return skiplist.cbegin();
}
Iterator end()
{
return skiplist.end();
}
ConstIterator end() const
{
return skiplist.cend();
}
ConstIterator cend() const
{
return skiplist.cend();
}
std::pair<Iterator, bool> insert_unique(const K& key, const T& item)
{
return skiplist.insert({key, item}, preds, succs);
}
std::pair<Iterator, bool> insert_unique(const K& key, T&& item)
{
return skiplist.insert({key, std::move(item)}, preds, succs);
}
ConstIterator find(const K& key) const
{
return skiplist.find(key);
}
Iterator find(const K& key)
{
return skiplist.find(key);
}
bool remove(const K& key)
{
return skiplist.remove(key, preds, succs);
}
private:
SkipList& skiplist;
Node* preds[H], *succs[H];
};
Accessor access()
{
return Accessor(*this);
}
private:
using guard_t = std::unique_lock<lock_t>;
Iterator begin()
{
return Iterator(header->forward(0));
}
ConstIterator begin() const
{
return ConstIterator(header->forward(0));
}
ConstIterator cbegin() const
{
return ConstIterator(header->forward(0));
}
Iterator end()
{
return Iterator();
}
ConstIterator end() const
{
return ConstIterator();
}
ConstIterator cend() const
{
return ConstIterator();
}
size_t size() const
{
return count.load(std::memory_order_acquire);
}
iterator find(const K& key)
bool greater(const K& key, const Node* const node)
{
auto pred = node_ref_t(header);
node_ref_t node;
return node && key > node->data.first;
}
uint8_t h = pred->height - 1;
bool less(const K& key, const Node* const node)
{
return (node == nullptr) || key < node->data.first;
}
ConstIterator find(const K& key) const
{
return find_node<ConstIterator>(key);
}
Iterator find(const K& key)
{
return find_node<Iterator>(key);
}
template <class It>
It find_node(const K& key)
{
Node* node, *pred = header;
int h = static_cast<int>(pred->height) - 1;
while(true)
{
@ -230,7 +383,7 @@ public:
// if we overshoot at every layer, item doesn't exist
if(h < 0)
return iterator();
return It();
// the item is farther to the right, continue going right as long
// as the key is greater than the current node's key
@ -238,47 +391,169 @@ public:
pred = node, node = node->forward(h);
// check if we have a hit. if not, we need to descend down again
if(!less(key, node))
return iterator(std::move(node));
if(!less(key, node) && !node->flags.is_marked())
return It(node);
}
}
private:
std::atomic<size_t> count {0};
Node* header;
bool greater(const K& key, const Node* const node)
int find_path(Node* from, int start, const K& key,
Node* preds[], Node* succs[])
{
return node && key > node->key;
}
bool less(const K& key, const Node* const node)
{
return (node == nullptr) || key < node->key;
}
int find_path(Node& from, int start, const K& key,
node_ref_t (preds&)[],
node_ref_t (succs&)[])
{
int lfound = -1;
int level_found = -1;
Node* pred = from;
for(int level = start_level; level >= 0; --level)
for(int level = start; level >= 0; --level)
{
Node* node = pred->forward(level);
while(greater(key, node))
pred = node, node = pred->forward(level);
if(lfound == -1 && !less(key, node))
lfound = level;
if(level_found == -1 && !less(key, node))
level_found = level;
preds[level] = pred;
succs[level] = node;
}
return lfound;
return level_found;
}
template <bool ADDING>
bool lock_nodes(uint8_t height, guard_t guards[],
Node* preds[], Node* succs[])
{
Node *prepred, *pred, *succ = nullptr;
bool valid = true;
for(int level = 0; valid && level < height; ++level)
{
pred = preds[level], succ = succs[level];
if(pred != prepred)
guards[level] = pred->acquire_unique(), prepred = pred;
valid = !pred->flags.is_marked() && pred->forward(level) == succ;
if(ADDING)
valid = valid && (succ == nullptr || !succ->flags.is_marked());
}
return valid;
}
std::pair<Iterator, bool>
insert(data_t&& data, Node* preds[], Node* succs[])
{
while(true)
{
auto level = find_path(header, H - 1, data.first, preds, succs);
if(level != -1)
{
auto found = succs[level];
if(found->flags.is_marked())
continue;
while(!found->flags.is_fully_linked())
usleep(250);
return {Iterator {succs[level]}, false};
}
auto height = rnd();
guard_t guards[H];
// try to acquire the locks for predecessors up to the height of
// the new node. release the locks and try again if someone else
// has the locks
if(!lock_nodes<true>(height, guards, preds, succs))
continue;
// you have the locks, create a new node
auto new_node = Node::create(std::move(data), height);
// link the predecessors and successors, e.g.
//
// 4 HEAD ... P ------------------------> S ... NULL
// 3 HEAD ... ... P -----> NEW ---------> S ... NULL
// 2 HEAD ... ... P -----> NEW -----> S ... ... NULL
// 1 HEAD ... ... ... P -> NEW -> S ... ... ... NULL
for(uint8_t level = 0; level < height; ++level)
{
new_node->forward(level, succs[level]);
preds[level]->forward(level, new_node);
}
new_node->flags.set_fully_linked();
count.fetch_add(1, std::memory_order_relaxed);
return {Iterator {new_node}, true};
}
}
bool ok_delete(Node* node, int level)
{
return node->flags.is_fully_linked()
&& node->height - 1 == level
&& !node->flags.is_marked();
}
bool remove(const K& key, Node* preds[], Node* succs[])
{
Node* node = nullptr;
guard_t node_guard;
bool marked = false;
int height = 0;
while(true)
{
auto level = find_path(header, H - 1, key, preds, succs);
if(!marked && (level == -1 || !ok_delete(succs[level], level)))
return false;
if(!marked)
{
node = succs[level];
height = node->height;
node_guard = node->acquire_unique();
if(node->flags.is_marked())
return false;
node->flags.set_marked();
}
guard_t guards[H];
if(!lock_nodes<false>(height, guards, preds, succs))
continue;
for(int level = height - 1; level >= 0; --level)
preds[level]->forward(level, node->forward(level));
// TODO recycle(node)
count.fetch_sub(1, std::memory_order_relaxed);
return true;
}
}
guard_t gc_lock_acquire()
{
return this->acquire_unique();
}
void vacuum()
{
}
std::atomic<size_t> count {0};
Node* header;
};
template <class K, class T, size_t H, class lock_t>
thread_local FastBinomial<H> SkipList<K, T, H, lock_t>::rnd;

Binary file not shown.

View File

@ -2,9 +2,52 @@
#include "data_structures/skiplist/skiplist.hpp"
using std::cout;
using std::endl;
using skiplist_t = SkipList<int, int>;
void print_skiplist(const skiplist_t::Accessor& skiplist)
{
cout << "---- skiplist now has: ";
for(auto& kv : skiplist)
cout << "(" << kv.first << ", " << kv.second << ") ";
cout << "----" << endl;
}
int main(void)
{
auto skiplist = new SkipList<int, int>();
cout << std::boolalpha;
skiplist_t skiplist;
auto accessor = skiplist.access();
cout << "added non-existing (1, 10)? (true) "
<< accessor.insert_unique(1, 10).second << endl;
cout << "added already existing (1, 10)? (false) "
<< accessor.insert_unique(1, 10).second << endl;
accessor.insert_unique(2, 20);
print_skiplist(accessor);
cout << "value at key 3 exists? (false) "
<< (accessor.find(3) == accessor.end()) << endl;
cout << "value at key 2 exists? (true) "
<< (accessor.find(2) != accessor.end()) << endl;
cout << "at key 2 is? (20) " << accessor.find(2)->second << endl;
cout << "removed existing (1)? (true) " << accessor.remove(1) << endl;
cout << "removed non-existing (3)? (false) " << accessor.remove(3) << endl;
accessor.insert_unique(1, 10);
accessor.insert_unique(4, 40);
print_skiplist(accessor);
return 0;
}

View File

@ -0,0 +1,29 @@
#pragma once
#include "recycler.hpp"
template <class T, class Allocator>
class DeferredRecycler : Recycler<T, Allocator>
{
public:
using Recycler<T, Allocator>::acquire;
void recycle(T* item)
{
auto guard = this->acquire_unique();
dirty.push_back(item);
}
void clean()
{
auto guard = this->acquire_unique();
for(auto item : dirty)
this->recycle_or_delete(item);
dirty.clear();
}
private:
std::queue<T*> dirty;
};

View File

@ -33,6 +33,8 @@ public:
return;
this->derived().vacuum();
dirty.store(false, std::memory_order_release);
}
protected:

View File

@ -7,7 +7,7 @@
#include "threading/sync/spinlock.hpp"
template <class T, class Allocator=std::allocator<T>>
class Recycler : Lockable<SpinLock>
class Recycler : public Lockable<SpinLock>
{
static constexpr size_t default_max_reserved = 100;
@ -19,17 +19,28 @@ public:
T* acquire(Args&&... args)
{
auto guard = acquire_unique();
return new T(std::forward<Args>(args)...); // todo refactor :D
return fetch_or_create(std::forward<Args>(args)...);
}
void release(T* item)
{
auto guard = acquire_unique();
delete item; // todo refactor :D
return recycle_or_delete(item);
}
private:
protected:
Allocator alloc;
size_t max_reserved {default_max_reserved};
std::queue<T*> items;
template <class... Args>
T* fetch_or_create(Args&&... args)
{
return new T(std::forward<Args>(args)...); // todo refactor :D
}
void recycle_or_delete(T* item)
{
delete item; // todo refactor :D
}
};

View File

@ -17,7 +17,7 @@
* and the app is left in an inconsistent state. ideally, you would like
* to call resource.disable regardles of the exception being thrown.
* OnScopeExit makes this possible and very convenient via a 'Auto' macro
*
*
* void hard_worker()
* {
* resource.enable();