This commit is contained in:
Marko Budiselic 2016-08-02 17:05:15 +01:00
commit b5f884d428
38 changed files with 2223 additions and 1452 deletions

89
.clang-format Normal file
View File

@ -0,0 +1,89 @@
---
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: 4
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
...

1
.gitignore vendored
View File

@ -13,3 +13,4 @@ memgraph
*.session.yaml
tags
.gdb_history
Testing/

View File

@ -0,0 +1 @@
---

View File

@ -0,0 +1,3 @@
Start testing: Jul 28 19:05 BST
----------------------------------------------------------
End testing: Jul 28 19:05 BST

View File

@ -0,0 +1,94 @@
#pragma once
#include "data_structures/concurrent/skiplist.hpp"
#include "utils/total_ordering.hpp"
using std::pair;
template <typename K, typename T>
class Item : public TotalOrdering<Item<K, T>>,
public TotalOrdering<K, Item<K, T>>,
public TotalOrdering<Item<K, T>, K>,
public pair<const K, T>
{
public:
using pair<const K, T>::pair;
friend constexpr bool operator<(const Item &lhs, const Item &rhs)
{
std::pair<const K, T> *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;
}
};
template <typename T>
class AccessorBase
{
typedef SkipList<T> list;
typedef typename SkipList<T>::Iterator list_it;
typedef typename SkipList<T>::ConstIterator list_it_con;
protected:
AccessorBase(list *skiplist) : accessor(skiplist->access()) {}
public:
AccessorBase(const AccessorBase &) = delete;
AccessorBase(AccessorBase &&other) : accessor(std::move(other.accessor)) {}
~AccessorBase() {}
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(); }
template <class K>
typename SkipList<T>::template MultiIterator<K> end(const K &data)
{
return accessor.template mend<K>(data);
}
template <class K>
typename SkipList<T>::template MultiIterator<K> mend(const K &data)
{
return accessor.template mend<K>(data);
}
size_t size() const { return accessor.size(); }
protected:
typename list::Accessor accessor;
};

View File

@ -1,9 +1,60 @@
#pragma once
#include "data_structures/skiplist/skiplist_map.hpp"
#include "data_structures/concurrent/common.hpp"
#include "data_structures/concurrent/skiplist.hpp"
template<class K, class T>
class ConcurrentMap : public SkipListMap<K, T>
{
// TODO implement with SkipList
using std::pair;
template <typename K, typename T>
class ConcurrentMap
{
typedef Item<K, T> item_t;
typedef SkipList<item_t> list;
typedef typename SkipList<item_t>::Iterator list_it;
typedef typename SkipList<item_t>::ConstIterator list_it_con;
public:
ConcurrentMap() {}
class Accessor : public AccessorBase<item_t>
{
friend class ConcurrentMap;
using AccessorBase<item_t>::AccessorBase;
private:
using AccessorBase<item_t>::accessor;
public:
std::pair<list_it, bool> insert(const K &key, const T &data)
{
return accessor.insert(item_t(key, data));
}
std::pair<list_it, bool> insert(const K &key, T &&data)
{
return accessor.insert(item_t(key, std::forward<T>(data)));
}
std::pair<list_it, bool> insert(K &&key, T &&data)
{
return accessor.insert(
item_t(std::forward<K>(key), std::forward<T>(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); }
};
Accessor access() { return Accessor(&skiplist); }
const Accessor access() const { return Accessor(&skiplist); }
private:
list skiplist;
};

View File

@ -0,0 +1,70 @@
#pragma once
#include "data_structures/concurrent/skiplist.hpp"
#include "utils/total_ordering.hpp"
using std::pair;
template <typename K, typename T>
class ConcurrentMultiMap
{
typedef Item<K, T> item_t;
typedef SkipList<item_t> list;
typedef typename SkipList<item_t>::Iterator list_it;
typedef typename SkipList<item_t>::ConstIterator list_it_con;
typedef typename SkipList<item_t>::template MultiIterator<K> list_it_multi;
public:
ConcurrentMultiMap() {}
class Accessor : public AccessorBase<item_t>
{
friend class ConcurrentMultiMap<K, T>;
using AccessorBase<item_t>::AccessorBase;
private:
using AccessorBase<item_t>::accessor;
public:
list_it insert(const K &key, const T &data)
{
return accessor.insert_non_unique(item_t(key, data));
}
list_it insert(const K &key, T &&data)
{
return accessor.insert_non_unique(
item_t(key, std::forward<T>(data)));
}
list_it insert(K &&key, T &&data)
{
return accessor.insert_non_unique(
item_t(std::forward<K>(key), std::forward<T>(data)));
}
list_it_multi find_multi(const K &key)
{
return accessor.find_multi(key);
}
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); }
};
Accessor access() { return Accessor(&skiplist); }
const Accessor access() const { return Accessor(&skiplist); }
private:
list skiplist;
};

View File

@ -0,0 +1,50 @@
#pragma once
#include "data_structures/concurrent/skiplist.hpp"
template <class T>
class ConcurrentMultiSet
{
typedef SkipList<T> list;
typedef typename SkipList<T>::Iterator list_it;
typedef typename SkipList<T>::ConstIterator list_it_con;
public:
ConcurrentMultiSet() {}
class Accessor : public AccessorBase<T>
{
friend class ConcurrentMultiSet;
using AccessorBase<T>::AccessorBase;
private:
using AccessorBase<T>::accessor;
public:
list_it insert(const T &item) { return accessor.insert_non_unique(item); }
list_it insert(T &&item)
{
return accessor.insert_non_unique(std::forward<T>(item));
}
list_it_con find(const T &item) const { return accessor.find(item); }
list_it find(const T &item) { return accessor.find(item); }
bool contains(const T &item) const
{
return this->find(item) != this->end();
}
bool remove(const T &item) { return accessor.remove(item); }
};
Accessor access() { return Accessor(&skiplist); }
const Accessor access() const { return Accessor(&skiplist); }
private:
list skiplist;
};

View File

@ -1,8 +1,54 @@
#pragma once
#include "data_structures/concurrent/common.hpp"
#include "data_structures/concurrent/skiplist.hpp"
template<class T>
class ConcurrentSet : public SkipList<T>
template <class T>
class ConcurrentSet
{
typedef SkipList<T> list;
typedef typename SkipList<T>::Iterator list_it;
typedef typename SkipList<T>::ConstIterator list_it_con;
public:
ConcurrentSet() {}
class Accessor : public AccessorBase<T>
{
friend class ConcurrentSet;
using AccessorBase<T>::AccessorBase;
private:
using AccessorBase<T>::accessor;
public:
std::pair<list_it, bool> insert(const T &item)
{
return accessor.insert(item);
}
std::pair<list_it, bool> insert(T &&item)
{
return accessor.insert(std::forward<T>(item));
}
list_it_con find(const T &item) const { return accessor.find(item); }
list_it find(const T &item) { return accessor.find(item); }
bool contains(const T &item) const
{
return this->find(item) != this->end();
}
bool remove(const T &item) { return accessor.remove(item); }
};
Accessor access() { return Accessor(&skiplist); }
const Accessor access() const { return Accessor(&skiplist); }
private:
list skiplist;
};

File diff suppressed because it is too large Load Diff

View File

@ -25,6 +25,7 @@ public:
// take freelist if there is no more threads
{
auto lock = this->acquire_unique();
assert(this->count > 0);
--this->count;
if (this->count == 0) {
freelist.swap(local_freelist);
@ -32,23 +33,24 @@ public:
}
if (local_freelist.size() > 0) {
std::cout << "GC started" << std::endl;
std::cout << "Local skiplist size: " <<
local_freelist.size() << std::endl;
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) {
counter++;
if (element->flags.is_marked()) T::destroy(element);
if (element->flags.is_marked()) {
T::destroy(element);
counter++;
}
}
std::cout << "Number of destroyed elements " << counter << std::endl;
std::cout << "Number of destroyed elements " << counter
<< std::endl;
}
}
void collect(T *node)
{
freelist.add(node);
}
void collect(T *node) { freelist.add(node); }
private:
FreeList<T> freelist;

