diff --git a/data_structures/skiplist/skiplist.hpp b/data_structures/skiplist/skiplist.hpp index 573a1da99..61047c5ff 100644 --- a/data_structures/skiplist/skiplist.hpp +++ b/data_structures/skiplist/skiplist.hpp @@ -2,22 +2,26 @@ #include #include +#include -#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 SkipList +class SkipList : LazyGC>, Lockable { +public: static thread_local FastBinomial rnd; + using data_t = std::pair; + struct Flags { enum node_flags : uint8_t { @@ -51,62 +55,20 @@ class SkipList class Node : Lockable { + 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 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 insert_unique(const K& key, const T& item) + { + return skiplist.insert({key, item}, preds, succs); + } + + std::pair 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; + + 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(key); + } + + Iterator find(const K& key) + { + return find_node(key); + } + + template + It find_node(const K& key) + { + Node* node, *pred = header; + int h = static_cast(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 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 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 + 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(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(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 count {0}; + Node* header; }; + +template +thread_local FastBinomial SkipList::rnd; + diff --git a/examples/skiplist b/examples/skiplist deleted file mode 100755 index dc0c8c70b..000000000 Binary files a/examples/skiplist and /dev/null differ diff --git a/examples/skiplist.cpp b/examples/skiplist.cpp index 601b9ec2b..062dfce7c 100644 --- a/examples/skiplist.cpp +++ b/examples/skiplist.cpp @@ -2,9 +2,52 @@ #include "data_structures/skiplist/skiplist.hpp" +using std::cout; +using std::endl; + +using skiplist_t = SkipList; + +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(); + 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; } diff --git a/memory/deferred_recycler.hpp b/memory/deferred_recycler.hpp new file mode 100644 index 000000000..f8b3c4b8d --- /dev/null +++ b/memory/deferred_recycler.hpp @@ -0,0 +1,29 @@ +#pragma once + +#include "recycler.hpp" + +template +class DeferredRecycler : Recycler +{ +public: + using Recycler::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 dirty; +}; diff --git a/memory/lazy_gc.hpp b/memory/lazy_gc.hpp index 75f52b9c9..c3a969192 100644 --- a/memory/lazy_gc.hpp +++ b/memory/lazy_gc.hpp @@ -33,6 +33,8 @@ public: return; this->derived().vacuum(); + + dirty.store(false, std::memory_order_release); } protected: diff --git a/memory/recycler.hpp b/memory/recycler.hpp index b9fe797ac..1d4c174b6 100644 --- a/memory/recycler.hpp +++ b/memory/recycler.hpp @@ -7,7 +7,7 @@ #include "threading/sync/spinlock.hpp" template > -class Recycler : Lockable +class Recycler : public Lockable { 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)...); // todo refactor :D + return fetch_or_create(std::forward(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 items; + + template + T* fetch_or_create(Args&&... args) + { + return new T(std::forward(args)...); // todo refactor :D + } + + void recycle_or_delete(T* item) + { + delete item; // todo refactor :D + } }; diff --git a/utils/auto_scope.hpp b/utils/auto_scope.hpp index de6a5f347..10de5751f 100644 --- a/utils/auto_scope.hpp +++ b/utils/auto_scope.hpp @@ -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();