2015-11-23 04:35:40 +08:00
|
|
|
#pragma once
|
2015-06-23 21:17:10 +08:00
|
|
|
|
|
|
|
#include <algorithm>
|
2015-11-23 04:35:40 +08:00
|
|
|
#include <memory>
|
2015-06-23 21:17:10 +08:00
|
|
|
|
2015-11-23 04:35:40 +08:00
|
|
|
#include "threading/hazard_ptr.hpp"
|
|
|
|
#include "threading/sync/lockable.hpp"
|
|
|
|
#include "threading/sync/spinlock.hpp"
|
|
|
|
|
|
|
|
#include "utils/random/fast_binomial.hpp"
|
2015-06-23 21:17:10 +08:00
|
|
|
|
2015-06-28 17:43:52 +08:00
|
|
|
// 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
|
|
|
|
|
2015-11-23 04:35:40 +08:00
|
|
|
template <class K, class T, size_t H=32, class lock_t=SpinLock>
|
2015-06-25 09:06:00 +08:00
|
|
|
class SkipList
|
2015-06-23 21:17:10 +08:00
|
|
|
{
|
2015-11-23 04:35:40 +08:00
|
|
|
static thread_local FastBinomial<H> rnd;
|
2015-06-23 21:17:10 +08:00
|
|
|
|
2015-11-23 04:35:40 +08:00
|
|
|
struct Flags
|
2015-06-28 17:43:52 +08:00
|
|
|
{
|
2015-11-23 04:35:40 +08:00
|
|
|
enum node_flags : uint8_t {
|
|
|
|
MARKED = 0x01,
|
|
|
|
FULLY_LINKED = 0x10,
|
|
|
|
};
|
|
|
|
|
|
|
|
bool is_marked() const
|
2015-06-28 17:43:52 +08:00
|
|
|
{
|
2015-11-23 04:35:40 +08:00
|
|
|
return flags.load(std::memory_order_acquire) & MARKED;
|
2015-06-28 17:43:52 +08:00
|
|
|
}
|
2015-06-23 21:17:10 +08:00
|
|
|
|
2015-11-23 04:35:40 +08:00
|
|
|
void set_marked()
|
|
|
|
{
|
|
|
|
flags.fetch_or(MARKED, std::memory_order_release);
|
|
|
|
}
|
2015-06-23 21:17:10 +08:00
|
|
|
|
2015-11-23 04:35:40 +08:00
|
|
|
bool is_fully_linked() const
|
|
|
|
{
|
|
|
|
return flags.load(std::memory_order_acquire) & FULLY_LINKED;
|
|
|
|
}
|
2015-06-23 21:17:10 +08:00
|
|
|
|
2015-11-23 04:35:40 +08:00
|
|
|
void set_fully_linked()
|
|
|
|
{
|
|
|
|
flags.fetch_or(FULLY_LINKED, std::memory_order_release);
|
|
|
|
}
|
2015-06-23 21:17:10 +08:00
|
|
|
|
2015-11-23 04:35:40 +08:00
|
|
|
private:
|
|
|
|
std::atomic<uint8_t> flags {0};
|
|
|
|
};
|
2015-06-23 21:17:10 +08:00
|
|
|
|
2015-11-23 04:35:40 +08:00
|
|
|
class Node : Lockable<lock_t>
|
2015-06-23 21:17:10 +08:00
|
|
|
{
|
2015-11-23 04:35:40 +08:00
|
|
|
friend class SkipList;
|
2015-06-23 21:17:10 +08:00
|
|
|
|
2015-11-23 04:35:40 +08:00
|
|
|
public:
|
|
|
|
class reference
|
2015-06-28 17:43:52 +08:00
|
|
|
{
|
2015-11-23 04:35:40 +08:00
|
|
|
public:
|
|
|
|
reference() {}
|
2015-06-28 17:43:52 +08:00
|
|
|
|
2015-11-23 04:35:40 +08:00
|
|
|
reference(Node* node, bool store_hazard = true)
|
|
|
|
: node(node), hazard(store_hazard ? node : nullptr) {}
|
2015-06-28 17:43:52 +08:00
|
|
|
|
2015-11-23 04:35:40 +08:00
|
|
|
reference(const reference&) = delete;
|
2015-06-28 17:43:52 +08:00
|
|
|
|
2015-11-23 04:35:40 +08:00
|
|
|
reference(reference&& other)
|
|
|
|
: node(other.node), hazard(std::move(other.hazard))
|
|
|
|
{
|
|
|
|
other.node = nullptr;
|
|
|
|
}
|
2015-06-28 17:43:52 +08:00
|
|
|
|
2015-11-23 04:35:40 +08:00
|
|
|
reference& operator=(const reference& other) = delete;
|
2015-06-28 17:43:52 +08:00
|
|
|
|
2015-11-23 04:35:40 +08:00
|
|
|
reference& operator=(const reference&& other)
|
|
|
|
{
|
|
|
|
node = other.node;
|
|
|
|
other.node = nullptr;
|
|
|
|
hazard = std::move(other.hazard);
|
2015-06-23 21:17:10 +08:00
|
|
|
|
2015-11-23 04:35:40 +08:00
|
|
|
return *this;
|
|
|
|
}
|
2015-06-23 21:17:10 +08:00
|
|
|
|
2015-11-23 04:35:40 +08:00
|
|
|
T& operator*() { return *node; }
|
|
|
|
const T& operator*() const { return *node; }
|
2015-06-28 17:43:52 +08:00
|
|
|
|
2015-11-23 04:35:40 +08:00
|
|
|
T* operator->() { return node; }
|
|
|
|
const T* operator->() const { return node; }
|
2015-06-28 17:43:52 +08:00
|
|
|
|
2015-11-23 04:35:40 +08:00
|
|
|
operator T*() { return node; }
|
|
|
|
const operator T*() const { return node; }
|
2015-06-28 17:43:52 +08:00
|
|
|
|
2015-11-23 04:35:40 +08:00
|
|
|
const hazard_ptr& get_hazard() const
|
|
|
|
{
|
|
|
|
return hazard;
|
|
|
|
}
|
2015-06-28 17:43:52 +08:00
|
|
|
|
2015-11-23 04:35:40 +08:00
|
|
|
private:
|
|
|
|
hazard_ptr hazard;
|
|
|
|
Node* node;
|
|
|
|
};
|
|
|
|
|
|
|
|
K key;
|
|
|
|
T item;
|
2015-06-28 17:43:52 +08:00
|
|
|
|
2015-11-23 04:35:40 +08:00
|
|
|
const uint8_t height;
|
|
|
|
Flags flags;
|
|
|
|
|
|
|
|
static Node* create(K key, T item, uint8_t height)
|
|
|
|
{
|
|
|
|
// [ Node ][Node*][Node*][Node*]...[Node*]
|
|
|
|
// | | | | |
|
|
|
|
// | 0 1 2 height-1
|
|
|
|
// |----------------||-----------------------------|
|
|
|
|
// space for Node space for tower pointers
|
|
|
|
// structure right after the Node
|
|
|
|
// structure
|
|
|
|
auto size = sizeof(Node) + height * sizeof(std::atomic<Node*>);
|
|
|
|
auto node = static_cast<Node*>(std::malloc(size));
|
|
|
|
|
|
|
|
// we have raw memory and we need to construct an object
|
|
|
|
// of type Node on it
|
|
|
|
return new (node) Node(key, item, height);
|
2015-06-28 17:43:52 +08:00
|
|
|
}
|
|
|
|
|
2015-11-23 04:35:40 +08:00
|
|
|
static void destroy(Node* node)
|
|
|
|
{
|
|
|
|
node->~Node();
|
|
|
|
free(node);
|
|
|
|
}
|
2015-06-23 21:17:10 +08:00
|
|
|
|
2015-11-23 04:35:40 +08:00
|
|
|
Node::reference forward(size_t level) const
|
|
|
|
{
|
|
|
|
while(true)
|
|
|
|
{
|
|
|
|
auto ref = Node::reference(load_tower(level));
|
|
|
|
|
|
|
|
if(ref.get_hazard() == load_tower(level))
|
|
|
|
return std::move(ref);
|
|
|
|
}
|
|
|
|
}
|
2015-06-28 17:43:52 +08:00
|
|
|
|
2015-11-23 04:35:40 +08:00
|
|
|
private:
|
|
|
|
Node(K key, T item, uint8_t height)
|
|
|
|
: key(key), item(item), height(height)
|
2015-06-28 17:43:52 +08:00
|
|
|
{
|
2015-11-23 04:35:40 +08:00
|
|
|
// here we assume, that the memory for N towers (N = height) has
|
|
|
|
// been allocated right after the Node structure so we need to
|
|
|
|
// initialize that memory
|
|
|
|
for(auto i = 0; i < height; ++i)
|
|
|
|
new (&tower[i]) std::atomic<Node*> {nullptr};
|
|
|
|
}
|
2015-06-28 17:43:52 +08:00
|
|
|
|
2015-11-23 04:35:40 +08:00
|
|
|
~Node()
|
|
|
|
{
|
|
|
|
for(auto i = 0; i < height; ++i)
|
|
|
|
tower[i].~atomic();
|
|
|
|
}
|
2015-06-28 17:43:52 +08:00
|
|
|
|
2015-11-23 04:35:40 +08:00
|
|
|
void forward(size_t level, Node* next)
|
|
|
|
{
|
|
|
|
tower[level].store(next, std::memory_order_release);
|
|
|
|
}
|
2015-06-23 21:17:10 +08:00
|
|
|
|
2015-11-23 04:35:40 +08:00
|
|
|
Node* load_tower(size_t level)
|
|
|
|
{
|
|
|
|
return tower[level].load(std::memory_order_acquire);
|
2015-06-28 17:43:52 +08:00
|
|
|
}
|
2015-06-23 21:17:10 +08:00
|
|
|
|
2015-11-23 04:35:40 +08:00
|
|
|
// 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
|
|
|
|
// have two memory allocations, one for node and one for the forward
|
|
|
|
// list. this way we avoid expensive malloc/free calls and also cache
|
|
|
|
// thrashing when following a pointer on the heap
|
|
|
|
std::atomic<Node*> tower[0];
|
|
|
|
};
|
2015-06-28 17:43:52 +08:00
|
|
|
|
2015-11-23 04:35:40 +08:00
|
|
|
using node_ref_t = typename Node::reference;
|
|
|
|
|
|
|
|
public:
|
|
|
|
class iterator
|
2015-06-23 21:17:10 +08:00
|
|
|
{
|
2015-11-23 04:35:40 +08:00
|
|
|
public:
|
|
|
|
iterator() = default;
|
|
|
|
iterator(node_ref_t&& node) : node(std::move(node)) {}
|
|
|
|
iterator(const iterator&) = delete;
|
2015-06-28 17:43:52 +08:00
|
|
|
|
2015-11-23 04:35:40 +08:00
|
|
|
T& operator*() { return node; }
|
|
|
|
T* operator->() { return node; }
|
2015-06-28 17:43:52 +08:00
|
|
|
|
2015-11-23 04:35:40 +08:00
|
|
|
operator T*() { return node; }
|
2015-06-28 17:43:52 +08:00
|
|
|
|
2015-11-23 04:35:40 +08:00
|
|
|
iterator& operator++()
|
|
|
|
{
|
|
|
|
node = node->forward(0);
|
|
|
|
return *this;
|
|
|
|
}
|
2015-06-28 17:43:52 +08:00
|
|
|
|
2015-11-23 04:35:40 +08:00
|
|
|
iterator& operator++(int)
|
|
|
|
{
|
|
|
|
return operator++();
|
2015-06-28 17:43:52 +08:00
|
|
|
}
|
2015-06-23 21:17:10 +08:00
|
|
|
|
2015-11-23 04:35:40 +08:00
|
|
|
private:
|
|
|
|
node_ref_t node;
|
|
|
|
};
|
|
|
|
|
|
|
|
SkipList() : header(Node::create(K(), T(), H)) {}
|
|
|
|
|
|
|
|
auto begin() { return iterator(header->forward(0)); }
|
|
|
|
auto end() { return iterator(); }
|
|
|
|
|
|
|
|
size_t size() const
|
2015-06-28 17:43:52 +08:00
|
|
|
{
|
2015-11-23 04:35:40 +08:00
|
|
|
return count.load(std::memory_order_acquire);
|
2015-06-28 17:43:52 +08:00
|
|
|
}
|
|
|
|
|
2015-11-23 04:35:40 +08:00
|
|
|
iterator find(const K& key)
|
2015-06-28 17:43:52 +08:00
|
|
|
{
|
2015-11-23 04:35:40 +08:00
|
|
|
auto pred = node_ref_t(header);
|
|
|
|
node_ref_t node;
|
|
|
|
|
|
|
|
uint8_t h = pred->height - 1;
|
2015-06-28 17:43:52 +08:00
|
|
|
|
|
|
|
while(true)
|
|
|
|
{
|
2015-11-23 04:35:40 +08:00
|
|
|
// try to descend down first the next key on this layer overshoots
|
|
|
|
for(; h >= 0 && less(key, node = pred->forward(h)); --h) {}
|
2015-06-28 17:43:52 +08:00
|
|
|
|
2015-11-23 04:35:40 +08:00
|
|
|
// if we overshoot at every layer, item doesn't exist
|
|
|
|
if(h < 0)
|
|
|
|
return iterator();
|
2015-06-28 17:43:52 +08:00
|
|
|
|
2015-11-23 04:35:40 +08:00
|
|
|
// the item is farther to the right, continue going right as long
|
|
|
|
// as the key is greater than the current node's key
|
|
|
|
while(greater(key, node))
|
|
|
|
pred = node, node = node->forward(h);
|
2015-06-28 17:43:52 +08:00
|
|
|
|
2015-11-23 04:35:40 +08:00
|
|
|
// check if we have a hit. if not, we need to descend down again
|
|
|
|
if(!less(key, node))
|
|
|
|
return iterator(std::move(node));
|
|
|
|
}
|
|
|
|
}
|
2015-06-28 17:43:52 +08:00
|
|
|
|
2015-11-23 04:35:40 +08:00
|
|
|
private:
|
|
|
|
std::atomic<size_t> count {0};
|
|
|
|
Node* header;
|
2015-06-28 17:43:52 +08:00
|
|
|
|
2015-11-23 04:35:40 +08:00
|
|
|
bool greater(const K& key, const Node* const node)
|
|
|
|
{
|
|
|
|
return node && key > node->key;
|
|
|
|
}
|
2015-06-28 17:43:52 +08:00
|
|
|
|
2015-11-23 04:35:40 +08:00
|
|
|
bool less(const K& key, const Node* const node)
|
|
|
|
{
|
|
|
|
return (node == nullptr) || key < node->key;
|
|
|
|
}
|
2015-06-28 17:43:52 +08:00
|
|
|
|
2015-11-23 04:35:40 +08:00
|
|
|
int find_path(Node& from, int start, const K& key,
|
|
|
|
node_ref_t (preds&)[],
|
|
|
|
node_ref_t (succs&)[])
|
|
|
|
{
|
|
|
|
int lfound = -1;
|
|
|
|
Node* pred = from;
|
2015-06-28 17:43:52 +08:00
|
|
|
|
2015-11-23 04:35:40 +08:00
|
|
|
for(int level = start_level; level >= 0; --level)
|
|
|
|
{
|
|
|
|
Node* node = pred->forward(level);
|
2015-06-28 17:43:52 +08:00
|
|
|
|
2015-11-23 04:35:40 +08:00
|
|
|
while(greater(key, node))
|
|
|
|
pred = node, node = pred->forward(level);
|
2015-06-28 17:43:52 +08:00
|
|
|
|
2015-06-25 09:06:00 +08:00
|
|
|
|
2015-11-23 04:35:40 +08:00
|
|
|
if(lfound == -1 && !less(key, node))
|
|
|
|
lfound = level;
|
2015-06-23 21:17:10 +08:00
|
|
|
|
2015-11-23 04:35:40 +08:00
|
|
|
preds[level] = pred;
|
|
|
|
succs[level] = node;
|
|
|
|
}
|
|
|
|
|
|
|
|
return lfound;
|
|
|
|
}
|
|
|
|
};
|