From 99b8a4f234e86f6dc88d6c548de5150461fcf272 Mon Sep 17 00:00:00 2001 From: sale Date: Tue, 31 Jan 2017 13:48:39 +0100 Subject: [PATCH] Added Skiplist ReverseIterator, Distance Approximation Prototype and documented some stuff Summary: Skiplist ReverseIterator and Distance Approximation Test Plan: manual Reviewers: florijan, buda Reviewed By: buda Subscribers: pullbot, florijan, buda Differential Revision: https://phabricator.memgraph.io/D44 --- .../data_structures/concurrent/skiplist.hpp | 1784 +++++++++-------- include/threading/sync/lockable.hpp | 33 +- include/threading/sync/spinlock.hpp | 35 +- include/utils/placeholder.hpp | 116 +- include/utils/random/generator.h | 11 + .../concurrent/skiplist_reverse_iteration.cpp | 109 + tests/unit/skiplist_reverse_iteration.cpp | 420 ++++ 7 files changed, 1595 insertions(+), 913 deletions(-) create mode 100644 tests/benchmark/data_structures/concurrent/skiplist_reverse_iteration.cpp create mode 100644 tests/unit/skiplist_reverse_iteration.cpp diff --git a/include/data_structures/concurrent/skiplist.hpp b/include/data_structures/concurrent/skiplist.hpp index 49a8a3edd..0462a7686 100644 --- a/include/data_structures/concurrent/skiplist.hpp +++ b/include/data_structures/concurrent/skiplist.hpp @@ -2,6 +2,7 @@ #include #include +#include #include #include "utils/placeholder.hpp" @@ -94,908 +95,1003 @@ * and deletion of nodes. */ template -class SkipList : private 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; +class SkipList : private 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; - /** @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 - { - MARKED = 0x01, - FULLY_LINKED = 0x10, - }; - - bool is_marked() const { return flags.load() & MARKED; } - - void set_marked() { flags.fetch_or(MARKED); } - - bool is_fully_linked() const { return flags.load() & FULLY_LINKED; } - - void set_fully_linked() { flags.fetch_or(FULLY_LINKED); } - - private: - std::atomic flags{0}; + /** @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 { + MARKED = 0x01, + FULLY_LINKED = 0x10, }; - class Node : Lockable - { - public: - friend class SkipList; + bool is_marked() const { return flags.load() & MARKED; } - const uint8_t height; - Flags flags; + void set_marked() { flags.fetch_or(MARKED); } - T &value() { return data.get(); } + bool is_fully_linked() const { return flags.load() & FULLY_LINKED; } - const T &value() const { return data.get(); } + void set_fully_linked() { flags.fetch_or(FULLY_LINKED); } - static Node *sentinel(uint8_t height) - { - // we have raw memory and we need to construct an object - // of type Node on it - return new (allocate(height)) Node(height); - } + private: + std::atomic flags{0}; + }; - static Node *create(const T &item, uint8_t height) - { - return create(height, item); - } + class Node : Lockable { + public: + friend class SkipList; - static Node *create(T &&item, uint8_t height) - { - auto node = allocate(height); + const uint8_t height; + Flags flags; - // we have raw memory and we need to construct an object - // of type Node on it - return new (node) Node(std::move(item), height); - } + T &value() { return data.get(); } - template - static Node *emplace(uint8_t height, Args &&... args) - { - auto node = allocate(height); + const T &value() const { return data.get(); } - // we have raw memory and we need to construct an object - // of type Node on it - return new (node) Node(height, std::forward(args)...); - } - - static void destroy(Node *node) - { - node->~Node(); - std::free(node); - } - - Node *forward(size_t level) const { return tower[level].load(); } - - void forward(size_t level, Node *next) { tower[level].store(next); } - - private: - Node(uint8_t height) : 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 - // initialize that memory - for (auto i = 0; i < height; ++i) - new (&tower[i]) std::atomic{nullptr}; - } - - template - Node(uint8_t height, Args &&... args) : Node(height) - { - this->data.emplace(std::forward(args)...); - } - - Node(T &&data, uint8_t height) : Node(height) - { - this->data.set(std::move(data)); - } - - ~Node() - { - for (auto i = 0; i < height; ++i) - tower[i].~atomic(); - } - - static Node *allocate(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); - auto node = static_cast(std::malloc(size)); - - return node; - } - - Placeholder data; - - /** - * 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 tower[0]; - }; - -public: - template - class IteratorBase : public Crtp - { - protected: - IteratorBase(Node *node) : node(node) {} - - Node *node{nullptr}; - - public: - IteratorBase() = default; - IteratorBase(const IteratorBase &) = default; - - T &operator*() - { - assert(node != nullptr); - return node->value(); - } - - T *operator->() - { - assert(node != nullptr); - return &node->value(); - } - - operator T &() - { - assert(node != nullptr); - return node->value(); - } - - It &operator++() - { - assert(node != nullptr); - node = node->forward(0); - return this->derived(); - } - - bool has_next() - { - assert(node != nullptr); - return node->forward(0) != nullptr; - } - - It &operator++(int) { return operator++(); } - - friend bool operator==(const It &a, const It &b) - { - return a.node == b.node; - } - - friend bool operator!=(const It &a, const It &b) { return !(a == b); } - }; - - class ConstIterator : public IteratorBase - { - friend class SkipList; - ConstIterator(Node *node) : IteratorBase(node) {} - - public: - ConstIterator() = default; - ConstIterator(const ConstIterator &) = default; - - const T &operator*() - { - return IteratorBase::operator*(); - } - - const T *operator->() - { - return IteratorBase::operator->(); - } - - operator const T &() - { - return IteratorBase::operator T &(); - } - }; - - class Iterator : public IteratorBase - { - friend class SkipList; - Iterator(Node *node) : IteratorBase(node) {} - - public: - Iterator() = default; - Iterator(const Iterator &) = default; - }; - - template - class MultiIterator : public Crtp> - { - friend class SkipList; - - MultiIterator(const K &data) : data(data), skiplist(nullptr) - { - succs[0] = nullptr; - }; - MultiIterator(SkipList *skiplist, const K &data) - : data(data), skiplist(skiplist) - { - // WHe must find the first element with K key. - // All of logic in this loop was taken from insert method. - while (true) { - auto level = find_path(skiplist, H - 1, data, preds, succs); - if (level == -1) { - succs[0] = nullptr; - } else if (succs[0] != succs[succs[0]->height - 1] || - !succs[level]->flags.is_fully_linked()) { - usleep(250); - continue; - } - break; - } - } - - public: - MultiIterator(const MultiIterator &) = default; - - T &operator*() - { - assert(succs[0] != nullptr); - return succs[0]->value(); - } - - T *operator->() - { - assert(succs[0] != nullptr); - return &succs[0]->value(); - } - - operator T &() - { - assert(succs[0] != nullptr); - return succs[0]->value(); - } - - bool has_value() { return succs[0] != nullptr; } - - MultiIterator &operator++() - { - assert(succs[0] != nullptr); - // NOTE: This whole method can be optimized if it's valid to expect - // height - // of 1 on same key elements. - - // First update preds and succs. - for (int i = succs[0]->height - 1; i >= 0; i--) { - preds[i] = succs[i]; - succs[i] = preds[i]->forward(i); - } - - // If there exists current value then check if it is equal to our - // data. - if (succs[0] != nullptr) { - if (succs[0]->value() != data) { - // Current data isn't equal to our data that means that this - // is the end of list of same values. - succs[0] = nullptr; - } else { - // Current value is same as our data but whe must check that - // it is valid data and if not whe must wait for it to - // become valid. - while (succs[0] != succs[succs[0]->height - 1] || - !succs[0]->flags.is_fully_linked()) { - usleep(250); // Wait to become linked - // Reget succs. - for (int i = succs[0]->height - 1; i >= 0; i--) { - succs[i] = preds[i]->forward(i); - } - } - } - } - - return this->derived(); - } - - MultiIterator &operator++(int) { return operator++(); } - - friend bool operator==(const MultiIterator &a, const MultiIterator &b) - { - return a.succs[0] == b.succs[0]; - } - - friend bool operator!=(const MultiIterator &a, const MultiIterator &b) - { - return !(a == b); - } - - bool is_removed() - { - assert(succs[0] != nullptr); - return succs[0]->flags.is_marked(); - } - - // True if this call successfuly removed value. ITERATOR IS'T ADVANCED. - // False may mean that data has already been removed. - bool remove() - { - assert(succs[0] != nullptr); - // Calls skiplist remove method. - - return skiplist->template remove( - data, preds, succs, - SkipList::template MultiIterator::update_path); - } - - private: - // TODO: figure why start is unused - static int update_path(SkipList *skiplist, int start, const K &item, - Node *preds[], Node *succs[]) - { - // NOTE: This could be done more efficent than serching for item - // element again. Whe just need to use infromation already present - // in preds and succ because whe know that this method is used - // exclusively by passing it into skiplist remove method from - // MultiIterator remove method. - - // One optimization here would be to wait for is_fully_linked to be - // true. That way that doesnt have to be done in constructor and - // ++ operator. - int level_found = succs[0]->height - 1; - assert(succs[0] == succs[level_found]); - - for (auto it = MultiIterator(skiplist, item); it.has_value(); - it++) { - if (it.succs[0] == succs[0]) { // Found it - std::copy(it.preds, it.preds + H, preds); - std::copy(it.succs, it.succs + H, succs); - return level_found; - } - } - // Someone removed it - return -1; - } - - const K &data; - SkipList *skiplist; - Node *preds[H], *succs[H]; - }; - - SkipList() : header(Node::sentinel(H)) {} - - ~SkipList() - { - // Someone could be using this map through an Accessor. - Node *now = header; - header = nullptr; - - while (now != nullptr) { - Node *next = now->forward(0); - Node::destroy(now); - now = next; - } + static Node *sentinel(uint8_t height) { + // we have raw memory and we need to construct an object + // of type Node on it + return new (allocate(height)) Node(height); } - friend class Accessor; - - class Accessor - { - friend class SkipList; - - Accessor(SkipList *skiplist) : skiplist(skiplist) - { - assert(skiplist != nullptr); - - skiplist->gc.add_ref(); - } - - public: - Accessor(const Accessor &) = delete; - - Accessor(Accessor &&other) : skiplist(other.skiplist) - { - other.skiplist = nullptr; - } - - ~Accessor() - { - if (skiplist == nullptr) return; - - skiplist->gc.release_ref(); - } - - 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(); } - - template - MultiIterator end(const K &data) - { - return skiplist->mend(data); - } - - template - MultiIterator mend(const K &data) - { - return skiplist->template mend(data); - } - - std::pair insert(const T &item) - { - return skiplist->insert(preds, succs, item); - } - - std::pair insert(T &&item) - { - return skiplist->insert(preds, succs, std::move(item)); - } - - template - std::pair emplace(K &key, Args &&... args) - { - return skiplist->emplace(preds, succs, key, - std::forward(args)...); - } - - Iterator insert_non_unique(const T &item) - { - return skiplist->insert_non_unique(item, preds, succs); - } - - Iterator insert_non_unique(T &&item) - { - return skiplist->insert_non_unique(std::forward(item), preds, - succs); - } - - template - MultiIterator find_multi(const K &item) const - { - return MultiIterator(this->skiplist, item); - } - - template - ConstIterator find(const K &item) const - { - return static_cast(*skiplist).find(item); - } - - template - Iterator find(const K &item) - { - return skiplist->find(item); - } - - template - ConstIterator find_or_larger(const K &item) const - { - return static_cast(*skiplist).find_or_larger( - item); - } - - template - It find_or_larger(const K &item) - { - return skiplist->find_or_larger(item); - } - - template - bool contains(const K &item) const - { - return this->find(item) != this->end(); - } - - template - bool remove(const K &item) - { - return skiplist->remove(item, preds, succs, - SkipList::template find_path); - } - - size_t size() const { return skiplist->size(); } - - private: - SkipList *skiplist; - Node *preds[H], *succs[H]; - }; - - Accessor access() { return Accessor(this); } - - const Accessor access() const { 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(); } - - template - MultiIterator end(const K &data) - { - return MultiIterator(data); + static Node *create(const T &item, uint8_t height) { + return create(height, item); } - template - MultiIterator mend(const K &data) - { - return MultiIterator(data); + static Node *create(T &&item, uint8_t height) { + auto node = allocate(height); + + // we have raw memory and we need to construct an object + // of type Node on it + return new (node) Node(std::move(item), height); } - size_t size() const { return count.load(); } + template + static Node *emplace(uint8_t height, Args &&... args) { + auto node = allocate(height); - template - static bool greater(const K &item, const Node *const node) - { - return node && item > node->value(); + // we have raw memory and we need to construct an object + // of type Node on it + return new (node) Node(height, std::forward(args)...); } - template - static bool less(const K &item, const Node *const node) - { - return (node == nullptr) || item < node->value(); + static void destroy(Node *node) { + node->~Node(); + std::free(node); } + Node *forward(size_t level) const { return tower[level].load(); } + + void forward(size_t level, Node *next) { tower[level].store(next); } + + private: + Node(uint8_t height) : 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 + // initialize that memory + for (auto i = 0; i < height; ++i) + new (&tower[i]) std::atomic{nullptr}; + } + + template + Node(uint8_t height, Args &&... args) : Node(height) { + this->data.emplace(std::forward(args)...); + } + + Node(T &&data, uint8_t height) : Node(height) { + this->data.set(std::move(data)); + } + + ~Node() { + for (auto i = 0; i < height; ++i) tower[i].~atomic(); + } + + static Node *allocate(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); + auto node = static_cast(std::malloc(size)); + + return node; + } + + Placeholder data; + /** - * Returns first occurence of item if there exists one. + * 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 */ - template - ConstIterator find(const K &item) const - { - return const_cast(this)->find_node(item); + std::atomic tower[0]; + }; + + public: + template + class IteratorBase : public Crtp { + protected: + IteratorBase(Node *node) : node(node) {} + + Node *node{nullptr}; + + public: + IteratorBase() = default; + IteratorBase(const IteratorBase &) = default; + + T &operator*() { + assert(node != nullptr); + return node->value(); } + T *operator->() { + assert(node != nullptr); + return &node->value(); + } + + operator T &() { + assert(node != nullptr); + return node->value(); + } + + It &operator++() { + assert(node != nullptr); + node = node->forward(0); + return this->derived(); + } + + bool has_next() { + assert(node != nullptr); + return node->forward(0) != nullptr; + } + + It &operator++(int) { return operator++(); } + + friend bool operator==(const It &a, const It &b) { + return a.node == b.node; + } + + friend bool operator!=(const It &a, const It &b) { return !(a == b); } + }; + + class ConstIterator : public IteratorBase { + friend class SkipList; + ConstIterator(Node *node) : IteratorBase(node) {} + + public: + ConstIterator() = default; + ConstIterator(const ConstIterator &) = default; + + const T &operator*() { return IteratorBase::operator*(); } + + const T *operator->() { return IteratorBase::operator->(); } + + operator const T &() { return IteratorBase::operator T &(); } + }; + + class Iterator : public IteratorBase { + friend class SkipList; + Iterator(Node *node) : IteratorBase(node) {} + + public: + Iterator() = default; + Iterator(const Iterator &) = default; + }; + + /** + @class ReverseIterator + @author Sandi Fatic + + @brief + ReverseIterator is used to iterate the skiplist in backwards. The current + implementation complexity is M*C*Log(N) where M is the number of elements + we are iterating, N the size of the skiplist and some constant C. The time + is better then using find M times. Look the benchmark for this class. + + The performance of the reverse iterator is similar to M finds, but faster + because it stores the preds and shortens the search space. + + @todo + Research possible better and faster more optimized traversals. + */ + class ReverseIterator : public Crtp { + friend class SkipList; + + ReverseIterator(Node *node) : node_(node) {} + + public: + ReverseIterator(SkipList *skiplist, Node *node, Node *preds[]) + : skiplist_(skiplist), node_(node) { + for (int i = 0; i < H; i++) preds_[i] = preds[i]; + if (node_ == skiplist_->header) node_ = node_->forward(0); + } + + T &operator*() { + assert(node_ != nullptr); + return node_->value(); + } + + T *operator->() { + assert(node_ != nullptr); + return &node_->value(); + } + + operator T &() { + assert(node_ != nullptr); + return node_->value(); + } + + ReverseIterator &operator++() { + assert(node_ != nullptr); + do { + next(); + } while (node_->flags.is_marked()); + return this->derived(); + } + + friend bool operator==(const ReverseIterator &a, const ReverseIterator &b) { + return a.node_ == b.node_; + } + + ReverseIterator &operator++(int) { return operator++(); } + /** - * Returns first occurence of item if there exists one. - */ - template - Iterator find(const K &item) - { - return find_node(item); + @brief + The next() function generates the previous element in skiplist if exists. + + It uses the stored preds to find the previous element and updates the + preds to optimize the search. + */ + void next() { + int level_found = -1, curr = 0; + auto prev = preds_[0]->value(); + + Node *pred = preds_[curr]; + + // finds the level from which to start the search + while (curr < H) { + curr++; + if (pred != preds_[curr]) break; + } + + // goes on level up if possible for better performance + // not always the optimal but benchmarks are better + if (curr + 1 < H) curr++; + + while (level_found == -1 && curr < H) { + Node *pred = preds_[curr]; + for (int level = curr; level >= 0; --level) { + Node *node = pred->forward(level); + + while (greater(prev, node)) { + pred = node, node = pred->forward(level); + } + + if (level_found == -1 && !less(prev, node)) + level_found = level; + + preds_[level] = pred; + } + curr++; + } + + node_ = preds_[0]; + if (node_ == skiplist_->header) node_ = node_->forward(0); } - template - It find_node(const K &item) - { - auto it = find_or_larger(item); - if (it.node == nullptr || item == *it) { - return std::move(it); + bool has_next() { return node_ != skiplist_->header; } + + private: + SkipList *skiplist_; + Node *node_{nullptr}; + Node *preds_[H]; + }; + + template + class MultiIterator : public Crtp> { + friend class SkipList; + + MultiIterator(const K &data) : data(data), skiplist(nullptr) { + succs[0] = nullptr; + }; + MultiIterator(SkipList *skiplist, const K &data) + : data(data), skiplist(skiplist) { + // We must find the first element with K key. + // All of logic in this loop was taken from insert method. + while (true) { + auto level = find_path(skiplist, H - 1, data, preds, succs); + if (level == -1) { + succs[0] = nullptr; + } else if (succs[0] != succs[succs[0]->height - 1] || + !succs[level]->flags.is_fully_linked()) { + usleep(250); + continue; + } + break; + } + } + + public: + MultiIterator(const MultiIterator &) = default; + + T &operator*() { + assert(succs[0] != nullptr); + return succs[0]->value(); + } + + T *operator->() { + assert(succs[0] != nullptr); + return &succs[0]->value(); + } + + operator T &() { + assert(succs[0] != nullptr); + return succs[0]->value(); + } + + bool has_value() { return succs[0] != nullptr; } + + MultiIterator &operator++() { + assert(succs[0] != nullptr); + // NOTE: This whole method can be optimized if it's valid to expect + // height + // of 1 on same key elements. + + // First update preds and succs. + for (int i = succs[0]->height - 1; i >= 0; i--) { + preds[i] = succs[i]; + succs[i] = preds[i]->forward(i); + } + + // If there exists current value then check if it is equal to our + // data. + if (succs[0] != nullptr) { + if (succs[0]->value() != data) { + // Current data isn't equal to our data that means that this + // is the end of list of same values. + succs[0] = nullptr; } else { - return It(); + // Current value is same as our data but we must check that + // it is valid data and if not we must wait for it to + // become valid. + while (succs[0] != succs[succs[0]->height - 1] || + !succs[0]->flags.is_fully_linked()) { + usleep(250); // Wait to become linked + // Reget succs. + for (int i = succs[0]->height - 1; i >= 0; i--) { + succs[i] = preds[i]->forward(i); + } + } } + } + + return this->derived(); } - /** - * Returns iterator on searched element or the first larger element. - */ - template - It find_or_larger(const K &item) - { - Node *node, *pred = header; - int h = static_cast(pred->height) - 1; + MultiIterator &operator++(int) { return operator++(); } - while (true) { - // try to descend down first the next key on this layer overshoots - for (; h >= 0 && less(item, node = pred->forward(h)); --h) { - } + friend bool operator==(const MultiIterator &a, const MultiIterator &b) { + return a.succs[0] == b.succs[0]; + } - // if we overshoot at every layer, item doesn't exist - if (h < 0) return It(node); + friend bool operator!=(const MultiIterator &a, const MultiIterator &b) { + return !(a == b); + } - // 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(item, node)) - pred = node, node = node->forward(h); + bool is_removed() { + assert(succs[0] != nullptr); + return succs[0]->flags.is_marked(); + } - // check if we have a hit. if not, we need to descend down again - if (!less(item, node) && !node->flags.is_marked()) return It(node); + // True if this call successfuly removed value. ITERATOR IS'T ADVANCED. + // False may mean that data has already been removed. + bool remove() { + assert(succs[0] != nullptr); + // Calls skiplist remove method. + + return skiplist->template remove( + data, preds, succs, + SkipList::template MultiIterator::update_path); + } + + private: + // TODO: figure why start is unused + static int update_path(SkipList *skiplist, int start, const K &item, + Node *preds[], Node *succs[]) { + // NOTE: This could be done more efficent than serching for item + // element again. Whe just need to use infromation already present + // in preds and succ because he know that this method is used + // exclusively by passing it into skiplist remove method from + // MultiIterator remove method. + + // One optimization here would be to wait for is_fully_linked to be + // true. That way that doesnt have to be done in constructor and + // ++ operator. + int level_found = succs[0]->height - 1; + assert(succs[0] == succs[level_found]); + + for (auto it = MultiIterator(skiplist, item); it.has_value(); it++) { + if (it.succs[0] == succs[0]) { // Found it + std::copy(it.preds, it.preds + H, preds); + std::copy(it.succs, it.succs + H, succs); + return level_found; } + } + // Someone removed it + return -1; + } + + const K &data; + SkipList *skiplist; + Node *preds[H], *succs[H]; + }; + + SkipList() : header(Node::sentinel(H)) {} + + ~SkipList() { + // Someone could be using this map through an Accessor. + Node *now = header; + header = nullptr; + + while (now != nullptr) { + Node *next = now->forward(0); + Node::destroy(now); + now = next; + } + } + + friend class Accessor; + + class Accessor { + friend class SkipList; + + Accessor(SkipList *skiplist) : skiplist(skiplist) { + assert(skiplist != nullptr); + + skiplist->gc.add_ref(); + } + + public: + Accessor(const Accessor &) = delete; + + Accessor(Accessor &&other) : skiplist(other.skiplist) { + other.skiplist = nullptr; + } + + ~Accessor() { + if (skiplist == nullptr) return; + + skiplist->gc.release_ref(); + } + + 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(); } + + ReverseIterator rbegin() { return skiplist->rbegin(); } + + ReverseIterator rend() { return skiplist->rend(); } + + template + MultiIterator end(const K &data) { + return skiplist->mend(data); } template - static int find_path(SkipList *skiplist, int start, const K &item, - Node *preds[], Node *succs[]) - { - int level_found = -1; - Node *pred = skiplist->header; - - for (int level = start; level >= 0; --level) { - Node *node = pred->forward(level); - - while (greater(item, node)) - pred = node, node = pred->forward(level); - - if (level_found == -1 && !less(item, node)) level_found = level; - - preds[level] = pred; - succs[level] = node; - } - - return level_found; + MultiIterator mend(const K &data) { + return skiplist->template mend(data); } - template - static bool lock_nodes(uint8_t height, guard_t guards[], Node *preds[], - Node *succs[]) - { - Node *prepred = nullptr, *pred = nullptr, *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(const T &item) { + return skiplist->insert(preds, succs, item); } - /** - * Inserts non unique data into list. - * - * NOTE: Uses modified logic from insert method. - */ - Iterator insert_non_unique(T &&data, Node *preds[], Node *succs[]) - { - while (true) { - - auto level = find_path(this, H - 1, data, preds, succs); - - auto height = 1; - if (level != -1) { - - auto found = succs[level]; - - if (found->flags.is_marked()) continue; - - if (!found->flags.is_fully_linked()) { - usleep(250); - continue; - } - - // TODO Optimization for avoiding degrading of skiplist to list. - // Somehow this operation is errornus. - // if (found->height == 1) { // To avoid linearization new - // element - // // will be at least 2 height and - // will - // // be added in front. - // height = rnd(); - // // if (height == 1) height = 2; - // } else { - - // Only level 0 will be used so the rest is irrelevant. - preds[0] = found; - succs[0] = found->forward(0); - - // This maybe isn't necessary - auto next = succs[0]; - if (next != nullptr) { - - if (next->flags.is_marked()) continue; - - if (!next->flags.is_fully_linked()) { - usleep(250); - continue; - } - } - } else { - height = rnd(); - // Optimization which doesn't add any extra locking. - if (height == 1) - height = 2; // Same key list will be skipped more often. - } - - 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; - - return insert_here(Node::create(std::move(data), height), preds, - succs, height, guards); - } + std::pair insert(T &&item) { + return skiplist->insert(preds, succs, std::move(item)); } - /** - * Insert unique data - * - * F - type of funct which will create new node if needed. Recieves height - * of node. - */ - std::pair insert(Node *preds[], Node *succs[], T &&data) - { - while (true) { - // TODO: before here was data.first - auto level = find_path(this, H - 1, data, 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; - - return {insert_here(Node::create(std::move(data), height), preds, - succs, height, guards), - true}; - } - } - - /** - * Insert unique data - * - * NOTE: This is almost all duplicate code from insert. - */ template - std::pair emplace(Node *preds[], Node *succs[], K &key, - Args &&... args) - { - while (true) { - // TODO: before here was data.first - auto level = find_path(this, H - 1, key, 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; - - return { - insert_here(Node::emplace(height, std::forward(args)...), - preds, succs, height, guards), - true}; - } + std::pair emplace(K &key, Args &&... args) { + return skiplist->emplace(preds, succs, key, std::forward(args)...); } - /** - * Inserts data to specified locked location. - */ - Iterator insert_here(Node *new_node, Node *preds[], Node *succs[], - int height, guard_t guards[]) // TODO: querds unused - { - // 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); - - return Iterator{new_node}; + Iterator insert_non_unique(const T &item) { + return skiplist->insert_non_unique(item, preds, succs); } - static bool ok_delete(Node *node, int level) - { - return node->flags.is_fully_linked() && node->height - 1 == level && - !node->flags.is_marked(); + Iterator insert_non_unique(T &&item) { + return skiplist->insert_non_unique(std::forward(item), preds, succs); } - /** - * Removes item found with fp with arguments skiplist, preds and succs. - * fp has to fill preds and succs which reflect location of item or return - * -1 as in not found otherwise returns level on which the item was first - * found. - */ template - bool remove(const K &item, Node *preds[], Node *succs[], - int (*fp)(SkipList *, int, const K &, Node *[], Node *[])) - { - Node *node = nullptr; - guard_t node_guard; - bool marked = false; - int height = 0; - - while (true) { - auto level = fp(this, H - 1, item, 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(); - marked = true; - } - - 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: review and test - gc.collect(node); - - count.fetch_sub(1); - return true; - } + MultiIterator find_multi(const K &item) const { + return MultiIterator(this->skiplist, item); } - /** - * number of elements - */ - std::atomic count{0}; - Node *header; - SkiplistGC gc; + template + ConstIterator find(const K &item) const { + return static_cast(*skiplist).find(item); + } + + template + Iterator find(const K &item) { + return skiplist->find(item); + } + + template + std::pair reverse(const K &item) { + return skiplist->reverse(item); + } + + template + ConstIterator find_or_larger(const K &item) const { + return static_cast(*skiplist).find_or_larger(item); + } + + template + It find_or_larger(const K &item) { + return skiplist->find_or_larger(item); + } + + template + bool contains(const K &item) const { + return this->find(item) != this->end(); + } + + template + bool remove(const K &item) { + return skiplist->remove(item, preds, succs, + SkipList::template find_path); + } + + size_t size() const { return skiplist->size(); } + + template + size_t distance(const K &first, const K &second) { + return skiplist->distance(first, second); + } + + private: + SkipList *skiplist; + Node *preds[H], *succs[H]; + }; + + Accessor access() { return Accessor(this); } + + const Accessor access() const { 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(); } + + ReverseIterator rend() { return ReverseIterator(header->forward(0)); } + + ReverseIterator rbegin() { + Node *begin = header; + Node *preds[H]; + for (int level = H - 1; level >= 0; level--) { + preds[level] = begin; + while (begin->forward(level) != nullptr) { + begin = begin->forward(level); + preds[level] = begin; + } + } + return ReverseIterator(this, begin, preds); + } + + ConstIterator end() const { return ConstIterator(); } + + ConstIterator cend() const { return ConstIterator(); } + + template + MultiIterator end(const K &data) { + return MultiIterator(data); + } + + template + MultiIterator mend(const K &data) { + return MultiIterator(data); + } + + size_t size() const { return count.load(); } + + template + static bool equal(const K *item, const Node *const node) { + return node && item == node->value(); + } + + template + static bool greater(const K &item, const Node *const node) { + return node && item > node->value(); + } + + template + static bool less(const K &item, const Node *const node) { + return (node == nullptr) || item < node->value(); + } + + /** + * Returns first occurence of item if there exists one. + */ + template + ConstIterator find(const K &item) const { + return const_cast(this)->find_node(item); + } + + /** + * Returns first occurence of item if there exists one. + */ + template + Iterator find(const K &item) { + return find_node(item); + } + + template + std::pair reverse(const K &item) { + auto it = find(item); + if (it == end()) { + return std::make_pair(rend(), false); + } + + Node *preds[H]; + auto level = find_path(this, H - 1, item, preds); + return std::make_pair(ReverseIterator(this, preds[0], preds), true); + } + + template + It find_node(const K &item) { + auto it = find_or_larger(item); + if (it.node == nullptr || item == *it) { + return std::move(it); + } else { + return It(); + } + } + + /** + * Returns iterator on searched element or the first larger element. + */ + template + It find_or_larger(const K &item) { + Node *node, *pred = header; + int h = static_cast(pred->height) - 1; + + while (true) { + // try to descend down first the next key on this layer overshoots + for (; h >= 0 && less(item, node = pred->forward(h)); --h) { + } + + // if we overshoot at every layer, item doesn't exist + if (h < 0) return It(node); + + // 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(item, node)) pred = node, node = node->forward(h); + + // check if we have a hit. if not, we need to descend down again + if (!less(item, node) && !node->flags.is_marked()) return It(node); + } + } + + template + static int find_path(SkipList *skiplist, int start, const K &item, + Node *preds[] = nullptr, Node *succs[] = nullptr) { + int level_found = -1; + Node *pred = skiplist->header; + + for (int level = start; level >= 0; --level) { + Node *node = pred->forward(level); + + while (greater(item, node)) pred = node, node = pred->forward(level); + + if (level_found == -1 && !less(item, node)) level_found = level; + + if (preds != nullptr) preds[level] = pred; + if (succs != nullptr) succs[level] = node; + } + + return level_found; + } + + /** + @brief + Distance method approximates the distance between two elements. + + General idea of the skiplist distance function is to find the preds which + are the same for two elements and based on that calculate the distance. + With every pred which is the same the skiplist should be 2 times smaller. + + @todo + Current implementation is trivial. The mistake is quite big (up to x8) + times in some cases. + */ + template + size_t distance(const K &first, const K &second) { + auto skiplist_size = size(); + + // finds the max level of the skiplist based on the size (simple math). + auto level = static_cast(std::round(std::log2(skiplist_size))); + + Node *first_preds[32]; + Node *second_preds[32]; + + find_path(this, H - 1, first, first_preds, nullptr); + find_path(this, H - 1, second, second_preds, nullptr); + + for (int i = level; i >= 0; i--) + if (first_preds[i] == second_preds[i]) skiplist_size /= 2; + + return skiplist_size; + } + + template + static bool lock_nodes(uint8_t height, guard_t guards[], Node *preds[], + Node *succs[]) { + Node *prepred = nullptr, *pred = nullptr, *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; + } + + /** + * Inserts non unique data into list. + * + * NOTE: Uses modified logic from insert method. + */ + Iterator insert_non_unique(T &&data, Node *preds[], Node *succs[]) { + while (true) { + auto level = find_path(this, H - 1, data, preds, succs); + + auto height = 1; + if (level != -1) { + auto found = succs[level]; + + if (found->flags.is_marked()) continue; + + if (!found->flags.is_fully_linked()) { + usleep(250); + continue; + } + + // TODO Optimization for avoiding degrading of skiplist to list. + // Somehow this operation is errornus. + // if (found->height == 1) { // To avoid linearization new + // element + // // will be at least 2 height and + // will + // // be added in front. + // height = rnd(); + // // if (height == 1) height = 2; + // } else { + + // Only level 0 will be used so the rest is irrelevant. + preds[0] = found; + succs[0] = found->forward(0); + + // This maybe isn't necessary + auto next = succs[0]; + if (next != nullptr) { + if (next->flags.is_marked()) continue; + + if (!next->flags.is_fully_linked()) { + usleep(250); + continue; + } + } + } else { + height = rnd(); + // Optimization which doesn't add any extra locking. + if (height == 1) + height = 2; // Same key list will be skipped more often. + } + + 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; + + return insert_here(Node::create(std::move(data), height), preds, succs, + height, guards); + } + } + + /** + * Insert unique data + * + * F - type of funct which will create new node if needed. Recieves height + * of node. + */ + std::pair insert(Node *preds[], Node *succs[], T &&data) { + while (true) { + // TODO: before here was data.first + auto level = find_path(this, H - 1, data, 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; + + return {insert_here(Node::create(std::move(data), height), preds, succs, + height, guards), + true}; + } + } + + /** + * Insert unique data + * + * NOTE: This is almost all duplicate code from insert. + */ + template + std::pair emplace(Node *preds[], Node *succs[], K &key, + Args &&... args) { + while (true) { + // TODO: before here was data.first + auto level = find_path(this, H - 1, key, 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; + + return {insert_here(Node::emplace(height, std::forward(args)...), + preds, succs, height, guards), + true}; + } + } + + /** + * Inserts data to specified locked location. + */ + Iterator insert_here(Node *new_node, Node *preds[], Node *succs[], int height, + guard_t guards[]) // TODO: querds unused + { + // 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); + + return Iterator{new_node}; + } + + static bool ok_delete(Node *node, int level) { + return node->flags.is_fully_linked() && node->height - 1 == level && + !node->flags.is_marked(); + } + + /** + * Removes item found with fp with arguments skiplist, preds and succs. + * fp has to fill preds and succs which reflect location of item or return + * -1 as in not found otherwise returns level on which the item was first + * found. + */ + template + bool remove(const K &item, Node *preds[], Node *succs[], + int (*fp)(SkipList *, int, const K &, Node *[], Node *[])) { + Node *node = nullptr; + guard_t node_guard; + bool marked = false; + int height = 0; + + while (true) { + auto level = fp(this, H - 1, item, 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(); + marked = true; + } + + 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: review and test + gc.collect(node); + + count.fetch_sub(1); + return true; + } + } + + /** + * number of elements + */ + std::atomic count{0}; + Node *header; + SkiplistGC gc; }; template diff --git a/include/threading/sync/lockable.hpp b/include/threading/sync/lockable.hpp index 495634363..f99eb4a87 100644 --- a/include/threading/sync/lockable.hpp +++ b/include/threading/sync/lockable.hpp @@ -4,21 +4,28 @@ #include "threading/sync/spinlock.hpp" +/** + * @class Lockable + * + * @brief + * Lockable is used as an custom implementation of a mutex mechanism. It is + * implemented as a wrapper around std::lock_guard and std::unique_guard with + * a default lock called Spinlock. + * + * @tparam lock_t type of lock to be used (default = Spinlock) + */ template -class Lockable -{ -public: - using lock_type = lock_t; +class Lockable { + public: + using lock_type = lock_t; - std::lock_guard acquire_guard() const - { - return std::lock_guard(lock); - } + std::lock_guard acquire_guard() const { + return std::lock_guard(lock); + } - std::unique_lock acquire_unique() const - { - return std::unique_lock(lock); - } + std::unique_lock acquire_unique() const { + return std::unique_lock(lock); + } - mutable lock_t lock; + mutable lock_t lock; }; diff --git a/include/threading/sync/spinlock.hpp b/include/threading/sync/spinlock.hpp index 01beaf434..a515d68ef 100644 --- a/include/threading/sync/spinlock.hpp +++ b/include/threading/sync/spinlock.hpp @@ -1,23 +1,28 @@ #pragma once -#include #include +#include #include "utils/cpu_relax.hpp" -class SpinLock -{ -public: - void lock() - { // Before was memorz_order_acquire - while (lock_flag.test_and_set(std::memory_order_seq_cst)) - cpu_relax(); - /// usleep(250); - } - // Before was memory_order_release - void unlock() { lock_flag.clear(std::memory_order_seq_cst); } +/** + * @class SpinLock + * + * @brief + * Spinlock is used as an locking mechanism based on an atomic flag and + * waiting loops. It uses the cpu_relax "asm pause" command to optimize wasted + * time while the threads are waiting. + * + */ +class SpinLock { + public: + void lock() { // Before was memory_order_acquire + while (lock_flag.test_and_set(std::memory_order_seq_cst)) cpu_relax(); + } + // Before was memory_order_release + void unlock() { lock_flag.clear(std::memory_order_seq_cst); } -private: - // guaranteed by standard to be lock free! - mutable std::atomic_flag lock_flag = ATOMIC_FLAG_INIT; + private: + // guaranteed by standard to be lock free! + mutable std::atomic_flag lock_flag = ATOMIC_FLAG_INIT; }; diff --git a/include/utils/placeholder.hpp b/include/utils/placeholder.hpp index 31bdf7cb4..16eeace75 100644 --- a/include/utils/placeholder.hpp +++ b/include/utils/placeholder.hpp @@ -1,56 +1,90 @@ #pragma once -#include #include +#include "utils/assert.hpp" + +#include + +/** + * @class Placeholder + * + * @brief + * Placeholder is used to allocate memory for an object on heap providing + * methods for setting and getting the object and making sure that the + * object is initialized. + * + * @tparam T type of object to be wrapped in the placeholder + */ + template -class Placeholder -{ -public: - Placeholder() = default; +class Placeholder { + public: + Placeholder() = default; - Placeholder(Placeholder &) = delete; - Placeholder(Placeholder &&) = delete; + Placeholder(Placeholder &) = delete; + Placeholder(Placeholder &&) = delete; - ~Placeholder() - { - if (initialized) get().~T(); - }; + /** + * The destructor automatically calls the wrapped objects destructor. + */ + ~Placeholder() { + if (initialized) get().~T(); + }; - bool is_initialized() { return initialized; } + /** + * @return returns true if object is set in memory otherwise false. + */ + bool is_initialized() { return initialized; } - T &get() noexcept - { - assert(initialized); - return *data._M_ptr(); - } + T &get() noexcept { + runtime_assert(initialized, "Placeholder object not initialized"); + return *data._M_ptr(); + } - const T &get() const noexcept - { - assert(initialized); - return *data._M_ptr(); - } + /** + * @return const reference to object. + */ + const T &get() const noexcept { + runtime_assert(initialized, "Placeholder object not initialized"); + return *data._M_ptr(); + } - void set(const T &item) - { - new (data._M_addr()) T(item); - initialized = true; - } + /** + * Sets item in allocated memory and sets the initialized flag. + * + * @param T& item reference to the item initialized in allocated memory + */ + void set(const T &item) { + new (data._M_addr()) T(item); + initialized = true; + } - void set(T &&item) - { - new (data._M_addr()) T(std::move(item)); - initialized = true; - } + /** + * Moves item to allocated memory and sets initialized flag.1 + * + * @param T&& rvalue reference to the item which is moved to allocated memory + */ + void set(T &&item) { + new (data._M_addr()) T(std::move(item)); + initialized = true; + } - template - void emplace(Args &&... args) - { - new (data._M_addr()) T(args...); - initialized = true; - } + /** + * Emplaces item to allocated memory and calls the Constructor with specified + * arguments. + * + * @tparam Args type of arguments to be passed to the objects constructor. + * @param Parameters passed to the objects constructor. + */ + template + void emplace(Args &&... args) { + new (data._M_addr()) T(args...); + initialized = true; + } -private: - __gnu_cxx::__aligned_buffer data; - bool initialized = false; + private: + // libstd aligned buffer struct + __gnu_cxx::__aligned_buffer data; + bool initialized = false; }; diff --git a/include/utils/random/generator.h b/include/utils/random/generator.h index bf110a99a..dd3432330 100644 --- a/include/utils/random/generator.h +++ b/include/utils/random/generator.h @@ -1,4 +1,5 @@ #include +#include #include // namespace ::utils @@ -71,5 +72,15 @@ auto generate_vector(RandomGenerator &gen, int size) { return elements; } +// IMPORTANT +// be careful with RandomGenerator ranges and set size +// condition must be valid: size(set) << range(RandomGenerator) +template +auto generate_set(RandomGenerator &gen, int size) { + std::set elements; + while (elements.size() < size) elements.insert(gen.next()); + return elements; +} + }; // namespace utils::random }; // namespace ::utils diff --git a/tests/benchmark/data_structures/concurrent/skiplist_reverse_iteration.cpp b/tests/benchmark/data_structures/concurrent/skiplist_reverse_iteration.cpp new file mode 100644 index 000000000..a9282b0f6 --- /dev/null +++ b/tests/benchmark/data_structures/concurrent/skiplist_reverse_iteration.cpp @@ -0,0 +1,109 @@ +/** + @date: 2017-01-31 + @authors: Sandi Fatic + + These tests are used to benchmark the ReverseIterator vs the Find function + while iterating the whole skiplist in reverse. +*/ + +#include +#include +#include + +#include "benchmark/benchmark_api.h" +#include "data_structures/concurrent/skiplist.hpp" +#include "logging/default.hpp" +#include "logging/streams/stdout.hpp" +#include "utils/random/generator.h" + +using utils::random::NumberGenerator; +using IntegerGenerator = NumberGenerator, + std::default_random_engine, int>; + +void InsertSkiplist(SkipList *skiplist, int start, int end) { + auto accessor = skiplist->access(); + + for (int start = 0; start < end; start++) { + accessor.insert(std::move(start)); + } +} + +void InsertConcurrentSkiplist(SkipList *skiplist, int start, int end) { + int number_od_threads = std::thread::hardware_concurrency(); + std::vector threads(number_od_threads); + + for (int i = 0; i < number_od_threads; i++) { + int part = (end - start) / number_od_threads; + threads[i] = std::thread(InsertSkiplist, skiplist, start + i * part, + start + (i + 1) * part); + } + + for (int i = 0; i < number_od_threads; i++) { + threads[i].join(); + } +} + +static void ReverseFromRBegin(benchmark::State &state) { + while (state.KeepRunning()) { + state.PauseTiming(); + + SkipList skiplist; + InsertConcurrentSkiplist(&skiplist, 0, state.range(0)); + int counter = 10; + + auto accessor = skiplist.access(); + auto rbegin = accessor.rbegin(); + auto rend = accessor.rend(); + + state.ResumeTiming(); + + for (int i = 0; i < counter; i++) { + if (rbegin != rend) { + rbegin++; + } + } + } +} + +static void FindFromRBegin(benchmark::State &state) { + while (state.KeepRunning()) { + state.PauseTiming(); + + SkipList skiplist; + InsertConcurrentSkiplist(&skiplist, 0, state.range(0)); + int counter = 10; + + auto accessor = skiplist.access(); + auto rbegin = accessor.rbegin(); + auto rend = accessor.rend(); + + state.ResumeTiming(); + + for (int i = 0; i < counter; i++) { + accessor.find(*rbegin - i); + } + } +} + +auto BM_ReverseFromRBegin = [](benchmark::State &state) { + ReverseFromRBegin(state); +}; + +auto BM_FindFromRBegin = [](benchmark::State &state) { FindFromRBegin(state); }; + +int main(int argc, char **argv) { + logging::init_async(); + logging::log->pipe(std::make_unique()); + + benchmark::RegisterBenchmark("ReverseFromRBegin", BM_ReverseFromRBegin) + ->RangeMultiplier(2) + ->Range(1 << 10, 1 << 16); + + benchmark::RegisterBenchmark("FindFromRBegin", BM_FindFromRBegin) + ->RangeMultiplier(2) + ->Range(1 << 10, 1 << 16); + + benchmark::RunSpecifiedBenchmarks(); + + return 0; +} diff --git a/tests/unit/skiplist_reverse_iteration.cpp b/tests/unit/skiplist_reverse_iteration.cpp new file mode 100644 index 000000000..12216f214 --- /dev/null +++ b/tests/unit/skiplist_reverse_iteration.cpp @@ -0,0 +1,420 @@ +/** + @date: 2017-01-2017 + @authors: Sandi Fatic + + @brief + These tests are used to test the functionality of the reverse() function in + a single threaded scenario. For more concurrent tests look in the concurrent + testing folder. + + @todo + Concurrent tests are missing for now. +*/ + +#include + +#include "gtest/gtest.h" + +#include "data_structures/concurrent/skiplist.hpp" +#include "logging/default.cpp" +#include "utils/random/generator.h" + +using utils::random::NumberGenerator; +using IntegerGenerator = NumberGenerator, + std::default_random_engine, int>; + +/* + Tests Skiplist rbegin() and rend() iterators on a sequential dataset. +*/ +TEST(SkipListReverseIteratorTest, SequentialIteratorsBeginToEnd) { + SkipList skiplist; + + auto accessor = skiplist.access(); + int number_of_elements = 1024 * 16; + + for (int i = 0; i < number_of_elements; i++) accessor.insert(std::move(i)); + + auto rbegin = accessor.rbegin(); + auto rend = accessor.rend(); + + while (rbegin != rend) { + ASSERT_EQ(number_of_elements - 1, *rbegin); + rbegin++; + number_of_elements--; + } +} + +/* + Tests Skiplist rbegin() and rend() iterators on a random dataset. +*/ +TEST(SkipListReverseIteratorTest, RandomIteratorsBeginToEnd) { + SkipList skiplist; + + auto accessor = skiplist.access(); + int number_of_elements = 1024 * 16; + + IntegerGenerator generator(0, 1000000000); + std::set elems_set = + utils::random::generate_set(generator, number_of_elements); + + int end = elems_set.size(); + std::vector elems_descending(end); + + for (auto el : elems_set) { + end--; + accessor.insert(std::move(el)); + elems_descending[end] = el; + } + + auto rbegin = accessor.rbegin(); + auto rend = accessor.rend(); + + while (rbegin != rend) { + ASSERT_EQ(elems_descending[end], *rbegin); + end++; + rbegin++; + } +} + +/* + Tests Skiplist reverse() when element exists. The skiplist uses a sequential + dataset and the element provided exists is in range of the dataset. The + reverse function should return an std::pair. +*/ +TEST(SkipListReverseIteratorTest, SequentialIteratorsElementExists) { + SkipList skiplist; + auto accessor = skiplist.access(); + + int number_of_elements = 1024 * 16; + + for (int i = 0; i < number_of_elements; i++) accessor.insert(std::move(i)); + + int element = 1024 * 8; + auto reverse_pair = accessor.reverse(element); + + ASSERT_EQ(reverse_pair.second, true); + + auto rbegin = reverse_pair.first; + auto rend = accessor.rend(); + + while (rbegin != rend) { + ASSERT_EQ(element - 1, *rbegin); + rbegin++; + element--; + } +} + +/* + Tests Skiplist reverse() when element exists. The skiplist uses a random + dataset and the element provide exists in the random dataset. The reverse + function should return an std::pair. +*/ +TEST(SkipListReverseIteratorTest, RandomIteratorsElementExists) { + SkipList skiplist; + + auto accessor = skiplist.access(); + int number_of_elements = 1024 * 16; + + IntegerGenerator generator(0, 1000000000); + std::set elems_set = + utils::random::generate_set(generator, number_of_elements); + + int end = elems_set.size(); + std::vector elems_descending(end); + + for (auto el : elems_set) { + end--; + accessor.insert(std::move(el)); + elems_descending[end] = el; + } + + int middle = end / 2; + auto reverse_pair = accessor.reverse(elems_descending[middle]); + + ASSERT_EQ(reverse_pair.second, true); + + auto rbegin = reverse_pair.first; + auto rend = accessor.rend(); + + while (rbegin != rend) { + ASSERT_EQ(elems_descending[middle + 1], *rbegin); + middle++; + rbegin++; + } +} + +/* + Tests Skiplist reverse() when element exists and the element is the first one + in the skiplist. The skiplist uses a sequential dataset. The reverse function + should return an std::pair. +*/ +TEST(SkipListReverseIteratorTest, SequentialIteratorsMinimumElement) { + SkipList skiplist; + + auto accessor = skiplist.access(); + int number_of_elements = 1024 * 16; + + for (int i = 0; i < number_of_elements; i++) accessor.insert(std::move(i)); + + auto reverse_pair = accessor.reverse(0); + + ASSERT_EQ(reverse_pair.second, true); + + auto rbegin = reverse_pair.first; + auto rend = accessor.rend(); + + ASSERT_EQ(rbegin, rend); +} + +/* + Tests Skiplist reverse() when element exists and the element is the first one + in the skiplist. The skiplist uses a random dataset. The reverse function + should return an std::pair. +*/ +TEST(SkipListReverseIteratorTest, RandomIteratorsMinimumElement) { + SkipList skiplist; + + auto accessor = skiplist.access(); + int number_of_elements = 1024 * 16; + + IntegerGenerator generator(0, 1000000000); + std::set elems_set = + utils::random::generate_set(generator, number_of_elements); + + auto min_el = std::min_element(elems_set.begin(), elems_set.end()); + for (auto el : elems_set) accessor.insert(std::move(el)); + + auto reverse_pair = accessor.reverse(*min_el); + + ASSERT_EQ(reverse_pair.second, true); + + auto rbegin = reverse_pair.first; + auto rend = accessor.rend(); + + ASSERT_EQ(rbegin, rend); +} + +/* + Tests Skiplist reverse() when element exists and the element is the last one + in the skiplist. The skiplist uses a sequential dataset. The reverse function + should return an std::pair. +*/ +TEST(SkipListReverseIteratorTest, SequentialIteratorsMaximumElement) { + SkipList skiplist; + + auto accessor = skiplist.access(); + int number_of_elements = 1024 * 16; + + for (int i = 0; i < number_of_elements; i++) accessor.insert(std::move(i)); + + auto reverse_pair = accessor.reverse(number_of_elements - 1); + + ASSERT_EQ(reverse_pair.second, true); + + auto rbegin = reverse_pair.first; + auto rbeing_real = accessor.rbegin(); + rbeing_real++; + + ASSERT_EQ(rbegin, rbeing_real); +} + +/* + Tests Skiplist reverse() when element exists and the element is the last one + in the skiplist. the skiplist uses a random dataset. The reverse function + should return and std::pair,iterator_to_smaller_element, true>. +*/ +TEST(SkipListReverseIteratorTest, RandomIteratorsMaximumElement) { + SkipList skiplist; + + auto accessor = skiplist.access(); + int number_of_elements = 1024 * 16; + + IntegerGenerator generator(0, 1000000000); + std::set elems_set = + utils::random::generate_set(generator, number_of_elements); + + auto max_el = std::max_element(elems_set.begin(), elems_set.end()); + for (auto el : elems_set) accessor.insert(std::move(el)); + + auto reverse_pair = accessor.reverse(*max_el); + + ASSERT_EQ(reverse_pair.second, true); + + auto rbegin = reverse_pair.first; + auto rbeing_real = accessor.rbegin(); + rbeing_real++; + + ASSERT_EQ(rbegin, rbeing_real); +} + +/* + Tests Skipslist reverse() when element out of bounds. The skiplist uses a + sequential dataset and the element provided is bigger then the last element + in skiplist. Reverse function should return an std::pair. +*/ +TEST(SkipListReverseIteratorTest, SequentialIteratorsElementBiggerThanLast) { + SkipList skiplist; + auto accessor = skiplist.access(); + + int number_of_elements = 1024 * 16; + + for (int i = 0; i < number_of_elements; i++) accessor.insert(std::move(i)); + + auto reverse_pair = accessor.reverse(number_of_elements + 1); + + ASSERT_EQ(reverse_pair.second, false); + + auto rbegin = reverse_pair.first; + auto rend = accessor.rend(); + + ASSERT_EQ(rend, rbegin); +} + +/* + Tests Skipslist reverse() when element out of bounds. The skiplist uses a + random dataset and the element provide is bigger then the last element in the + skiplist. Reverse function should return an std::pair. +*/ +TEST(SkipListReverseIteratorTest, RandomIteratorsElementBiggerThanLast) { + SkipList skiplist; + auto accessor = skiplist.access(); + + int number_of_elements = 1024 * 16; + + IntegerGenerator generator(0, 1000000000); + std::set elems_set = + utils::random::generate_set(generator, number_of_elements); + + auto max_el = std::max_element(elems_set.begin(), elems_set.end()); + for (auto el : elems_set) accessor.insert(std::move(el)); + + auto reverse_pair = accessor.reverse(*max_el + 1); + + ASSERT_EQ(reverse_pair.second, false); + + auto rbegin = reverse_pair.first; + auto rend = accessor.rend(); + + ASSERT_EQ(rend, rbegin); +} + +/* + Tests Skipslist reverse() when element out of bounds. + The skiplist uses a sequential dataset and the element provided is lower + then the first element in skiplist. Reverse function should return an + std::pair. +*/ +TEST(SkipListReverseIteratorTest, SequentialIteratorsElementLowerThanFirst) { + SkipList skiplist; + auto accessor = skiplist.access(); + + int number_of_elements = 1024 * 16; + + for (int i = 0; i < number_of_elements; i++) accessor.insert(std::move(i)); + + auto reverse_pair = accessor.reverse(-1); + + ASSERT_EQ(reverse_pair.second, false); + + auto rbegin = reverse_pair.first; + auto rend = accessor.rend(); + + ASSERT_EQ(rend, rbegin); +} + +/* + Tests Skiplist reverse() when element out of bounds. The skiplist uses a + random dataset and the element provided is lower then the first element in + skiplist. Reverse function should return an std::pair. +*/ +TEST(SkipListReverseIteratorTest, RandomIteratorsElementLowerThanFirst) { + SkipList skiplist; + auto accessor = skiplist.access(); + + int number_of_elements = 1024 * 16; + + IntegerGenerator generator(0, 1000000000); + std::set elems_set = + utils::random::generate_set(generator, number_of_elements); + + auto min_el = std::min_element(elems_set.begin(), elems_set.end()); + for (auto el : elems_set) accessor.insert(std::move(el)); + + auto reverse_pair = accessor.reverse(*min_el - 1); + + ASSERT_EQ(reverse_pair.second, false); + + auto rbegin = reverse_pair.first; + auto rend = accessor.rend(); + + ASSERT_EQ(rend, rbegin); +} + + +/* + Tests Skiplist ReverseIterator when concurrently inserting an element while + iterating. The inserted element should also be traversed. +*/ +TEST(SkipListReverseIteratorTest, InsertWhileIteratingTest) { + SkipList skiplist; + auto accessor = skiplist.access(); + int number_of_elements = 1024 * 16; + + for (int i = 1; i < number_of_elements; i+=2) accessor.insert(std::move(i)); + + auto rbegin = accessor.rbegin(); + auto rend = accessor.rend(); + + for (int i = 0; i < number_of_elements; i+=2) accessor.insert(std::move(i)); + + int element = number_of_elements - 1; + while (rbegin != rend) { + ASSERT_EQ(element, *rbegin); + rbegin++; + element--; + } +} + + +/* + Tests Skiplist ReverseIterator when concurrently deleting an element while + iterating. The deleted element shouldn't be traversed except if the element + is deleted while pointing to the element. +*/ +TEST(SkipListReverseIteratorTest, DeleteWhileIteratingTest) { + SkipList skiplist; + auto accessor = skiplist.access(); + int number_of_elements = 1024 * 16; + + for (int i = 0; i < number_of_elements; i++) accessor.insert(std::move(i)); + + auto rbegin = accessor.rbegin(); + auto rend = accessor.rend(); + + int element = number_of_elements - 2; + // check element which will be deleted + rbegin++; + ASSERT_EQ(element, *rbegin); + + // delete elements + for (int i = 0; i < number_of_elements; i+=2) accessor.remove(i); + + // check if still points to the same after delete + ASSERT_EQ(element, *rbegin); + rbegin++; + + // check all deleted elements after + while (rbegin != rend && element > 0) { + ASSERT_EQ(element-1, *rbegin); + rbegin++; + element-=2; + } +} + +int main(int argc, char** argv) { + logging::init_sync(); + logging::log->pipe(std::make_unique()); + + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +}