From 62fcffffa5c5ec13f24b3ad6fe1d43317ee73928 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20Tomic=CC=8Cevic=CC=81?= Date: Tue, 8 Dec 2015 18:30:20 +0100 Subject: [PATCH] added a lot of comments to skiplist --- data_structures/skiplist/skiplist.hpp | 126 ++++++++++++++++++++++---- mvcc/version_list.hpp | 18 +++- 2 files changed, 121 insertions(+), 23 deletions(-) diff --git a/data_structures/skiplist/skiplist.hpp b/data_structures/skiplist/skiplist.hpp index 8ce10f03c..770d0602e 100644 --- a/data_structures/skiplist/skiplist.hpp +++ b/data_structures/skiplist/skiplist.hpp @@ -10,18 +10,104 @@ #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 - +/* @brief Concurrent lock-based skiplist with fine grained locking + * + * From Wikipedia: + * "A skip list is a data structure that allows fast search within an + * ordered sequence of elements. Fast search is made possible by + * maintaining a linked hierarchy of subsequences, each skipping over + * fewer elements. Searching starts in the sparsest subsequence until + * two consecutive elements have been found, one smaller and one + * larger than or equal to the element searched for." + * + * [_]---------------->[+]----------->[_] + * [_]->[+]----------->[+]------>[+]->[_] + * [_]->[+]------>[+]->[+]------>[+]->[_] + * [_]->[+]->[+]->[+]->[+]->[+]->[+]->[_] + * head 1 2 4 5 8 9 nil + * + * The logarithmic properties are maintained by randomizing the height for + * every new node using the binomial distribution + * p(k) = (1/2)^k for k in [1...H]. + * + * The implementation is based on the work described in the paper + * "A Provably Correct Scalable Concurrent Skip List" + * URL: https://www.cs.tau.ac.il/~shanir/nir-pubs-web/Papers/OPODIS2006-BA.pdf + * + * The proposed implementation is in Java so the authors don't worry about + * garbage collection, but obviously we have to. This implementation uses + * lazy garbage collection. When all clients stop using the skiplist, we can + * be sure that all logically removed nodes are not visible to anyone so + * we can safely remove them. The idea of counting active clients implies + * the use of a intermediary structure (called Accessor) when accessing the + * skiplist. + * + * The implementation has an interface which closely resembles the functions + * with arguments and returned types frequently used by the STL. + * + * Example usage: + * Skiplist skiplist; + * + * { + * auto accessor = skiplist.access(); + * + * // inserts into the skiplist and returns + * // pair. iterator points to the newly created + * // node and the boolean member evaluates to true denoting that the + * // insertion was successful + * accessor.insert_unique(key1, value1); + * + * // nothing gets inserted because key1 already exist in the skiplist + * // returned iterator points to the existing element and the return + * // boolean evaluates to false denoting the failed insertion + * accessor.insert_unique(key1, value1); + * + * // returns an iterator to the element pair + * auto it = accessor.find(key1); + * + * // returns an empty iterator. it == accessor.end() + * auto it = accessor.find(key2); + * + * // iterate over all key value pairs + * for(auto it = accessor.begin(); it != accessor.end(); ++it) + * cout << it->first << " " << it->second; + * + * // range based for loops also work + * for(auto& e : accessor) + * cout << e.first << " " << e.second; + * + * accessor.remove(key1); // returns true + * accessor.remove(key1); // returns false because key1 doesn't exist + * } + * + * // accessor out of scope, garbage collection might occur + * + * For detailed operations available, please refer to the Accessor class + * inside the public section of the SkipList class. + * + * @tparam K Type to use as the key + * @tparam T Type to use as the value + * @tparam H Maximum node height. Determines the effective number of nodes + * the skiplist can hold in order to preserve it's log2 properties + * @tparam lock_t Lock type used when locking is needed during the creation + * and deletion of nodes. + */ template class SkipList : LazyGC>, Lockable { public: + // computes the height for the new node from the interval [1...H] + // with p(k) = (1/2)^k for all k from the interval static thread_local FastBinomial rnd; - using data_t = std::pair; + using value_type = std::pair; + /* @brief Wrapper class for flags used in the implementation + * + * MARKED flag is used to logically delete a node. + * FULLY_LINKED is used to mark the node as fully inserted, i.e. linked + * at all layers in the skiplist up to the node height + */ struct Flags { enum node_flags : uint8_t { @@ -58,7 +144,7 @@ public: public: friend class SkipList; - data_t data; + value_type data; const uint8_t height; Flags flags; @@ -70,10 +156,10 @@ public: static Node* create(const K& key, T&& item, uint8_t height) { - return create({key, std::move(item)}, height); + return create({key, std::forward(item)}, height); } - static Node* create(data_t&& data, uint8_t height) + static Node* create(value_type&& data, uint8_t height) { // [ Node ][Node*][Node*][Node*]...[Node*] // | | | | | @@ -87,7 +173,7 @@ public: // we have raw memory and we need to construct an object // of type Node on it - return new (node) Node(std::move(data), height); + return new (node) Node(std::forward(data), height); } static void destroy(Node* node) @@ -107,7 +193,7 @@ public: } private: - Node(data_t&& data, uint8_t height) + Node(value_type&& data, uint8_t height) : data(std::move(data)), height(height) { // here we assume, that the memory for N towers (N = height) has @@ -144,19 +230,19 @@ public: IteratorBase() = default; IteratorBase(const IteratorBase&) = default; - data_t& operator*() + value_type& operator*() { assert(node != nullptr); return node->data; } - data_t* operator->() + value_type* operator->() { assert(node != nullptr); return &node->data; } - operator data_t&() + operator value_type&() { assert(node != nullptr); return node->data; @@ -191,19 +277,19 @@ public: using IteratorBase::IteratorBase; public: - const data_t& operator*() + const value_type& operator*() { return IteratorBase::operator*(); } - const data_t* operator->() + const value_type* operator->() { return IteratorBase::operator->(); } - operator const data_t&() + operator const value_type&() { - return IteratorBase::operator data_t&(); + return IteratorBase::operator value_type&(); } }; @@ -263,7 +349,7 @@ public: std::pair insert_unique(const K& key, T&& item) { - return skiplist.insert({key, std::move(item)}, preds, succs); + return skiplist.insert({key, std::forward(item)}, preds, succs); } ConstIterator find(const K& key) const @@ -422,7 +508,7 @@ private: } std::pair - insert(data_t&& data, Node* preds[], Node* succs[]) + insert(value_type&& data, Node* preds[], Node* succs[]) { while(true) { @@ -451,7 +537,7 @@ private: continue; // you have the locks, create a new node - auto new_node = Node::create(std::move(data), height); + auto new_node = Node::create(std::forward(data), height); // link the predecessors and successors, e.g. // diff --git a/mvcc/version_list.hpp b/mvcc/version_list.hpp index 76964da61..9849740a8 100644 --- a/mvcc/version_list.hpp +++ b/mvcc/version_list.hpp @@ -16,7 +16,6 @@ class VersionList : public LazyGC> friend class Accessor; public: - using uptr = std::unique_ptr>; class Accessor @@ -67,9 +66,22 @@ public: }; VersionList() = default; - VersionList(const VersionList&) = delete; - VersionList(VersionList&&) = delete; + + /* @brief Move constructs the version list + * Note: use only at the beginning of the "other's" lifecycle since this + * constructor doesn't move the RecordLock, but only the head pointer + */ + VersionList(VersionList&& other) + { + this->head = other.head; + other.head = nullptr; + } + + ~VersionList() + { + delete head.load(); + } Accessor access(tx::Transaction& transaction) {