Merge branch 'dev' of https://phabricator.tomicevic.com/diffusion/MG/memgraph into dev
This commit is contained in:
commit
b5f884d428
.clang-format.gitignore
Testing/Temporary
src
data_structures
concurrent
common.hppconcurrent_map.hppconcurrent_multimap.hppconcurrent_multiset.hppconcurrent_set.hppskiplist.hppskiplist_gc.hpp
skiplist
memory
storage
threading/sync
utils
tests
concurrent
common.hlinkedlist.cppskiplist.cppsl_insert.cppsl_insert_competetive.cppsl_map.cppsl_memory.cppsl_memory_leak.cppsl_multiiterator.cppsl_multiiterator_remove.cppsl_multiiterator_remove_duplicates.cppsl_multimap.cppsl_multiset.cppsl_remove_competetive.cppsl_remove_disjoint.cppsl_remove_joint.cppsl_set.cppsl_simulation.cpp
unit
89
.clang-format
Normal file
89
.clang-format
Normal 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
1
.gitignore
vendored
@ -13,3 +13,4 @@ memgraph
|
||||
*.session.yaml
|
||||
tags
|
||||
.gdb_history
|
||||
Testing/
|
||||
|
1
Testing/Temporary/CTestCostData.txt
Normal file
1
Testing/Temporary/CTestCostData.txt
Normal file
@ -0,0 +1 @@
|
||||
---
|
3
Testing/Temporary/LastTest.log
Normal file
3
Testing/Temporary/LastTest.log
Normal file
@ -0,0 +1,3 @@
|
||||
Start testing: Jul 28 19:05 BST
|
||||
----------------------------------------------------------
|
||||
End testing: Jul 28 19:05 BST
|
94
src/data_structures/concurrent/common.hpp
Normal file
94
src/data_structures/concurrent/common.hpp
Normal 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;
|
||||
};
|
@ -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;
|
||||
};
|
||||
|
70
src/data_structures/concurrent/concurrent_multimap.hpp
Normal file
70
src/data_structures/concurrent/concurrent_multimap.hpp
Normal 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;
|
||||
};
|
50
src/data_structures/concurrent/concurrent_multiset.hpp
Normal file
50
src/data_structures/concurrent/concurrent_multiset.hpp
Normal 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;
|
||||
};
|
@ -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
@ -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;
|
@ -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;
|
||||
|
@ -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;
|
||||
};
|
@ -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"
|
||||
|
@ -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;
|
||||
};
|
||||
|
@ -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;
|
||||
};
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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!
|
||||
|
@ -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
233
tests/concurrent/common.h
Normal 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");
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
39
tests/concurrent/sl_insert.cpp
Normal file
39
tests/concurrent/sl_insert.cpp
Normal 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);
|
||||
});
|
||||
}
|
40
tests/concurrent/sl_insert_competetive.cpp
Normal file
40
tests/concurrent/sl_insert_competetive.cpp
Normal 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);
|
||||
});
|
||||
}
|
76
tests/concurrent/sl_map.cpp
Normal file
76
tests/concurrent/sl_map.cpp
Normal 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);
|
||||
}
|
||||
});
|
||||
}
|
24
tests/concurrent/sl_memory.cpp
Normal file
24
tests/concurrent/sl_memory.cpp
Normal 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);
|
||||
});
|
||||
}
|
72
tests/concurrent/sl_memory_leak.cpp
Normal file
72
tests/concurrent/sl_memory_leak.cpp
Normal 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");
|
||||
}
|
||||
});
|
||||
}
|
55
tests/concurrent/sl_multiiterator.cpp
Normal file
55
tests/concurrent/sl_multiiterator.cpp
Normal 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);
|
||||
});
|
||||
}
|
90
tests/concurrent/sl_multiiterator_remove.cpp
Normal file
90
tests/concurrent/sl_multiiterator_remove.cpp
Normal 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);
|
||||
});
|
||||
}
|
82
tests/concurrent/sl_multiiterator_remove_duplicates.cpp
Normal file
82
tests/concurrent/sl_multiiterator_remove_duplicates.cpp
Normal 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);
|
||||
});
|
||||
}
|
120
tests/concurrent/sl_multimap.cpp
Normal file
120
tests/concurrent/sl_multimap.cpp
Normal 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");
|
||||
});
|
||||
}
|
70
tests/concurrent/sl_multiset.cpp
Normal file
70
tests/concurrent/sl_multiset.cpp
Normal 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");
|
||||
|
||||
});
|
||||
}
|
64
tests/concurrent/sl_remove_competetive.cpp
Normal file
64
tests/concurrent/sl_remove_competetive.cpp
Normal 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);
|
||||
});
|
||||
}
|
50
tests/concurrent/sl_remove_disjoint.cpp
Normal file
50
tests/concurrent/sl_remove_disjoint.cpp
Normal 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);
|
||||
});
|
||||
}
|
62
tests/concurrent/sl_remove_joint.cpp
Normal file
62
tests/concurrent/sl_remove_joint.cpp
Normal 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);
|
||||
});
|
||||
}
|
61
tests/concurrent/sl_set.cpp
Normal file
61
tests/concurrent/sl_set.cpp
Normal 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");
|
||||
});
|
||||
}
|
69
tests/concurrent/sl_simulation.cpp
Normal file
69
tests/concurrent/sl_simulation.cpp
Normal 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);
|
||||
});
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user