diff --git a/Testing/Temporary/CTestCostData.txt b/Testing/Temporary/CTestCostData.txt new file mode 100644 index 000000000..ed97d539c --- /dev/null +++ b/Testing/Temporary/CTestCostData.txt @@ -0,0 +1 @@ +--- diff --git a/Testing/Temporary/LastTest.log b/Testing/Temporary/LastTest.log new file mode 100644 index 000000000..df85b1e79 --- /dev/null +++ b/Testing/Temporary/LastTest.log @@ -0,0 +1,3 @@ +Start testing: Jul 28 19:05 BST +---------------------------------------------------------- +End testing: Jul 28 19:05 BST diff --git a/format.clang-format b/format.clang-format new file mode 100644 index 000000000..fa5dbcb08 --- /dev/null +++ b/format.clang-format @@ -0,0 +1,90 @@ +--- +Language: Cpp +# BasedOnStyle: LLVM +AccessModifierOffset: -4 +AlignAfterOpenBracket: Align +AlignConsecutiveAssignments: false +AlignConsecutiveDeclarations: false +AlignEscapedNewlinesLeft: false +AlignOperands: true +AlignTrailingComments: true +AllowAllParametersOfDeclarationOnNextLine: true +AllowShortBlocksOnASingleLine: false +AllowShortCaseLabelsOnASingleLine: false +AllowShortFunctionsOnASingleLine: All +AllowShortIfStatementsOnASingleLine: true +AllowShortLoopsOnASingleLine: false +AlwaysBreakAfterDefinitionReturnType: None +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: false +AlwaysBreakTemplateDeclarations: true +BinPackArguments: true +BinPackParameters: true +BraceWrapping: + AfterClass: true + AfterControlStatement: false + AfterEnum: true + AfterFunction: true + AfterNamespace: true + AfterObjCDeclaration: false + AfterStruct: true + AfterUnion: true + BeforeCatch: false + BeforeElse: false + IndentBraces: false +BreakBeforeBinaryOperators: None +BreakBeforeBraces: Custom +BreakBeforeTernaryOperators: true +BreakConstructorInitializersBeforeComma: false +ColumnLimit: 80 +CommentPragmas: '^ IWYU pragma:' +ConstructorInitializerAllOnOneLineOrOnePerLine: false +ConstructorInitializerIndentWidth: 4 +ContinuationIndentWidth: 4 +Cpp11BracedListStyle: true +DerivePointerAlignment: false +DisableFormat: false +ExperimentalAutoDetectBinPacking: false +ForEachMacros: [ foreach, Q_FOREACH, BOOST_FOREACH ] +IncludeCategories: + - Regex: '^"(llvm|llvm-c|clang|clang-c)/' + Priority: 2 + - Regex: '^(<|"(gtest|isl|json)/)' + Priority: 3 + - Regex: '.*' + Priority: 1 +IndentCaseLabels: false +IndentWidth: 2 +IndentWrappedFunctionNames: false +KeepEmptyLinesAtTheStartOfBlocks: true +MacroBlockBegin: '' +MacroBlockEnd: '' +MaxEmptyLinesToKeep: 1 +NamespaceIndentation: None +ObjCBlockIndentWidth: 2 +ObjCSpaceAfterProperty: false +ObjCSpaceBeforeProtocolList: true +PenaltyBreakBeforeFirstCallParameter: 19 +PenaltyBreakComment: 300 +PenaltyBreakFirstLessLess: 120 +PenaltyBreakString: 1000 +PenaltyExcessCharacter: 1000000 +PenaltyReturnTypeOnItsOwnLine: 60 +PointerAlignment: Right +ReflowComments: true +SortIncludes: true +SpaceAfterCStyleCast: false +SpaceBeforeAssignmentOperators: true +SpaceBeforeParens: ControlStatements +SpaceInEmptyParentheses: false +SpacesBeforeTrailingComments: 1 +SpacesInAngles: false +SpacesInContainerLiterals: true +SpacesInCStyleCastParentheses: false +SpacesInParentheses: false +SpacesInSquareBrackets: false +Standard: "C++11" +TabWidth: 8 +UseTab: Never +... + diff --git a/src/data_structures/concurrent/concurrent_map.hpp b/src/data_structures/concurrent/concurrent_map.hpp index a9e04a495..c9d9064e7 100644 --- a/src/data_structures/concurrent/concurrent_map.hpp +++ b/src/data_structures/concurrent/concurrent_map.hpp @@ -1,9 +1,107 @@ #pragma once -#include "data_structures/skiplist/skiplist_map.hpp" +#include "data_structures/concurrent/skiplist.hpp" +#include "utils/total_ordering.hpp" -template -class ConcurrentMap : public SkipListMap -{ - // TODO implement with SkipList +using std::pair; + +template class ConcurrentMap { + + class Item : public TotalOrdering, + public TotalOrdering, + public TotalOrdering, + public pair { + public: + using pair::pair; + + friend constexpr bool operator<(const Item &lhs, const Item &rhs) { + std::pair *a; + return lhs.first < rhs.first; + } + + friend constexpr bool operator==(const Item &lhs, const Item &rhs) { + return lhs.first == rhs.first; + } + + friend constexpr bool operator<(const K &lhs, const Item &rhs) { + return lhs < rhs.first; + } + + friend constexpr bool operator==(const K &lhs, const Item &rhs) { + return lhs == rhs.first; + } + + friend constexpr bool operator<(const Item &lhs, const K &rhs) { + return lhs.first < rhs; + } + + friend constexpr bool operator==(const Item &lhs, const K &rhs) { + return lhs.first == rhs; + } + }; + + typedef SkipList list; + typedef typename SkipList::Iterator list_it; + typedef typename SkipList::ConstIterator list_it_con; + +public: + ConcurrentMap() {} + + friend class Accessor; + class Accessor { + friend class ConcurrentMap; + + Accessor(list *skiplist) : accessor(skiplist->access()) {} + + public: + Accessor(const Accessor &) = delete; + + Accessor(Accessor &&other) : accessor(std::move(other.accessor)) {} + + ~Accessor() {} + + list_it begin() { return accessor.begin(); } + + list_it_con begin() const { return accessor.cbegin(); } + + list_it_con cbegin() const { return accessor.cbegin(); } + + list_it end() { return accessor.end(); } + + list_it_con end() const { return accessor.cend(); } + + list_it_con cend() const { return accessor.cend(); } + + std::pair insert(const K &key, const T &data) { + return accessor.insert(Item(key, data)); + } + + std::pair insert(const K &key, T &&data) { + return accessor.insert(Item(key, std::forward(data))); + } + + std::pair insert(K &&key, T &&data) { + return accessor.insert(Item(std::forward(key), std::forward(data))); + } + + list_it_con find(const K &key) const { return accessor.find(key); } + + list_it find(const K &key) { return accessor.find(key); } + + bool contains(const K &key) const { return this->find(key) != this->end(); } + + bool remove(const K &key) { return accessor.remove(key); } + + size_t size() const { return accessor.size(); } + + private: + typename list::Accessor accessor; + }; + + Accessor access() { return Accessor(&skiplist); } + + const Accessor access() const { return Accessor(&skiplist); } + +private: + list skiplist; }; diff --git a/src/data_structures/concurrent/skiplist.hpp b/src/data_structures/concurrent/skiplist.hpp index abad9828d..710275bf9 100644 --- a/src/data_structures/concurrent/skiplist.hpp +++ b/src/data_structures/concurrent/skiplist.hpp @@ -1,11 +1,11 @@ #pragma once #include -#include #include +#include -#include "utils/random/fast_binomial.hpp" #include "utils/placeholder.hpp" +#include "utils/random/fast_binomial.hpp" #include "threading/sync/lockable.hpp" #include "threading/sync/spinlock.hpp" @@ -46,7 +46,7 @@ * * The implementation has an interface which closely resembles the functions * with arguments and returned types frequently used by the STL. - * + * * Example usage: * Skiplist skiplist; * @@ -93,589 +93,465 @@ * @tparam lock_t Lock type used when locking is needed during the creation * and deletion of nodes. */ -template -class SkipList : private Lockable -{ +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; + // 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(item, height); - } + 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::forward(item), height); - } + T &value() { return data.get(); } - static void destroy(Node* node) - { - node->~Node(); - free(node); - } + const T &value() const { return data.get(); } - 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}; - } - - Node(T&& data, uint8_t height) : Node(height) - { - this->data.set(std::forward(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(); - } - - 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; - }; - - SkipList() : header(Node::sentinel(H)) {} - - 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(); - } - - std::pair insert(const T& item) - { - return skiplist->insert(item, preds, succs); - } - - std::pair insert(T&& item) - { - return skiplist->insert(std::forward(item), preds, succs); - } - - ConstIterator find(const T& item) const - { - return static_cast(*skiplist).find(item); - } - - Iterator find(const T& item) - { - return skiplist->find(item); - } - - bool contains(const T& item) const - { - return this->find(item) != this->end(); - } - - bool remove(const T& item) - { - return skiplist->remove(item, preds, succs); - } - - size_t size() const - { - return skiplist->size(); - } - - private: - SkipList* skiplist; - Node* preds[H], *succs[H]; - }; - - Accessor access() - { - return Accessor(this); + 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); } - const Accessor access() const - { - return Accessor(this); + static Node *create(const T &item, uint8_t height) { + return create(item, height); } + 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::forward(item), height); + } + + 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}; + } + + Node(T &&data, uint8_t height) : Node(height) { + this->data.set(std::forward(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(); + } + + 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; + }; + + 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(); } + + std::pair insert(const T &item) { + return skiplist->insert(item, preds, succs); + } + + std::pair insert(T &&item) { + return skiplist->insert(std::forward(item), preds, succs); + } + + template ConstIterator find(const K &item) const { + return static_cast(*skiplist).find(item); + } + + template Iterator find(const K &item) { + return skiplist->find(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); + } + + 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; + using guard_t = std::unique_lock; - Iterator begin() - { - return Iterator(header->forward(0)); + 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(); } + + template bool greater(const K &item, const Node *const node) { + return node && item > node->value(); + } + + template bool less(const K &item, const Node *const node) { + return (node == nullptr) || item < node->value(); + } + + template ConstIterator find(const K &item) const { + return const_cast(this)->find_node(item); + } + + template Iterator find(const K &item) { + return find_node(item); + } + + template It find_node(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(); + + // 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 + int find_path(Node *from, int start, const K &item, Node *preds[], + Node *succs[]) { + int level_found = -1; + Node *pred = from; + + 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; } - ConstIterator begin() const - { - return ConstIterator(header->forward(0)); + 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()); } - ConstIterator cbegin() const - { - return ConstIterator(header->forward(0)); + return valid; + } + + std::pair insert(T &&data, Node *preds[], Node *succs[]) { + while (true) { + // TODO: before here was data.first + auto level = find_path(header, 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; + + // you have the locks, create a new node + auto new_node = Node::create(std::forward(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}, true}; } + } - Iterator end() - { - return Iterator(); + bool ok_delete(Node *node, int level) { + return node->flags.is_fully_linked() && node->height - 1 == level && + !node->flags.is_marked(); + } + + template bool remove(const K &item, 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, 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; } + } - ConstIterator end() const - { - return ConstIterator(); - } - - ConstIterator cend() const - { - return ConstIterator(); - } - - size_t size() const - { - return count.load(); - } - - bool greater(const T& item, const Node* const node) - { - return node && item > node->value(); - } - - bool less(const T& item, const Node* const node) - { - return (node == nullptr) || item < node->value(); - } - - ConstIterator find(const T& item) const - { - return const_cast(this)->find_node(item); - } - - Iterator find(const T& item) - { - return find_node(item); - } - - template - It find_node(const T& 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(); - - // 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); - } - } - - int find_path(Node* from, int start, const T& item, - Node* preds[], Node* succs[]) - { - int level_found = -1; - Node* pred = from; - - 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; - } - - 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(T&& data, Node* preds[], Node* succs[]) - { - while(true) - { - // TODO: before here was data.first - auto level = find_path(header, 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; - - // you have the locks, create a new node - auto new_node = Node::create(std::forward(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}, 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 T& item, 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, 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; + // number of elements + std::atomic count{0}; + Node *header; + SkiplistGC gc; }; template diff --git a/src/data_structures/skiplist/skiplist_gc.hpp b/src/data_structures/skiplist/skiplist_gc.hpp index c2ac53fd0..7faf96008 100644 --- a/src/data_structures/skiplist/skiplist_gc.hpp +++ b/src/data_structures/skiplist/skiplist_gc.hpp @@ -8,48 +8,45 @@ #include "threading/sync/spinlock.hpp" template -class SkiplistGC : public LazyGC, lock_t> -{ +class SkiplistGC : public LazyGC, lock_t> { public: - // release_ref method should be called by a thread - // when the thread finish it job over object - // which has to be lazy cleaned - // if thread counter becames zero, all objects in the local_freelist - // are going to be deleted - // the only problem with this approach is that - // GC may never be called, but for now we can deal with that - void release_ref() + // release_ref method should be called by a thread + // when the thread finish it job over object + // which has to be lazy cleaned + // if thread counter becames zero, all objects in the local_freelist + // are going to be deleted + // the only problem with this approach is that + // GC may never be called, but for now we can deal with that + void release_ref() { + std::vector local_freelist; + + // take freelist if there is no more threads { - std::vector local_freelist; - - // take freelist if there is no more threads - { - auto lock = this->acquire_unique(); - --this->count; - if (this->count == 0) { - freelist.swap(local_freelist); - } - } - - if (local_freelist.size() > 0) { - std::cout << "GC started" << std::endl; - std::cout << "Local skiplist size: " << - local_freelist.size() << std::endl; - long long counter = 0; - // destroy all elements from local_freelist - for (auto element : local_freelist) { - counter++; - if (element->flags.is_marked()) T::destroy(element); - } - std::cout << "Number of destroyed elements " << counter << std::endl; - } + auto lock = this->acquire_unique(); + --this->count; + if (this->count == 0) { + freelist.swap(local_freelist); + } } - void collect(T *node) - { - freelist.add(node); + if (local_freelist.size() > 0) { + std::cout << "GC started" << std::endl; + std::cout << "Local list size: " << local_freelist.size() << std::endl; + long long counter = 0; + // destroy all elements from local_freelist + for (auto element : local_freelist) { + + if (element->flags.is_marked()) { + T::destroy(element); + counter++; + } + } + std::cout << "Number of destroyed elements " << counter << std::endl; } + } + + void collect(T *node) { freelist.add(node); } private: - FreeList freelist; + FreeList freelist; }; diff --git a/src/memory/lazy_gc.hpp b/src/memory/lazy_gc.hpp index 38502de6d..8484f8796 100644 --- a/src/memory/lazy_gc.hpp +++ b/src/memory/lazy_gc.hpp @@ -1,24 +1,24 @@ #pragma once +// TODO: remove from here and from the project #include +#include #include "threading/sync/lockable.hpp" #include "utils/crtp.hpp" template -class LazyGC : public Crtp, public Lockable -{ +class LazyGC : public Crtp, public Lockable { public: - // add_ref method should be called by a thread - // when the thread has to do something over - // object which has to be lazy cleaned when - // the thread finish it job - void add_ref() - { - auto lock = this->acquire_unique(); - ++count; - } + // add_ref method should be called by a thread + // when the thread has to do something over + // object which has to be lazy cleaned when + // the thread finish it job + void add_ref() { + auto lock = this->acquire_unique(); + ++count; + } protected: - size_t count{0}; + size_t count{0}; }; diff --git a/src/storage/edges.hpp b/src/storage/edges.hpp index 23871419d..8349f9b2f 100644 --- a/src/storage/edges.hpp +++ b/src/storage/edges.hpp @@ -1,52 +1,46 @@ #pragma once #include "common.hpp" -#include "edge_accessor.hpp" #include "data_structures/concurrent/concurrent_map.hpp" +#include "edge_accessor.hpp" -class Edges -{ +class Edges { public: - Edge::Accessor find(tx::Transaction& t, const Id& id) - { - auto edges_accessor = edges.access(); - auto edges_iterator = edges_accessor.find(id); + Edge::Accessor find(tx::Transaction &t, const Id &id) { + auto edges_accessor = edges.access(); + auto edges_iterator = edges_accessor.find(id); - if (edges_iterator == edges_accessor.end()) - return Edge::Accessor(); + if (edges_iterator == edges_accessor.end()) + return Edge::Accessor(); - // find edge - auto edge = edges_iterator->second.find(t); + // find edge + auto edge = edges_iterator->second.find(t); - if (edge == nullptr) - return Edge::Accessor(); + if (edge == nullptr) + return Edge::Accessor(); - return Edge::Accessor(edge, &edges_iterator->second, this); - } + return Edge::Accessor(edge, &edges_iterator->second, this); + } - Edge::Accessor insert(tx::Transaction& t) - { - // get next vertex id - auto next = counter.next(std::memory_order_acquire); + Edge::Accessor insert(tx::Transaction &t) { + // get next vertex id + auto next = counter.next(std::memory_order_acquire); - // create new vertex record - EdgeRecord edge_record(next); + // create new vertex record + EdgeRecord edge_record(next); - // insert the new vertex record into the vertex store - auto edges_accessor = edges.access(); - auto result = edges_accessor.insert_unique( - next, - std::move(edge_record) - ); + // insert the new vertex record into the vertex store + auto edges_accessor = edges.access(); + auto result = edges_accessor.insert(next, std::move(edge_record)); - // create new vertex - auto inserted_edge_record = result.first; - auto edge = inserted_edge_record->second.insert(t); + // create new vertex + auto inserted_edge_record = result.first; + auto edge = inserted_edge_record->second.insert(t); - return Edge::Accessor(edge, &inserted_edge_record->second, this); - } + return Edge::Accessor(edge, &inserted_edge_record->second, this); + } private: - ConcurrentMap edges; - AtomicCounter counter; + ConcurrentMap edges; + AtomicCounter counter; }; diff --git a/src/storage/indexes/index.hpp b/src/storage/indexes/index.hpp index 98a90ccd8..850835bd5 100644 --- a/src/storage/indexes/index.hpp +++ b/src/storage/indexes/index.hpp @@ -7,39 +7,34 @@ #include "storage/indexes/index_record_collection.hpp" #include "storage/label/label.hpp" -template -class Index -{ +template class Index { public: - using container_t = ConcurrentMap; + using container_t = ConcurrentMap; - Index() : index(std::make_unique()) {} + Index() : index(std::make_unique()) {} - auto update(const Label &label, VertexIndexRecord &&index_record) - { - auto accessor = index->access(); - auto label_ref = label_ref_t(label); + auto update(const Label &label, VertexIndexRecord &&index_record) { + auto accessor = index->access(); + auto label_ref = label_ref_t(label); - // create Index Record Collection if it doesn't exist - if (!accessor.contains(label_ref)) { - accessor.insert_unique(label_ref, - std::move(VertexIndexRecordCollection())); - } - - // add Vertex Index Record to the Record Collection - auto &record_collection = (*accessor.find(label_ref)).second; - record_collection.add(std::forward(index_record)); + // create Index Record Collection if it doesn't exist + if (!accessor.contains(label_ref)) { + accessor.insert(label_ref, std::move(VertexIndexRecordCollection())); } - VertexIndexRecordCollection& find(const Label& label) - { - // TODO: accessor should be outside? - // bacause otherwise GC could delete record that has just be returned - auto label_ref = label_ref_t(label); - auto accessor = index->access(); - return (*accessor.find(label_ref)).second; - } + // add Vertex Index Record to the Record Collection + auto &record_collection = (*accessor.find(label_ref)).second; + record_collection.add(std::forward(index_record)); + } + + VertexIndexRecordCollection &find(const Label &label) { + // TODO: accessor should be outside? + // bacause otherwise GC could delete record that has just be returned + auto label_ref = label_ref_t(label); + auto accessor = index->access(); + return (*accessor.find(label_ref)).second; + } private: - std::unique_ptr index; + std::unique_ptr index; }; diff --git a/src/storage/vertices.cpp b/src/storage/vertices.cpp index d879a5e17..4f4075845 100644 --- a/src/storage/vertices.cpp +++ b/src/storage/vertices.cpp @@ -1,50 +1,45 @@ #include "storage/vertices.hpp" -const Vertex::Accessor Vertices::find(tx::Transaction &t, const Id &id) -{ - auto vertices_accessor = vertices.access(); - auto vertices_iterator = vertices_accessor.find(id); +const Vertex::Accessor Vertices::find(tx::Transaction &t, const Id &id) { + auto vertices_accessor = vertices.access(); + auto vertices_iterator = vertices_accessor.find(id); - if (vertices_iterator == vertices_accessor.end()) - return Vertex::Accessor(); + if (vertices_iterator == vertices_accessor.end()) + return Vertex::Accessor(); - // find vertex - auto vertex = vertices_iterator->second.find(t); + // find vertex + auto vertex = vertices_iterator->second.find(t); - if (vertex == nullptr) return Vertex::Accessor(); + if (vertex == nullptr) + return Vertex::Accessor(); - return Vertex::Accessor(vertex, &vertices_iterator->second, this); + return Vertex::Accessor(vertex, &vertices_iterator->second, this); } -Vertex::Accessor Vertices::insert(tx::Transaction &t) -{ - // get next vertex id - auto next = counter.next(); +Vertex::Accessor Vertices::insert(tx::Transaction &t) { + // get next vertex id + auto next = counter.next(); - // create new vertex record - VertexRecord vertex_record(next); - // vertex_record.id(next); + // create new vertex record + VertexRecord vertex_record(next); + // vertex_record.id(next); - // insert the new vertex record into the vertex store - auto vertices_accessor = vertices.access(); - auto result = - vertices_accessor.insert_unique(next, std::move(vertex_record)); + // insert the new vertex record into the vertex store + auto vertices_accessor = vertices.access(); + auto result = vertices_accessor.insert(next, std::move(vertex_record)); - // create new vertex - auto inserted_vertex_record = result.first; - auto vertex = inserted_vertex_record->second.insert(t); + // create new vertex + auto inserted_vertex_record = result.first; + auto vertex = inserted_vertex_record->second.insert(t); - return Vertex::Accessor(vertex, &inserted_vertex_record->second, this); + return Vertex::Accessor(vertex, &inserted_vertex_record->second, this); } void Vertices::update_label_index(const Label &label, - VertexIndexRecord &&index_record) -{ - label_index.update(label, - std::forward(index_record)); + VertexIndexRecord &&index_record) { + label_index.update(label, std::forward(index_record)); } -VertexIndexRecordCollection& Vertices::find_label_index(const Label& label) -{ - return label_index.find(label); +VertexIndexRecordCollection &Vertices::find_label_index(const Label &label) { + return label_index.find(label); } diff --git a/src/utils/total_ordering.hpp b/src/utils/total_ordering.hpp index 97f8871e3..c59d4ea86 100644 --- a/src/utils/total_ordering.hpp +++ b/src/utils/total_ordering.hpp @@ -1,25 +1,19 @@ #pragma once -template -struct TotalOrdering -{ - friend constexpr bool operator!=(const Derived& a, const Derived& b) - { - return !(a == b); - } +template struct TotalOrdering { + friend constexpr bool operator!=(const Derived &a, const Other &b) { + return !(a == b); + } - friend constexpr bool operator<=(const Derived& a, const Derived& b) - { - return a < b || a == b; - } + friend constexpr bool operator<=(const Derived &a, const Other &b) { + return a < b || a == b; + } - friend constexpr bool operator>(const Derived& a, const Derived& b) - { - return !(a <= b); - } + friend constexpr bool operator>(const Derived &a, const Other &b) { + return !(a <= b); + } - friend constexpr bool operator>=(const Derived& a, const Derived& b) - { - return !(a < b); - } + friend constexpr bool operator>=(const Derived &a, const Other &b) { + return !(a < b); + } }; diff --git a/tests/concurrent/common.h b/tests/concurrent/common.h new file mode 100644 index 000000000..8587599ca --- /dev/null +++ b/tests/concurrent/common.h @@ -0,0 +1,135 @@ +#include "stdio.h" +#include "stdlib.h" +#include "string.h" +#include +#include +#include +#include +#include + +#include "data_structures/concurrent/concurrent_map.hpp" +#include "data_structures/concurrent/skiplist.hpp" +#include "data_structures/static_array.hpp" +#include "utils/assert.hpp" +#include "utils/sysinfo/memory.hpp" + +using std::cout; +using std::endl; +using skiplist_t = ConcurrentMap; +using namespace std::chrono_literals; + +auto rand_gen(size_t n) { + std::default_random_engine generator; + std::uniform_int_distribution distribution(0, n - 1); + return std::bind(distribution, generator); +} + +// Returns random bool generator with distribution of 1 true for n false. +auto rand_gen_bool(size_t n = 1) { + auto gen = rand_gen(n + 1); + return [=]() mutable { return gen() == 0; }; +} + +void check_present_same(skiplist_t::Accessor &acc, + std::pair> &owned) { + for (auto num : owned.second) { + permanent_assert(acc.find(num)->second == owned.first, + "My data is present and my"); + } +} +void check_present_same(skiplist_t::Accessor &acc, size_t owner, + std::vector &owned) { + for (auto num : owned) { + permanent_assert(acc.find(num)->second == owner, + "My data is present and my"); + } +} + +void check_size(const skiplist_t::Accessor &acc, long long size) { + // check size + + permanent_assert(acc.size() == size, + "Size should be " << size << ", but size is " << acc.size()); + + // check count + + size_t iterator_counter = 0; + + for (auto elem : acc) { + ++iterator_counter; + } + permanent_assert(iterator_counter == size, "Iterator count should be " + << size << ", but size is " + << acc.size()); +} + +template +std::vector>> +run(size_t threads_no, skiplist_t &skiplist, + std::function f) { + std::vector>> futures; + + for (size_t thread_i = 0; thread_i < threads_no; ++thread_i) { + std::packaged_task()> task([&skiplist, f, thread_i]() { + return std::pair(thread_i, f(skiplist.access(), thread_i)); + }); // wrap the function + futures.push_back(task.get_future()); // get a future + std::thread(std::move(task)).detach(); + } + return futures; +} + +template auto collect(std::vector> &collect) { + std::vector collection; + for (auto &fut : collect) { + collection.push_back(fut.get()); + } + return collection; +} + +template +auto insert_try(skiplist_t::Accessor &acc, size_t &downcount, + std::vector &owned) { + return [&](K key, D data) mutable { + if (acc.insert(key, data).second) { + downcount--; + owned.push_back(key); + } + }; +} + +int parseLine(char *line) { + // This assumes that a digit will be found and the line ends in " Kb". + int i = strlen(line); + const char *p = line; + while (*p < '0' || *p > '9') + p++; + line[i - 3] = '\0'; + i = atoi(p); + return i; +} + +int currently_used_memory() { // Note: this value is in KB! + FILE *file = fopen("/proc/self/status", "r"); + int result = -1; + char line[128]; + + while (fgets(line, 128, file) != NULL) { + if (strncmp(line, "VmSize:", 7) == 0) { + result = parseLine(line); + break; + } + } + fclose(file); + return result; +} + +void memory_check(size_t no_threads, std::function f) { + long long start = currently_used_memory(); + f(); + long long leaked = + currently_used_memory() - start - + no_threads * 73732; // OS sensitive, 73732 size allocated for thread + std::cout << "leaked: " << leaked << "\n"; + permanent_assert(leaked <= 0, "Memory leak check"); +} diff --git a/tests/concurrent/skiplist.cpp b/tests/concurrent/skiplist.cpp index cfb3c661d..d08938dbb 100644 --- a/tests/concurrent/skiplist.cpp +++ b/tests/concurrent/skiplist.cpp @@ -1,85 +1,69 @@ -#include -#include -#include - -#include "data_structures/concurrent/concurrent_map.hpp" -#include "data_structures/static_array.hpp" -#include "utils/assert.hpp" -#include "utils/sysinfo/memory.hpp" - -using std::cout; -using std::endl; -using skiplist_t = ConcurrentMap; -using namespace std::chrono_literals; +#include "common.h" #define THREADS_NO 1 -constexpr size_t elems_per_thread = 1000; +constexpr size_t elems_per_thread = 16e5; -int main() -{ +int main() { + memory_check(THREADS_NO, [&] { ds::static_array threads; skiplist_t skiplist; - + // put THREADS_NO * elems_per_thread items to the skiplist for (size_t thread_i = 0; thread_i < THREADS_NO; ++thread_i) { - threads[thread_i] = std::thread( - [&skiplist](size_t start, size_t end) { - auto accessor = skiplist.access(); - for (size_t elem_i = start; elem_i < end; ++elem_i) { - accessor.insert_unique(elem_i, elem_i); - } - }, - thread_i * elems_per_thread, - thread_i * elems_per_thread + elems_per_thread); + threads[thread_i] = std::thread( + [&skiplist](size_t start, size_t end) { + auto accessor = skiplist.access(); + for (size_t elem_i = 0; elem_i < elems_per_thread; ++elem_i) { + accessor.insert(elem_i, elem_i); + } + }, + thread_i * elems_per_thread, + thread_i * elems_per_thread + elems_per_thread); } // wait all threads for (auto &thread : threads) { - thread.join(); + thread.join(); } // get skiplist size { - auto accessor = skiplist.access(); - permanent_assert(accessor.size() == THREADS_NO * elems_per_thread, - "all elements in skiplist"); + auto accessor = skiplist.access(); + permanent_assert(accessor.size() == THREADS_NO * elems_per_thread, + "all elements in skiplist"); } for (size_t thread_i = 0; thread_i < THREADS_NO; ++thread_i) { threads[thread_i] = std::thread( [&skiplist](size_t start, size_t end) { - auto accessor = skiplist.access(); - for (size_t elem_i = start; elem_i < end; ++elem_i) { - permanent_assert(accessor.remove(elem_i) == true, ""); - } - }, - thread_i * elems_per_thread, - thread_i * elems_per_thread + elems_per_thread); + auto accessor = skiplist.access(); + for (size_t elem_i = 0; elem_i < elems_per_thread; ++elem_i) { + permanent_assert(accessor.remove(elem_i) == true, ""); + } + }, + thread_i * elems_per_thread, + thread_i * elems_per_thread + elems_per_thread); } // wait all threads for (auto &thread : threads) { - thread.join(); + thread.join(); } // check size { - auto accessor = skiplist.access(); - permanent_assert(accessor.size() == 0, "Size should be 0, but size is " << accessor.size()); + auto accessor = skiplist.access(); + permanent_assert(accessor.size() == 0, "Size should be 0, but size is " + << accessor.size()); } // check count { - size_t iterator_counter = 0; - auto accessor = skiplist.access(); - for (auto elem : accessor) { - ++iterator_counter; - cout << elem.first << " "; - } - permanent_assert(iterator_counter == 0, "deleted elements"); + size_t iterator_counter = 0; + auto accessor = skiplist.access(); + for (auto elem : accessor) { + ++iterator_counter; + cout << elem.first << " "; + } + permanent_assert(iterator_counter == 0, "deleted elements"); } - - std::this_thread::sleep_for(1s); - - // TODO: test GC and memory - - return 0; + }); } diff --git a/tests/concurrent/sl_insert.cpp b/tests/concurrent/sl_insert.cpp new file mode 100644 index 000000000..ba3497567 --- /dev/null +++ b/tests/concurrent/sl_insert.cpp @@ -0,0 +1,36 @@ +#include "common.h" + +#define THREADS_NO 8 + +constexpr size_t elems_per_thread = 100000; +constexpr size_t key_range = elems_per_thread * THREADS_NO * 2; + +// This test checks insert_unique method under pressure. +// Test checks for missing data and changed/overwriten data. +int main() { + memory_check(THREADS_NO, [] { + skiplist_t skiplist; + + auto futures = run>( + THREADS_NO, skiplist, [](auto acc, auto index) { + auto rand = rand_gen(key_range); + size_t downcount = elems_per_thread; + std::vector owned; + auto inserter = insert_try(acc, downcount, owned); + + do { + inserter(rand(), index); + } while (downcount > 0); + + check_present_same(acc, index, owned); + return owned; + }); + + auto accessor = skiplist.access(); + for (auto &owned : collect(futures)) { + check_present_same(accessor, owned); + } + + check_size(accessor, THREADS_NO * elems_per_thread); + }); +} diff --git a/tests/concurrent/sl_insert_competetive.cpp b/tests/concurrent/sl_insert_competetive.cpp new file mode 100644 index 000000000..f884f1212 --- /dev/null +++ b/tests/concurrent/sl_insert_competetive.cpp @@ -0,0 +1,37 @@ +#include "common.h" + +#define THREADS_NO 8 +constexpr size_t elems_per_thread = 100000; +constexpr size_t key_range = elems_per_thread * THREADS_NO * 2; + +// This test checks insert_unique method under pressure. +// Threads will try to insert keys in the same order. +// This will force threads to compete intensly with each other. +// Test checks for missing data and changed/overwriten data. +int main() { + memory_check(THREADS_NO, [] { + skiplist_t skiplist; + + auto futures = run>( + THREADS_NO, skiplist, [](auto acc, auto index) { + auto rand = rand_gen(key_range); + size_t downcount = elems_per_thread; + std::vector owned; + auto inserter = insert_try(acc, downcount, owned); + + for (int i = 0; downcount > 0; i++) { + inserter(i, index); + } + + check_present_same(acc, index, owned); + return owned; + }); + + auto accessor = skiplist.access(); + for (auto &owned : collect(futures)) { + check_present_same(accessor, owned); + } + + check_size(accessor, THREADS_NO * elems_per_thread); + }); +} diff --git a/tests/concurrent/sl_memory.cpp b/tests/concurrent/sl_memory.cpp new file mode 100644 index 000000000..02521b652 --- /dev/null +++ b/tests/concurrent/sl_memory.cpp @@ -0,0 +1,21 @@ +#include "common.h" + +#define THREADS_NO 8 + +constexpr size_t elements = 2e6; + +// Test for simple memory leaks +int main() { + memory_check(THREADS_NO, [] { + skiplist_t skiplist; + + auto futures = run(THREADS_NO, skiplist, [](auto acc, auto index) { + for (size_t i = 0; i < elements; i++) { + acc.insert(i, index); + } + return index; + }); + collect(futures); + check_size(skiplist.access(), elements); + }); +} diff --git a/tests/concurrent/sl_remove_competetive.cpp b/tests/concurrent/sl_remove_competetive.cpp new file mode 100644 index 000000000..d0bfbab56 --- /dev/null +++ b/tests/concurrent/sl_remove_competetive.cpp @@ -0,0 +1,62 @@ +#include "common.h" + +#define THREADS_NO 8 +constexpr size_t op_per_thread = 1e5; +// Depending on value there is a possiblity of numerical overflow +constexpr size_t max_number = 10; +constexpr size_t no_insert_for_one_delete = 2; + +// This test checks remove method under pressure. +// Threads will try to insert and remove keys aproximetly in the same order. +// This will force threads to compete intensly with each other. +// Calls of remove method are interleaved with insert calls. +int main() { + memory_check(THREADS_NO, [] { + skiplist_t skiplist; + + auto futures = run>( + THREADS_NO, skiplist, [](auto acc, auto index) { + auto rand_op = rand_gen_bool(no_insert_for_one_delete); + size_t downcount = op_per_thread; + long long sum = 0; + long long count = 0; + + for (int i = 0; downcount > 0; i++) { + auto data = i % max_number; + if (rand_op()) { + auto t = i; + while (t > 0) { + if (acc.remove(t)) { + sum -= t % max_number; + downcount--; + count--; + break; + } + t--; + } + } else { + if (acc.insert(i, data).second) { + sum += data; + count++; + downcount--; + } + } + } + return std::pair(sum, count); + }); + + auto accessor = skiplist.access(); + long long sums = 0; + long long counters = 0; + for (auto &data : collect(futures)) { + sums += data.second.first; + counters += data.second.second; + } + + for (auto &e : accessor) { + sums -= e.second; + } + permanent_assert(sums == 0, "Aproximetly Same values are present"); + check_size(accessor, counters); + }); +} diff --git a/tests/concurrent/sl_remove_disjoint.cpp b/tests/concurrent/sl_remove_disjoint.cpp new file mode 100644 index 000000000..34b47321e --- /dev/null +++ b/tests/concurrent/sl_remove_disjoint.cpp @@ -0,0 +1,46 @@ +#include "common.h" + +#define THREADS_NO 8 +constexpr size_t key_range = 1e5; +constexpr size_t op_per_thread = 1e6; +constexpr size_t no_insert_for_one_delete = 1; + +// This test checks remove method under pressure. +// Each thread removes it's own data. So removes are disjoint. +// Calls of remove method are interleaved with insert calls. +int main() { + memory_check(THREADS_NO, [] { + skiplist_t skiplist; + + auto futures = run>( + THREADS_NO, skiplist, [](auto acc, auto index) { + auto rand = rand_gen(key_range); + auto rand_op = rand_gen_bool(no_insert_for_one_delete); + size_t downcount = op_per_thread; + std::vector owned; + auto inserter = insert_try(acc, downcount, owned); + + do { + if (owned.size() != 0 && rand_op()) { + auto rem = rand() % owned.size(); + permanent_assert(acc.remove(owned[rem]), "Owned data removed"); + owned.erase(owned.begin() + rem); + downcount--; + } else { + inserter(rand(), index); + } + } while (downcount > 0); + + check_present_same(acc, index, owned); + return owned; + }); + + auto accessor = skiplist.access(); + size_t count = 0; + for (auto &owned : collect(futures)) { + check_present_same(accessor, owned); + count += owned.second.size(); + } + check_size(accessor, count); + }); +} diff --git a/tests/concurrent/sl_remove_joint.cpp b/tests/concurrent/sl_remove_joint.cpp new file mode 100644 index 000000000..6a7d7fd9e --- /dev/null +++ b/tests/concurrent/sl_remove_joint.cpp @@ -0,0 +1,60 @@ +#include "common.h" + +#define THREADS_NO 8 +constexpr size_t key_range = 1e5; +constexpr size_t op_per_thread = 1e5; +// Depending on value there is a possiblity of numerical overflow +constexpr size_t max_number = 10; +constexpr size_t no_insert_for_one_delete = 2; + +// This test checks remove method under pressure. +// Each thread removes random data. So removes are joint. +// Calls of remove method are interleaved with insert calls. +int main() { + memory_check(THREADS_NO, [] { + skiplist_t skiplist; + + auto futures = run>( + THREADS_NO, skiplist, [](auto acc, auto index) { + auto rand = rand_gen(key_range); + auto rand_op = rand_gen_bool(no_insert_for_one_delete); + size_t downcount = op_per_thread; + long long sum = 0; + long long count = 0; + + do { + auto num = rand(); + auto data = num % max_number; + if (rand_op()) { + if (acc.remove(num)) { + sum -= data; + downcount--; + count--; + } + } else { + if (acc.insert(num, data).second) { + sum += data; + downcount--; + count++; + } + } + } while (downcount > 0); + + return std::pair(sum, count); + }); + + auto accessor = skiplist.access(); + long long sums = 0; + long long counters = 0; + for (auto &data : collect(futures)) { + sums += data.second.first; + counters += data.second.second; + } + + for (auto &e : accessor) { + sums -= e.second; + } + permanent_assert(sums == 0, "Aproximetly Same values are present"); + check_size(accessor, counters); + }); +} diff --git a/tests/concurrent/sl_simulation.cpp b/tests/concurrent/sl_simulation.cpp new file mode 100644 index 000000000..86e467df8 --- /dev/null +++ b/tests/concurrent/sl_simulation.cpp @@ -0,0 +1,66 @@ +#include "common.h" + +#define THREADS_NO 8 +constexpr size_t key_range = 1e5; +constexpr size_t op_per_thread = 1e6; +// Depending on value there is a possiblity of numerical overflow +constexpr size_t max_number = 10; +constexpr size_t no_find_per_change = 5; +constexpr size_t no_insert_for_one_delete = 1; + +// This test simulates behavior of transactions. +// Each thread makes a series of finds interleaved with method which change. +// Exact ratio of finds per change and insert per delete can be regulated with +// no_find_per_change and no_insert_for_one_delete. +int main() { + memory_check(THREADS_NO, [] { + skiplist_t skiplist; + + auto futures = run>( + THREADS_NO, skiplist, [](auto acc, auto index) { + auto rand = rand_gen(key_range); + auto rand_change = rand_gen_bool(no_find_per_change); + auto rand_delete = rand_gen_bool(no_insert_for_one_delete); + long long sum = 0; + long long count = 0; + + for (int i = 0; i < op_per_thread; i++) { + auto num = rand(); + auto data = num % max_number; + if (rand_change()) { + if (rand_delete()) { + if (acc.remove(num)) { + sum -= data; + count--; + } + } else { + if (acc.insert(num, data).second) { + sum += data; + count++; + } + } + } else { + auto value = acc.find(num); + permanent_assert(value == acc.end() || value->second == data, + "Data is invalid"); + } + } + + return std::pair(sum, count); + }); + + auto accessor = skiplist.access(); + long long sums = 0; + long long counters = 0; + for (auto &data : collect(futures)) { + sums += data.second.first; + counters += data.second.second; + } + + for (auto &e : accessor) { + sums -= e.second; + } + permanent_assert(sums == 0, "Same values aren't present"); + check_size(accessor, counters); + }); +} diff --git a/tests/unit/concurrent_map.cpp b/tests/unit/concurrent_map.cpp index 5667fccb0..b9e008332 100644 --- a/tests/unit/concurrent_map.cpp +++ b/tests/unit/concurrent_map.cpp @@ -8,62 +8,59 @@ using std::endl; using skiplist_t = ConcurrentMap; -void print_skiplist(const skiplist_t::Accessor &skiplist) -{ - cout << "---- skiplist now has: "; +void print_skiplist(const skiplist_t::Accessor &skiplist) { + cout << "---- skiplist now has: "; - for (auto &kv : skiplist) - cout << "(" << kv.first << ", " << kv.second << ") "; + for (auto &kv : skiplist) + cout << "(" << kv.first << ", " << kv.second << ") "; - cout << "----" << endl; + cout << "----" << endl; } -int main(void) -{ - skiplist_t skiplist; - auto accessor = skiplist.access(); +int main(void) { + skiplist_t skiplist; + auto accessor = skiplist.access(); - // insert 10 - permanent_assert(accessor.insert_unique(1, 10).second == true, - "add first element"); + // insert 10 + permanent_assert(accessor.insert(1, 10).second == true, "add first element"); - // try insert 10 again (should fail) - permanent_assert(accessor.insert_unique(1, 10).second == false, - "add the same element, should fail"); + // try insert 10 again (should fail) + permanent_assert(accessor.insert(1, 10).second == false, + "add the same element, should fail"); - // insert 20 - permanent_assert(accessor.insert_unique(2, 20).second == true, - "insert new unique element"); + // insert 20 + permanent_assert(accessor.insert(2, 20).second == true, + "insert new unique element"); - print_skiplist(accessor); + print_skiplist(accessor); - // value at key 3 shouldn't exist - permanent_assert((accessor.find(3) == accessor.end()) == true, - "try to find element which doesn't exist"); + // value at key 3 shouldn't exist + permanent_assert((accessor.find(3) == accessor.end()) == true, + "try to find element which doesn't exist"); - // value at key 2 should exist - permanent_assert((accessor.find(2) != accessor.end()) == true, - "find iterator"); + // value at key 2 should exist + permanent_assert((accessor.find(2) != accessor.end()) == true, + "find iterator"); - // at key 2 is 20 (true) - permanent_assert(accessor.find(2)->second == 20, "find element"); + // at key 2 is 20 (true) + permanent_assert(accessor.find(2)->second == 20, "find element"); - // removed existing (1) - permanent_assert(accessor.remove(1) == true, "try to remove element"); + // removed existing (1) + permanent_assert(accessor.remove(1) == true, "try to remove element"); - // removed non-existing (3) - permanent_assert(accessor.remove(3) == false, - "try to remove element which doesn't exist"); + // removed non-existing (3) + permanent_assert(accessor.remove(3) == false, + "try to remove element which doesn't exist"); - // insert (1, 10) - permanent_assert(accessor.insert_unique(1, 10).second == true, - "insert unique element"); + // insert (1, 10) + permanent_assert(accessor.insert(1, 10).second == true, + "insert unique element"); - // insert (4, 40) - permanent_assert(accessor.insert_unique(4, 40).second == true, - "insert unique element"); + // insert (4, 40) + permanent_assert(accessor.insert(4, 40).second == true, + "insert unique element"); - print_skiplist(accessor); + print_skiplist(accessor); - return 0; + return 0; }