View File

@ -1,709 +0,0 @@
#pragma once
#include <algorithm>
#include <memory>
#include <cassert>
#include "threading/sync/lockable.hpp"
#include "threading/sync/spinlock.hpp"
#include "utils/random/fast_binomial.hpp"
#include "utils/placeholder.hpp"
#include "skiplist_gc.hpp"
/* @brief Concurrent lock-based skiplist with fine grained locking
*
* From Wikipedia:
* "A skip list is a data structure that allows fast search within an
* ordered sequence of elements. Fast search is made possible by
* maintaining a linked hierarchy of subsequences, each skipping over
* fewer elements. Searching starts in the sparsest subsequence until
* two consecutive elements have been found, one smaller and one
* larger than or equal to the element searched for."
*
* [_]---------------->[+]----------->[_]
* [_]->[+]----------->[+]------>[+]->[_]
* [_]->[+]------>[+]->[+]------>[+]->[_]
* [_]->[+]->[+]->[+]->[+]->[+]->[+]->[_]
* head 1 2 4 5 8 9 nil
*
* The logarithmic properties are maintained by randomizing the height for
* every new node using the binomial distribution
* p(k) = (1/2)^k for k in [1...H].
*
* The implementation is based on the work described in the paper
* "A Provably Correct Scalable Concurrent Skip List"
* URL: https://www.cs.tau.ac.il/~shanir/nir-pubs-web/Papers/OPODIS2006-BA.pdf
*
* The proposed implementation is in Java so the authors don't worry about
* garbage collection, but obviously we have to. This implementation uses
* lazy garbage collection. When all clients stop using the skiplist, we can
* be sure that all logically removed nodes are not visible to anyone so
* we can safely remove them. The idea of counting active clients implies
* the use of a intermediary structure (called Accessor) when accessing the
* skiplist.
*
* The implementation has an interface which closely resembles the functions
* with arguments and returned types frequently used by the STL.
*
* Example usage:
* Skiplist<K, T> skiplist;
*
* {
* auto accessor = skiplist.access();
*
* // inserts <key1, value1> into the skiplist and returns
* // <iterator, bool> pair. iterator points to the newly created
* // node and the boolean member evaluates to true denoting that the
* // insertion was successful
* accessor.insert_unique(key1, value1);
*
* // nothing gets inserted because key1 already exist in the skiplist
* // returned iterator points to the existing element and the return
* // boolean evaluates to false denoting the failed insertion
* accessor.insert_unique(key1, value1);
*
* // returns an iterator to the element pair <key1, value1>
* auto it = accessor.find(key1);
*
* // returns an empty iterator. it == accessor.end()
* auto it = accessor.find(key2);
*
* // iterate over all key value pairs
* for(auto it = accessor.begin(); it != accessor.end(); ++it)
* cout << it->first << " " << it->second;
*
* // range based for loops also work
* for(auto& e : accessor)
* cout << e.first << " " << e.second;
*
* accessor.remove(key1); // returns true
* accessor.remove(key1); // returns false because key1 doesn't exist
* }
*
* // accessor out of scope, garbage collection might occur
*
* For detailed operations available, please refer to the Accessor class
* inside the public section of the SkipList class.
*
* @tparam K Type to use as the key
* @tparam T Type to use as the value
* @tparam H Maximum node height. Determines the effective number of nodes
* the skiplist can hold in order to preserve it's log2 properties
* @tparam lock_t Lock type used when locking is needed during the creation
* and deletion of nodes.
*/
template <class K, class T, size_t H=32, class lock_t=SpinLock>
class SkipListMap : private Lockable<lock_t>
{
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<H> rnd;
using value_type = std::pair<const K, T>;
/* @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(std::memory_order_acquire) & MARKED;
}
void set_marked()
{
flags.fetch_or(MARKED, std::memory_order_release);
}
bool is_fully_linked() const
{
return flags.load(std::memory_order_acquire) & FULLY_LINKED;
}
void set_fully_linked()
{
flags.fetch_or(FULLY_LINKED, std::memory_order_release);
}
private:
std::atomic<uint8_t> flags {0};
};
class Node : Lockable<lock_t>
{
public:
friend class SkipListMap;
const uint8_t height;
Flags flags;
const K& key() const
{
return kv_pair().first;
}
T& value()
{
return kv_pair().second;
}
const T& value() const
{
return kv_pair().second;
}
value_type& kv_pair()
{
return data.get();
}
const value_type& kv_pair() const
{
return data.get();
}
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);
}
static Node* create(const K& key, const T& item, uint8_t height)
{
return create({key, item}, height);
}
static Node* create(const K& key, T&& item, uint8_t height)
{
return create({key, std::forward<T>(item)}, height);
}
static Node* create(value_type&& data, 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<value_type>(data), height);
}
static void destroy(Node* node)
{
node->~Node();
free(node);
}
Node* forward(size_t level) const
{
return tower[level].load(std::memory_order_acquire);
}
void forward(size_t level, Node* next)
{
tower[level].store(next, std::memory_order_release);
}
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<Node*> {nullptr};
}
Node(value_type&& data, uint8_t height) : Node(height)
{
this->data.set(std::forward<value_type>(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<Node*>);
auto node = static_cast<Node*>(std::malloc(size));
return node;
}
Placeholder<value_type> 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<Node*> tower[0];
};
public:
template <class It>
class IteratorBase : public Crtp<It>
{
protected:
IteratorBase(Node* node) : node(node) {}
Node* node {nullptr};
public:
IteratorBase() = default;
IteratorBase(const IteratorBase&) = default;
value_type& operator*()
{
assert(node != nullptr);
return node->kv_pair();
}
value_type* operator->()
{
assert(node != nullptr);
return &node->kv_pair();
}
operator value_type&()
{
assert(node != nullptr);
return node->kv_pair();
}
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<ConstIterator>
{
friend class SkipListMap;
ConstIterator(Node* node) : IteratorBase<ConstIterator>(node) {}
public:
ConstIterator() = default;
ConstIterator(const ConstIterator&) = default;
const value_type& operator*()
{
return IteratorBase<ConstIterator>::operator*();
}
const value_type* operator->()
{
return IteratorBase<ConstIterator>::operator->();
}
operator const value_type&()
{
return IteratorBase<ConstIterator>::operator value_type&();
}
};
class Iterator : public IteratorBase<Iterator>
{
friend class SkipListMap;
Iterator(Node* node) : IteratorBase<Iterator>(node) {}
public:
Iterator() = default;
Iterator(const Iterator&) = default;
};
SkipListMap() : header(Node::sentinel(H)) {}
friend class Accessor;
class Accessor
{
friend class SkipListMap;
Accessor(SkipListMap* 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<Iterator, bool> insert_unique(const K& key, const T& item)
{
return skiplist->insert({key, item}, preds, succs);
}
std::pair<Iterator, bool> insert_unique(const K& key, T&& item)
{
return skiplist->insert({key, std::forward<T>(item)}, preds, succs);
}
ConstIterator find(const K& key) const
{
return static_cast<const SkipListMap&>(*skiplist).find(key);
}
Iterator find(const K& key)
{
return skiplist->find(key);
}
bool contains(const K& key) const
{
return skiplist->contains(key);
}
bool remove(const K& key)
{
return skiplist->remove(key, preds, succs);
}
size_t size() const
{
return skiplist->size();
}
private:
SkipListMap* 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<lock_t>;
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();
}
bool greater(const K& key, const Node* const node)
{
return node && key > node->key();
}
bool less(const K& key, const Node* const node)
{
return (node == nullptr) || key < node->key();
}
ConstIterator find(const K& key) const
{
return const_cast<SkipListMap*>(this)->find_node<ConstIterator>(key);
}
Iterator find(const K& key)
{
return find_node<Iterator>(key);
}
bool contains(const K& key)
{
return this->find(key) != this->end();
}
template <class It>
It find_node(const K& key)
{
Node* node, *pred = header;
int h = static_cast<int>(pred->height) - 1;
while(true)
{
// try to descend down first the next key on this layer overshoots
for(; h >= 0 && less(key, 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(key, node))
pred = node, node = node->forward(h);
// check if we have a hit. if not, we need to descend down again
if(!less(key, node) && !node->flags.is_marked())
return It(node);
}
}
int find_path(Node* from, int start, const K& key,
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(key, node))
pred = node, node = pred->forward(level);
if(level_found == -1 && !less(key, node))
level_found = level;
preds[level] = pred;
succs[level] = node;
}
return level_found;
}
template <bool ADDING>
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<Iterator, bool>
insert(value_type&& data, Node* preds[], Node* succs[])
{
while(true)
{
auto level = find_path(header, H - 1, data.first, preds, succs);
if(level != -1)
{
auto found = succs[level];
if(found->flags.is_marked())
continue;
while(!found->flags.is_fully_linked())
usleep(250);
return {Iterator {succs[level]}, false};
}
auto height = rnd();
guard_t guards[H];
// try to acquire the locks for predecessors up to the height of
// the new node. release the locks and try again if someone else
// has the locks
if(!lock_nodes<true>(height, guards, preds, succs))
continue;
// you have the locks, create a new node
auto new_node = Node::create(std::forward<value_type>(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 K& key, Node* preds[], Node* succs[])
{
Node* node = nullptr;
guard_t node_guard;
bool marked = false;
int height = 0;
while(true)
{
auto level = find_path(header, H - 1, key, preds, succs);
if(!marked && (level == -1 || !ok_delete(succs[level], level)))
return false;
if(!marked)
{
node = succs[level];
height = node->height;
node_guard = node->acquire_unique();
if(node->flags.is_marked())
return false;
node->flags.set_marked();
marked = true;
}
guard_t guards[H];
if(!lock_nodes<false>(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<size_t> count {0};
Node* header;
SkiplistGC<Node> gc;
};
template <class K, class T, size_t H, class lock_t>
thread_local FastBinomial<H> SkipListMap<K, T, H, lock_t>::rnd;

View File

@ -1,234 +0,0 @@
#pragma once
#include <cassert>
#include "data_structures/skiplist/skiplist_map.hpp"
#include "utils/total_ordering.hpp"
template <class T, size_t H=32, class lock_t=SpinLock>
class SkipListSet
{
class Key : public TotalOrdering<Key>
{
public:
Key() = default;
Key(const T* item) : item(item) {}
Key(const T& item) : item(&item) {}
friend constexpr bool operator<(const Key& lhs, const Key& rhs)
{
assert(!lhs.empty());
assert(!rhs.empty());
return *lhs.item < *rhs.item;
}
friend constexpr bool operator==(const Key& lhs, const Key& rhs)
{
assert(!lhs.empty());
assert(!rhs.empty());
return *lhs.item == *rhs.item;
}
bool empty() const
{
return item == nullptr;
}
operator const T&() const
{
assert(item != nullptr);
return *item;
}
private:
const T* item {nullptr};
};
using skiplist_t = SkipListMap<Key, T, H, lock_t>;
using iter_t = typename skiplist_t::Iterator;
using const_iter_t = typename skiplist_t::ConstIterator;
public:
template <class Derived, class It>
class IteratorBase : public Crtp<Derived>
{
protected:
IteratorBase(const It& it) : it(it) {}
IteratorBase(It&& it) : it(std::move(it)) {}
It it;
public:
IteratorBase() = default;
IteratorBase(const IteratorBase&) = default;
auto& operator*()
{
return it->second;
}
auto* operator->()
{
return &it->second;
}
operator T&()
{
return it->second;
}
Derived& operator++()
{
it++;
return this->derived();
}
Derived& operator++(int)
{
return operator++();
}
friend bool operator==(const Derived& a, const Derived& b)
{
return a.it == b.it;
}
friend bool operator!=(const Derived& a, const Derived& b)
{
return !(a == b);
}
};
class ConstIterator : public IteratorBase<ConstIterator, const_iter_t>
{
friend class SkipListSet;
using Base = IteratorBase<ConstIterator, const_iter_t>;
ConstIterator(const const_iter_t& it) : Base(it) {}
ConstIterator(const_iter_t&& it) : Base(std::move(it)) {}
public:
ConstIterator() = default;
ConstIterator(const ConstIterator&) = default;
const T& operator*()
{
return Base::operator*();
}
const T* operator->()
{
return Base::operator->();
}
operator const T&()
{
return Base::operator T&();
}
};
class Iterator : public IteratorBase<Iterator, iter_t>
{
friend class SkipListSet;
using Base = IteratorBase<Iterator, iter_t>;
Iterator(const iter_t& it) : Base(it) {}
Iterator(iter_t&& it) : Base(std::move(it)) {}
public:
Iterator() = default;
Iterator(const Iterator&) = default;
};
SkipListSet() = default;
friend class Accessor;
class Accessor
{
friend class SkipListSet;
using accessor_t = typename skiplist_t::Accessor;
Accessor(accessor_t&& accessor) : accessor(std::move(accessor)) {}
public:
Accessor(const Accessor&) = delete;
Accessor(Accessor&&) = default;
Iterator begin()
{
return Iterator(accessor.begin());
}
ConstIterator begin() const
{
return ConstIterator(accessor.cbegin());
}
ConstIterator cbegin() const
{
return ConstIterator(accessor.cbegin());
}
Iterator end()
{
return Iterator(accessor.end());
}
ConstIterator end() const
{
return ConstIterator(accessor.cend());
}
ConstIterator cend() const
{
return ConstIterator(accessor.cend());
}
std::pair<Iterator, bool> insert(const T& item)
{
auto r = accessor.insert_unique(Key(item), item);
return { Iterator(r.first), r.second };
}
ConstIterator find(const T& item) const
{
return ConstIterator(accessor.find(Key(item)));
}
Iterator find(const T& item)
{
return Iterator(accessor.find(Key(item)));
}
bool contains(const T& item) const
{
return accessor.find(Key(item)) != accessor.end();
}
bool remove(const T& item)
{
return accessor.remove(Key(item));
}
private:
accessor_t accessor;
};
Accessor access()
{
return Accessor(std::move(data.access()));
}
Accessor access() const
{
return Accessor(std::move(data.access()));
}
private:
SkipListMap<Key, T> data;
};

View File

@ -1,6 +1,8 @@
#pragma once
// TODO: remove from here and from the project
#include <atomic>
#include <iostream>
#include "threading/sync/lockable.hpp"
#include "utils/crtp.hpp"

View File

@ -1,52 +1,47 @@
#pragma once
#include "common.hpp"
#include "edge_accessor.hpp"
#include "data_structures/concurrent/concurrent_map.hpp"
#include "edge_accessor.hpp"
class Edges
{
public:
Edge::Accessor find(tx::Transaction& t, const Id& id)
{
auto edges_accessor = edges.access();
auto edges_iterator = edges_accessor.find(id);
public:
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<uint64_t, EdgeRecord> edges;
AtomicCounter<uint64_t> counter;
private:
ConcurrentMap<uint64_t, EdgeRecord> edges;
AtomicCounter<uint64_t> counter;
};

View File

@ -10,36 +10,35 @@
template <class Key, class Item>
class Index
{
public:
using container_t = ConcurrentMap<Key, Item>;
public:
using container_t = ConcurrentMap<Key, Item>;
Index() : index(std::make_unique<container_t>()) {}
Index() : index(std::make_unique<container_t>()) {}
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<VertexIndexRecord>(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<VertexIndexRecord>(index_record));
}
private:
std::unique_ptr<container_t> index;
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<container_t> index;
};

View File

@ -5,8 +5,7 @@ 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);
@ -27,8 +26,7 @@ Vertex::Accessor Vertices::insert(tx::Transaction &t)
// 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));
auto result = vertices_accessor.insert(next, std::move(vertex_record));
// create new vertex
auto inserted_vertex_record = result.first;
@ -38,13 +36,12 @@ Vertex::Accessor Vertices::insert(tx::Transaction &t)
}
void Vertices::update_label_index(const Label &label,
VertexIndexRecord &&index_record)
VertexIndexRecord &&index_record)
{
label_index.update(label,
std::forward<VertexIndexRecord>(index_record));
label_index.update(label, std::forward<VertexIndexRecord>(index_record));
}
VertexIndexRecordCollection& Vertices::find_label_index(const Label& label)
VertexIndexRecordCollection &Vertices::find_label_index(const Label &label)
{
return label_index.find(label);
}

View File

@ -8,18 +8,14 @@
class SpinLock
{
public:
void lock()
{
while(lock_flag.test_and_set(std::memory_order_acquire))
{ // Before was memorz_order_acquire
while (lock_flag.test_and_set(std::memory_order_seq_cst))
cpu_relax();
///usleep(250);
}
void unlock()
{
lock_flag.clear(std::memory_order_release);
/// usleep(250);
}
// Before was memory_order_release
void unlock() { lock_flag.clear(std::memory_order_seq_cst); }
private:
// guaranteed by standard to be lock free!

View File

@ -1,25 +1,25 @@
#pragma once
template <class Derived>
template <class Derived, class Other = Derived>
struct TotalOrdering
{
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 || 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);
}
};

233
tests/concurrent/common.h Normal file
View File

@ -0,0 +1,233 @@
#include "stdio.h"
#include "stdlib.h"
#include "string.h"
#include <chrono>
#include <future>
#include <iostream>
#include <random>
#include <thread>
#include "data_structures/concurrent/concurrent_map.hpp"
#include "data_structures/concurrent/concurrent_multimap.hpp"
#include "data_structures/concurrent/concurrent_multiset.hpp"
#include "data_structures/concurrent/concurrent_set.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 map_t = ConcurrentMap<int, int>;
using set_t = ConcurrentSet<int>;
using multiset_t = ConcurrentMultiSet<int>;
using multimap_t = ConcurrentMultiMap<int, int>;
using namespace std::chrono_literals;
// Returns uniform random size_t generator from range [0,n>
auto rand_gen(size_t n)
{
std::default_random_engine generator;
std::uniform_int_distribution<size_t> 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; };
}
// Checks for all owned keys if there data is data.
template <typename S>
void check_present_same(typename S::Accessor &acc, size_t data,
std::vector<size_t> &owned)
{
for (auto num : owned) {
permanent_assert(acc.find(num)->second == data,
"My data is present and my");
}
}
// Checks for all owned.second keys if there data is owned.first.
template <typename S>
void check_present_same(typename S::Accessor &acc,
std::pair<size_t, std::vector<size_t>> &owned)
{
check_present_same<S>(acc, owned.first, owned.second);
}
// Checks if reported size and traversed size are equal to given size.
template <typename S>
void check_size(typename S::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 "
<< iterator_counter);
}
// Checks if order in list is maintened. It expects map
template <typename S>
void check_order(typename S::Accessor &acc)
{
if (acc.begin() != acc.end()) {
auto last = acc.begin()->first;
for (auto elem : acc) {
if (!(last <= elem))
std::cout << "Order isn't maintained. Before was: " << last
<< " next is " << elem.first << "\n";
last = elem.first;
}
}
}
void check_zero(size_t key_range, long array[], const char *str)
{
for (int i = 0; i < key_range; i++) {
permanent_assert(array[i] == 0,
str << " doesn't hold it's guarantees. It has "
<< array[i] << " extra elements.");
}
}
// Checks multiIterator and iterator guarantees
void check_multi_iterator(multimap_t::Accessor &accessor, size_t key_range,
long set[])
{
for (int i = 0; i < key_range; i++) {
auto it = accessor.find(i);
auto it_m = accessor.find_multi(i);
permanent_assert(
!(it_m != accessor.end(i) && it == accessor.end()),
"MultiIterator ended before Iterator. Set: " << set[i]);
permanent_assert(
!(it_m == accessor.end(i) && it != accessor.end()),
"Iterator ended before MultiIterator. Set: " << set[i]);
permanent_assert((it_m == accessor.end(i) && it == accessor.end()) ||
it->second == it_m->second,
"MultiIterator didn't found the same "
"first element. Set: "
<< set[i]);
if (set[i] > 0) {
for (int j = 0; j < set[i]; j++) {
permanent_assert(
it->second == it_m->second,
"MultiIterator and iterator aren't on the same "
"element.");
permanent_assert(it_m->first == i,
"MultiIterator is showing illegal data") it++;
it_m++;
}
}
permanent_assert(
it_m == accessor.end(i),
"There is more data than it should be in MultiIterator. "
<< it_m->first << "\n");
}
}
// Runs given function in threads_no threads and returns vector of futures for
// there
// results.
template <class R, typename S>
std::vector<std::future<std::pair<size_t, R>>>
run(size_t threads_no, S &skiplist,
std::function<R(typename S::Accessor, size_t)> f)
{
std::vector<std::future<std::pair<size_t, R>>> futures;
for (size_t thread_i = 0; thread_i < threads_no; ++thread_i) {
std::packaged_task<std::pair<size_t, R>()> task(
[&skiplist, f, thread_i]() {
return std::pair<size_t, R>(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;
}
// Collects all data from futures.
template <class R>
auto collect(std::vector<std::future<R>> &collect)
{
std::vector<R> collection;
for (auto &fut : collect) {
collection.push_back(fut.get());
}
return collection;
}
// Returns object which tracs in owned which (key,data) where added and
// downcounts.
template <class K, class D, class S>
auto insert_try(typename S::Accessor &acc, long long &downcount,
std::vector<K> &owned)
{
return [&](K key, D data) mutable {
if (acc.insert(key, data).second) {
downcount--;
owned.push_back(key);
}
};
}
// Helper function.
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;
}
// Returns currentlz used memory in kB.
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;
}
// Performs memory check to determine if memory usage before calling given
// function
// is aproximately equal to memory usage after function. Memory usage is thread
// senstive so no_threads spawned in function is necessary.
void memory_check(size_t no_threads, std::function<void()> 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");
}

View File

@ -10,51 +10,51 @@ using std::endl;
template <typename list_type>
void test_concurrent_list_access(list_type &list, std::size_t size)
{
// test concurrent access
for (int i = 0; i < 1000000; ++i) {
// test concurrent access
for (int i = 0; i < 1000000; ++i) {
std::thread t1([&list] {
list.push_front(1);
list.pop_front();
});
std::thread t1([&list] {
list.push_front(1);
list.pop_front();
});
std::thread t2([&list] {
list.push_front(2);
list.pop_front();
});
std::thread t2([&list] {
list.push_front(2);
list.pop_front();
});
t1.join();
t2.join();
t1.join();
t2.join();
assert(list.size() == size);
}
assert(list.size() == size);
}
}
int main()
{
LinkedList<int> list;
LinkedList<int> list;
// push & pop operations
list.push_front(10);
list.push_front(20);
auto a = list.front();
assert(a == 20);
list.pop_front();
a = list.front();
assert(a == 10);
list.pop_front();
assert(list.size() == 0);
// push & pop operations
list.push_front(10);
list.push_front(20);
auto a = list.front();
assert(a == 20);
list.pop_front();
a = list.front();
assert(a == 10);
list.pop_front();
assert(list.size() == 0);
// concurrent test
LinkedList<int> concurrent_list;
concurrent_list.push_front(1);
concurrent_list.push_front(1);
std::list<int> no_concurrent_list;
no_concurrent_list.push_front(1);
no_concurrent_list.push_front(1);
test_concurrent_list_access(concurrent_list, 2);
// test_concurrent_list_access(no_concurrent_list, 2);
// concurrent test
LinkedList<int> concurrent_list;
concurrent_list.push_front(1);
concurrent_list.push_front(1);
std::list<int> no_concurrent_list;
no_concurrent_list.push_front(1);
no_concurrent_list.push_front(1);
return 0;
test_concurrent_list_access(concurrent_list, 2);
// test_concurrent_list_access(no_concurrent_list, 2);
return 0;
}

View File

@ -1,85 +0,0 @@
#include <chrono>
#include <iostream>
#include <thread>
#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<int, int>;
using namespace std::chrono_literals;
#define THREADS_NO 1
constexpr size_t elems_per_thread = 1000;
int main()
{
ds::static_array<std::thread, THREADS_NO> 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);
}
// wait all threads
for (auto &thread : threads) {
thread.join();
}
// get skiplist size
{
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);
}
// wait all threads
for (auto &thread : threads) {
thread.join();
}
// check 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");
}
std::this_thread::sleep_for(1s);
// TODO: test GC and memory
return 0;
}

View File

@ -0,0 +1,39 @@
#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, [] {
map_t skiplist;
auto futures = run<std::vector<size_t>>(
THREADS_NO, skiplist, [](auto acc, auto index) {
auto rand = rand_gen(key_range);
long long downcount = elems_per_thread;
std::vector<size_t> owned;
auto inserter =
insert_try<size_t, size_t, map_t>(acc, downcount, owned);
do {
inserter(rand(), index);
} while (downcount > 0);
check_present_same<map_t>(acc, index, owned);
return owned;
});
auto accessor = skiplist.access();
for (auto &owned : collect(futures)) {
check_present_same<map_t>(accessor, owned);
}
check_size<map_t>(accessor, THREADS_NO * elems_per_thread);
check_order<map_t>(accessor);
});
}

View File

@ -0,0 +1,40 @@
#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, [] {
map_t skiplist;
auto futures = run<std::vector<size_t>>(
THREADS_NO, skiplist, [](auto acc, auto index) {
auto rand = rand_gen(key_range);
long long downcount = elems_per_thread;
std::vector<size_t> owned;
auto inserter =
insert_try<size_t, size_t, map_t>(acc, downcount, owned);
for (int i = 0; downcount > 0; i++) {
inserter(i, index);
}
check_present_same<map_t>(acc, index, owned);
return owned;
});
auto accessor = skiplist.access();
for (auto &owned : collect(futures)) {
check_present_same<map_t>(accessor, owned);
}
check_size<map_t>(accessor, THREADS_NO * elems_per_thread);
check_order<map_t>(accessor);
});
}

View File

@ -0,0 +1,76 @@
#include "common.h"
#define THREADS_NO 8
constexpr size_t elems_per_thread = 1e5;
int main()
{
memory_check(THREADS_NO, [&] {
ds::static_array<std::thread, THREADS_NO> threads;
map_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(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();
}
// get skiplist size
{
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);
}
// // wait all threads
for (auto &thread : threads) {
thread.join();
}
// check 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");
}
{
auto accessor = skiplist.access();
check_order<map_t>(accessor);
}
});
}

View File

@ -0,0 +1,24 @@
#include "common.h"
#define THREADS_NO 8
constexpr size_t elements = 2e6;
// Test for simple memory leaks
int main()
{
memory_check(THREADS_NO, [] {
map_t skiplist;
auto futures = run<size_t>(THREADS_NO, skiplist, [](auto acc, auto index) {
for (size_t i = 0; i < elements; i++) {
acc.insert(i, index);
}
return index;
});
collect(futures);
auto accessor = skiplist.access();
check_size<map_t>(accessor, elements);
});
}

View File

@ -0,0 +1,72 @@
#include "common.h"
#define THREADS_NO 1
constexpr size_t elems_per_thread = 16e5;
// Known memory leak at 1,600,000 elements.
int main()
{
memory_check(THREADS_NO, [&] {
ds::static_array<std::thread, THREADS_NO> threads;
map_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(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();
}
// get skiplist size
{
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);
}
// // wait all threads
for (auto &thread : threads) {
thread.join();
}
// check 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");
}
});
}

View File

@ -0,0 +1,55 @@
#include "common.h"
#define THREADS_NO 8
constexpr size_t key_range = 1e4;
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 = 1;
// This test checks MultiIterator from multimap.
// Each thread removes random data. So removes are joint.
// Calls of remove method are interleaved with insert calls which always
// succeed.
int main()
{
memory_check(THREADS_NO, [] {
multimap_t skiplist;
auto futures = run<std::vector<long long>>(
THREADS_NO, skiplist, [](auto acc, auto index) {
auto rand = rand_gen(key_range);
auto rand_op = rand_gen_bool(no_insert_for_one_delete);
long long downcount = op_per_thread;
std::vector<long long> set(key_range, 0);
do {
size_t num = rand();
auto data = num % max_number;
if (rand_op()) {
if (acc.remove(num)) {
downcount--;
set[num]--;
}
} else {
acc.insert(num, data);
downcount--;
set[num]++;
}
} while (downcount > 0);
return set;
});
long set[key_range] = {0};
for (auto &data : collect(futures)) {
for (int i = 0; i < key_range; i++) {
set[i] += data.second[i];
}
}
auto accessor = skiplist.access();
check_multi_iterator(accessor, key_range, set);
check_order<multimap_t>(accessor);
});
}

View File

@ -0,0 +1,90 @@
#include "common.h"
#define THREADS_NO 8
constexpr size_t key_range = 1e4;
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 = 1;
// This test checks MultiIterator remove method.
// Each thread removes random data. So removes are joint and scattered on same
// key values.
// Calls of remove method are interleaved with insert calls which always
// succeed.
int main()
{
memory_check(THREADS_NO, [] {
multimap_t skiplist;
auto futures = run<std::pair<long long, std::vector<long long>>>(
THREADS_NO, skiplist, [](auto acc, auto index) {
auto rand = rand_gen(key_range);
auto rand_op = rand_gen_bool(no_insert_for_one_delete);
long long downcount = op_per_thread;
std::vector<long long> set(key_range, 0);
long long sum = 0;
do {
size_t num = rand();
auto data = rand() % max_number;
if (rand_op()) {
int len = 0;
for (auto it = acc.find_multi(num); it.has_value();
it++) {
len++;
}
if (len > 0) {
int pos = rand() % len;
for (auto it = acc.find_multi(num); it.has_value();
it++) {
if (pos == 0) {
auto data_r = it->second;
if (it.remove()) {
downcount--;
set[num]--;
sum -= data_r;
permanent_assert(
it.is_removed(),
"is_removed method doesn't work");
}
break;
}
pos--;
}
}
} else {
acc.insert(num, data);
downcount--;
set[num]++;
sum += data;
}
} while (downcount > 0);
return std::pair<long long, std::vector<long long>>(sum, set);
});
long set[key_range] = {0};
long long sums = 0;
for (auto &data : collect(futures)) {
sums += data.second.first;
for (int i = 0; i < key_range; i++) {
set[i] += data.second.second[i];
}
}
auto accessor = skiplist.access();
check_multi_iterator(accessor, key_range, set);
for (auto &e : accessor) {
set[e.first]--;
sums -= e.second;
}
permanent_assert(sums == 0, "Aproximetly Same values are present");
check_zero(key_range, set, "MultiMap");
check_order<multimap_t>(accessor);
});
}

View File

@ -0,0 +1,82 @@
#include "common.h"
#define THREADS_NO 4
constexpr size_t key_range = 1e4;
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 MultiIterator remove method ].
// Each thread removes all duplicate data on random key. So removes are joint
// and scattered on same
// key values.
// Calls of remove method are interleaved with insert calls which always
// succeed.
int main()
{
memory_check(THREADS_NO, [] {
multimap_t skiplist;
auto futures = run<std::pair<long long, std::vector<long long>>>(
THREADS_NO, skiplist, [](auto acc, auto index) {
auto rand = rand_gen(key_range);
auto rand_op = rand_gen_bool(no_insert_for_one_delete);
long long downcount = op_per_thread;
std::vector<long long> set(key_range, 0);
long long sum = 0;
do {
size_t num = rand();
auto data = rand() % max_number;
if (rand_op()) {
auto it = acc.find_multi(num);
if (it.has_value()) {
it++;
while (it.has_value()) {
auto data_r = it->second;
if (it.remove()) {
downcount--;
set[num]--;
sum -= data_r;
permanent_assert(
it.is_removed(),
"is_removed method doesn't work");
}
it++;
}
}
} else {
acc.insert(num, data);
downcount--;
set[num]++;
sum += data;
}
} while (downcount > 0);
return std::pair<long long, std::vector<long long>>(sum, set);
});
long set[key_range] = {0};
long long sums = 0;
for (auto &data : collect(futures)) {
sums += data.second.first;
for (int i = 0; i < key_range; i++) {
set[i] += data.second.second[i];
}
}
auto accessor = skiplist.access();
check_multi_iterator(accessor, key_range, set);
for (auto &e : accessor) {
set[e.first]--;
sums -= e.second;
}
permanent_assert(sums == 0, "Aproximetly Same values are present");
check_zero(key_range, set, "MultiMap");
check_order<multimap_t>(accessor);
});
}

View File

@ -0,0 +1,120 @@
#include "common.h"
#define THREADS_NO 8
constexpr size_t key_range = 1e4;
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 = 1;
// This test checks multimap.
// Each thread removes random data. So removes are joint.
// Calls of remove method are interleaved with insert calls which always
// succeed.
int main()
{
memory_check(THREADS_NO, [] {
multimap_t skiplist;
std::atomic<long long> size(0);
auto futures = run<std::pair<long long, std::vector<long long>>>(
THREADS_NO, skiplist, [&size](auto acc, auto index) {
auto rand = rand_gen(key_range);
auto rand_op = rand_gen_bool(no_insert_for_one_delete);
long long downcount = op_per_thread;
std::vector<long long> set(key_range, 0);
long long sum = 0;
do {
size_t num = rand();
auto data = num % max_number;
if (rand_op()) {
if (acc.remove(num)) {
downcount--;
set[num]--;
sum -= data;
size--;
}
} else {
acc.insert(num, data);
downcount--;
set[num]++;
sum += data;
size++;
}
} while (downcount > 0);
return std::pair<long long, std::vector<long long>>(sum, set);
});
long set[key_range] = {0};
long long sums = 0;
long long size_calc = 0;
for (auto &data : collect(futures)) {
sums += data.second.first;
for (int i = 0; i < key_range; i++) {
set[i] += data.second.second[i];
size_calc += data.second.second[i];
}
}
auto accessor = skiplist.access();
permanent_assert(size == size_calc, "Set size isn't same as counted");
check_size<multimap_t>(accessor, size);
check_order<multimap_t>(accessor);
auto bef_it = accessor.end();
for (int i = 0; i < key_range; i++) {
auto it = accessor.find(i);
if (set[i] > 0) {
permanent_assert(it != accessor.end(),
"Multimap doesn't contain necessary element "
<< i);
if (bef_it == accessor.end()) bef_it = accessor.find(i);
for (int j = 0; j < set[i]; j++) {
permanent_assert(
bef_it != accessor.end(),
"Previous iterator doesn't iterate through same "
"key entrys. Expected "
<< i << " for " << set[i] - j
<< " more times but found null");
permanent_assert(
bef_it->first == i,
"Previous iterator doesn't iterate through same "
"key entrys. Expected "
<< i << " for " << set[i] - j
<< " more times but found " << bef_it->first
<< ". Occurances should be " << set[i]);
bef_it++;
}
for (int j = 0; j < set[i]; j++) {
permanent_assert(it != accessor.end(),
"Iterator doesn't iterate through same "
"key entrys. Expected "
<< i << " for " << set[i] - j
<< " more times but found null");
permanent_assert(
it->first == i,
"Iterator doesn't iterate through same "
"key entrys. Expected "
<< i << " for " << set[i] - j
<< " more times but found " << it->first
<< ". Occurances should be " << set[i]);
it++;
}
permanent_assert(it == accessor.end() || it->first != i,
"There is more data than it should be.");
bef_it = it;
}
}
for (auto &e : accessor) {
set[e.first]--;
sums -= e.second;
}
permanent_assert(sums == 0, "Aproximetly Same values are present");
check_zero(key_range, set, "MultiMap");
});
}

View File

@ -0,0 +1,70 @@
#include "common.h"
#define THREADS_NO 8
constexpr size_t key_range = 1e4;
constexpr size_t op_per_thread = 1e5;
constexpr size_t no_insert_for_one_delete = 1;
// This test checks multiset.
// Each thread removes random data. So removes are joint.
// Calls of remove method are interleaved with insert calls which always
// succeed.
int main()
{
memory_check(THREADS_NO, [] {
multiset_t skiplist;
auto futures = run<std::vector<long>>(
THREADS_NO, skiplist, [](auto acc, auto index) {
auto rand = rand_gen(key_range);
auto rand_op = rand_gen_bool(no_insert_for_one_delete);
long long downcount = op_per_thread;
std::vector<long> set(key_range, 0);
do {
size_t num = rand();
if (rand_op()) {
if (acc.remove(num)) {
downcount--;
set[num]--;
}
} else {
acc.insert(num);
downcount--;
set[num]++;
}
} while (downcount > 0);
return set;
});
long set[key_range] = {0};
for (auto &data : collect(futures)) {
for (int i = 0; i < key_range; i++) {
set[i] += data.second[i];
}
}
auto accessor = skiplist.access();
for (int i = 0; i < key_range; i++) {
auto it = accessor.find(i);
if (set[i] > 0) {
for (int j = 0; j < set[i]; j++) {
permanent_assert(
it == i,
"Iterator doesn't iterate through same key entrys");
it++;
}
}
permanent_assert(it == accessor.end() || it != i,
"There is more data than it should be.");
}
for (auto &e : accessor) {
set[e]--;
}
check_zero(key_range, set, "MultiSet");
});
}

View File

@ -0,0 +1,64 @@
#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, [] {
map_t skiplist;
auto futures = run<std::pair<long long, long long>>(
THREADS_NO, skiplist, [](auto acc, auto index) {
auto rand_op = rand_gen_bool(no_insert_for_one_delete);
long long 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<long long, long long>(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<map_t>(accessor, counters);
check_order<map_t>(accessor);
});
}

View File

@ -0,0 +1,50 @@
#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, [] {
map_t skiplist;
auto futures = run<std::vector<size_t>>(
THREADS_NO, skiplist, [](auto acc, auto index) {
auto rand = rand_gen(key_range);
auto rand_op = rand_gen_bool(no_insert_for_one_delete);
long long downcount = op_per_thread;
std::vector<size_t> owned;
auto inserter =
insert_try<size_t, size_t, map_t>(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<map_t>(acc, index, owned);
return owned;
});
auto accessor = skiplist.access();
size_t count = 0;
for (auto &owned : collect(futures)) {
check_present_same<map_t>(accessor, owned);
count += owned.second.size();
}
check_size<map_t>(accessor, count);
check_order<map_t>(accessor);
});
}

View File

@ -0,0 +1,62 @@
#include "common.h"
#define THREADS_NO 8
constexpr size_t key_range = 1e4;
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, [] {
map_t skiplist;
auto futures = run<std::pair<long long, long long>>(
THREADS_NO, skiplist, [](auto acc, auto index) {
auto rand = rand_gen(key_range);
auto rand_op = rand_gen_bool(no_insert_for_one_delete);
long long 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<long long, long long>(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<map_t>(accessor, counters);
check_order<map_t>(accessor);
});
}

View File

@ -0,0 +1,61 @@
#include "common.h"
#define THREADS_NO 8
constexpr size_t key_range = 1e4;
constexpr size_t op_per_thread = 1e5;
constexpr size_t no_insert_for_one_delete = 2;
// This test checks set.
// Each thread removes random data. So removes are joint.
// Calls of remove method are interleaved with insert calls.
int main()
{
memory_check(THREADS_NO, [] {
set_t skiplist;
auto futures = run<std::vector<long>>(
THREADS_NO, skiplist, [](auto acc, auto index) {
auto rand = rand_gen(key_range);
auto rand_op = rand_gen_bool(no_insert_for_one_delete);
long long downcount = op_per_thread;
std::vector<long> set(key_range);
do {
size_t num = rand();
if (rand_op()) {
if (acc.remove(num)) {
downcount--;
set[num]--;
}
} else {
if (acc.insert(num).second) {
downcount--;
set[num]++;
}
}
} while (downcount > 0);
return set;
});
long set[key_range] = {0};
for (auto &data : collect(futures)) {
for (int i = 0; i < key_range; i++) {
set[i] += data.second[i];
}
}
auto accessor = skiplist.access();
for (int i = 0; i < key_range; i++) {
permanent_assert(set[i] == 0 || set[i] == 1 ||
(set[i] == 1) ^ accessor.contains(i),
"Set doesn't hold it's guarantees.");
}
for (auto &e : accessor) {
set[e]--;
}
check_zero(key_range, set, "Set");
});
}

View File

@ -0,0 +1,69 @@
#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, [] {
map_t skiplist;
auto futures = run<std::pair<long long, long long>>(
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<long long, long long>(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<map_t>(accessor, counters);
check_order<map_t>(accessor);
});
}

View File

@ -8,62 +8,59 @@ using std::endl;
using skiplist_t = ConcurrentMap<int, int>;
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;
}