Implemented ConncurentMap and updated it's users.

Added tests for ConcurrentMap.
Implemented ~SkipList.
This commit is contained in:
Kruno Tomola Fabro 2016-07-30 21:20:21 +01:00
parent 6970170f69
commit ac7f6f507f
21 changed files with 1306 additions and 819 deletions

View File

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

View File

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

90
format.clang-format Normal file
View File

@ -0,0 +1,90 @@
---
Language: Cpp
# BasedOnStyle: LLVM
AccessModifierOffset: -4
AlignAfterOpenBracket: Align
AlignConsecutiveAssignments: false
AlignConsecutiveDeclarations: false
AlignEscapedNewlinesLeft: false
AlignOperands: true
AlignTrailingComments: true
AllowAllParametersOfDeclarationOnNextLine: true
AllowShortBlocksOnASingleLine: false
AllowShortCaseLabelsOnASingleLine: false
AllowShortFunctionsOnASingleLine: All
AllowShortIfStatementsOnASingleLine: true
AllowShortLoopsOnASingleLine: false
AlwaysBreakAfterDefinitionReturnType: None
AlwaysBreakAfterReturnType: None
AlwaysBreakBeforeMultilineStrings: false
AlwaysBreakTemplateDeclarations: true
BinPackArguments: true
BinPackParameters: true
BraceWrapping:
AfterClass: true
AfterControlStatement: false
AfterEnum: true
AfterFunction: true
AfterNamespace: true
AfterObjCDeclaration: false
AfterStruct: true
AfterUnion: true
BeforeCatch: false
BeforeElse: false
IndentBraces: false
BreakBeforeBinaryOperators: None
BreakBeforeBraces: Custom
BreakBeforeTernaryOperators: true
BreakConstructorInitializersBeforeComma: false
ColumnLimit: 80
CommentPragmas: '^ IWYU pragma:'
ConstructorInitializerAllOnOneLineOrOnePerLine: false
ConstructorInitializerIndentWidth: 4
ContinuationIndentWidth: 4
Cpp11BracedListStyle: true
DerivePointerAlignment: false
DisableFormat: false
ExperimentalAutoDetectBinPacking: false
ForEachMacros: [ foreach, Q_FOREACH, BOOST_FOREACH ]
IncludeCategories:
- Regex: '^"(llvm|llvm-c|clang|clang-c)/'
Priority: 2
- Regex: '^(<|"(gtest|isl|json)/)'
Priority: 3
- Regex: '.*'
Priority: 1
IndentCaseLabels: false
IndentWidth: 2
IndentWrappedFunctionNames: false
KeepEmptyLinesAtTheStartOfBlocks: true
MacroBlockBegin: ''
MacroBlockEnd: ''
MaxEmptyLinesToKeep: 1
NamespaceIndentation: None
ObjCBlockIndentWidth: 2
ObjCSpaceAfterProperty: false
ObjCSpaceBeforeProtocolList: true
PenaltyBreakBeforeFirstCallParameter: 19
PenaltyBreakComment: 300
PenaltyBreakFirstLessLess: 120
PenaltyBreakString: 1000
PenaltyExcessCharacter: 1000000
PenaltyReturnTypeOnItsOwnLine: 60
PointerAlignment: Right
ReflowComments: true
SortIncludes: true
SpaceAfterCStyleCast: false
SpaceBeforeAssignmentOperators: true
SpaceBeforeParens: ControlStatements
SpaceInEmptyParentheses: false
SpacesBeforeTrailingComments: 1
SpacesInAngles: false
SpacesInContainerLiterals: true
SpacesInCStyleCastParentheses: false
SpacesInParentheses: false
SpacesInSquareBrackets: false
Standard: "C++11"
TabWidth: 8
UseTab: Never
...

View File

@ -1,9 +1,107 @@
#pragma once
#include "data_structures/skiplist/skiplist_map.hpp"
#include "data_structures/concurrent/skiplist.hpp"
#include "utils/total_ordering.hpp"
template<class K, class T>
class ConcurrentMap : public SkipListMap<K, T>
{
// TODO implement with SkipList
using std::pair;
template <typename K, typename T> class ConcurrentMap {
class Item : public TotalOrdering<Item>,
public TotalOrdering<K, Item>,
public TotalOrdering<Item, 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;
}
};
typedef SkipList<Item> list;
typedef typename SkipList<Item>::Iterator list_it;
typedef typename SkipList<Item>::ConstIterator list_it_con;
public:
ConcurrentMap() {}
friend class Accessor;
class Accessor {
friend class ConcurrentMap;
Accessor(list *skiplist) : accessor(skiplist->access()) {}
public:
Accessor(const Accessor &) = delete;
Accessor(Accessor &&other) : accessor(std::move(other.accessor)) {}
~Accessor() {}
list_it begin() { return accessor.begin(); }
list_it_con begin() const { return accessor.cbegin(); }
list_it_con cbegin() const { return accessor.cbegin(); }
list_it end() { return accessor.end(); }
list_it_con end() const { return accessor.cend(); }
list_it_con cend() const { return accessor.cend(); }
std::pair<list_it, bool> insert(const K &key, const T &data) {
return accessor.insert(Item(key, data));
}
std::pair<list_it, bool> insert(const K &key, T &&data) {
return accessor.insert(Item(key, std::forward<T>(data)));
}
std::pair<list_it, bool> insert(K &&key, T &&data) {
return accessor.insert(Item(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); }
size_t size() const { return accessor.size(); }
private:
typename list::Accessor accessor;
};
Accessor access() { return Accessor(&skiplist); }
const Accessor access() const { return Accessor(&skiplist); }
private:
list skiplist;
};

File diff suppressed because it is too large Load Diff

View File

@ -8,48 +8,45 @@
#include "threading/sync/spinlock.hpp"
template <class T, class lock_t = SpinLock>
class SkiplistGC : public LazyGC<SkiplistGC<T, lock_t>, lock_t>
{
class SkiplistGC : public LazyGC<SkiplistGC<T, lock_t>, lock_t> {
public:
// release_ref method should be called by a thread
// when the thread finish it job over object
// which has to be lazy cleaned
// if thread counter becames zero, all objects in the local_freelist
// are going to be deleted
// the only problem with this approach is that
// GC may never be called, but for now we can deal with that
void release_ref()
// release_ref method should be called by a thread
// when the thread finish it job over object
// which has to be lazy cleaned
// if thread counter becames zero, all objects in the local_freelist
// are going to be deleted
// the only problem with this approach is that
// GC may never be called, but for now we can deal with that
void release_ref() {
std::vector<T *> local_freelist;
// take freelist if there is no more threads
{
std::vector<T *> local_freelist;
// take freelist if there is no more threads
{
auto lock = this->acquire_unique();
--this->count;
if (this->count == 0) {
freelist.swap(local_freelist);
}
}
if (local_freelist.size() > 0) {
std::cout << "GC started" << std::endl;
std::cout << "Local skiplist size: " <<
local_freelist.size() << std::endl;
long long counter = 0;
// destroy all elements from local_freelist
for (auto element : local_freelist) {
counter++;
if (element->flags.is_marked()) T::destroy(element);
}
std::cout << "Number of destroyed elements " << counter << std::endl;
}
auto lock = this->acquire_unique();
--this->count;
if (this->count == 0) {
freelist.swap(local_freelist);
}
}
void collect(T *node)
{
freelist.add(node);
if (local_freelist.size() > 0) {
std::cout << "GC started" << std::endl;
std::cout << "Local list size: " << local_freelist.size() << std::endl;
long long counter = 0;
// destroy all elements from local_freelist
for (auto element : local_freelist) {
if (element->flags.is_marked()) {
T::destroy(element);
counter++;
}
}
std::cout << "Number of destroyed elements " << counter << std::endl;
}
}
void collect(T *node) { freelist.add(node); }
private:
FreeList<T> freelist;
FreeList<T> freelist;
};

View File

@ -1,24 +1,24 @@
#pragma once
// TODO: remove from here and from the project
#include <atomic>
#include <iostream>
#include "threading/sync/lockable.hpp"
#include "utils/crtp.hpp"
template <class Derived, class lock_t = SpinLock>
class LazyGC : public Crtp<Derived>, public Lockable<lock_t>
{
class LazyGC : public Crtp<Derived>, public Lockable<lock_t> {
public:
// add_ref method should be called by a thread
// when the thread has to do something over
// object which has to be lazy cleaned when
// the thread finish it job
void add_ref()
{
auto lock = this->acquire_unique();
++count;
}
// add_ref method should be called by a thread
// when the thread has to do something over
// object which has to be lazy cleaned when
// the thread finish it job
void add_ref() {
auto lock = this->acquire_unique();
++count;
}
protected:
size_t count{0};
size_t count{0};
};

View File

@ -1,52 +1,46 @@
#pragma once
#include "common.hpp"
#include "edge_accessor.hpp"
#include "data_structures/concurrent/concurrent_map.hpp"
#include "edge_accessor.hpp"
class Edges
{
class Edges {
public:
Edge::Accessor find(tx::Transaction& t, const Id& id)
{
auto edges_accessor = edges.access();
auto edges_iterator = edges_accessor.find(id);
Edge::Accessor find(tx::Transaction &t, const Id &id) {
auto edges_accessor = edges.access();
auto edges_iterator = edges_accessor.find(id);
if (edges_iterator == edges_accessor.end())
return Edge::Accessor();
if (edges_iterator == edges_accessor.end())
return Edge::Accessor();
// find edge
auto edge = edges_iterator->second.find(t);
// find edge
auto edge = edges_iterator->second.find(t);
if (edge == nullptr)
return Edge::Accessor();
if (edge == nullptr)
return Edge::Accessor();
return Edge::Accessor(edge, &edges_iterator->second, this);
}
return Edge::Accessor(edge, &edges_iterator->second, this);
}
Edge::Accessor insert(tx::Transaction& t)
{
// get next vertex id
auto next = counter.next(std::memory_order_acquire);
Edge::Accessor insert(tx::Transaction &t) {
// get next vertex id
auto next = counter.next(std::memory_order_acquire);
// create new vertex record
EdgeRecord edge_record(next);
// create new vertex record
EdgeRecord edge_record(next);
// insert the new vertex record into the vertex store
auto edges_accessor = edges.access();
auto result = edges_accessor.insert_unique(
next,
std::move(edge_record)
);
// insert the new vertex record into the vertex store
auto edges_accessor = edges.access();
auto result = edges_accessor.insert(next, std::move(edge_record));
// create new vertex
auto inserted_edge_record = result.first;
auto edge = inserted_edge_record->second.insert(t);
// create new vertex
auto inserted_edge_record = result.first;
auto edge = inserted_edge_record->second.insert(t);
return Edge::Accessor(edge, &inserted_edge_record->second, this);
}
return Edge::Accessor(edge, &inserted_edge_record->second, this);
}
private:
ConcurrentMap<uint64_t, EdgeRecord> edges;
AtomicCounter<uint64_t> counter;
ConcurrentMap<uint64_t, EdgeRecord> edges;
AtomicCounter<uint64_t> counter;
};

View File

@ -7,39 +7,34 @@
#include "storage/indexes/index_record_collection.hpp"
#include "storage/label/label.hpp"
template <class Key, class Item>
class Index
{
template <class Key, class Item> class Index {
public:
using container_t = ConcurrentMap<Key, Item>;
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));
}
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;
std::unique_ptr<container_t> index;
};

View File

@ -1,50 +1,45 @@
#include "storage/vertices.hpp"
const Vertex::Accessor Vertices::find(tx::Transaction &t, const Id &id)
{
auto vertices_accessor = vertices.access();
auto vertices_iterator = vertices_accessor.find(id);
const Vertex::Accessor Vertices::find(tx::Transaction &t, const Id &id) {
auto vertices_accessor = vertices.access();
auto vertices_iterator = vertices_accessor.find(id);
if (vertices_iterator == vertices_accessor.end())
return Vertex::Accessor();
if (vertices_iterator == vertices_accessor.end())
return Vertex::Accessor();
// find vertex
auto vertex = vertices_iterator->second.find(t);
// find vertex
auto vertex = vertices_iterator->second.find(t);
if (vertex == nullptr) return Vertex::Accessor();
if (vertex == nullptr)
return Vertex::Accessor();
return Vertex::Accessor(vertex, &vertices_iterator->second, this);
return Vertex::Accessor(vertex, &vertices_iterator->second, this);
}
Vertex::Accessor Vertices::insert(tx::Transaction &t)
{
// get next vertex id
auto next = counter.next();
Vertex::Accessor Vertices::insert(tx::Transaction &t) {
// get next vertex id
auto next = counter.next();
// create new vertex record
VertexRecord vertex_record(next);
// vertex_record.id(next);
// create new vertex record
VertexRecord vertex_record(next);
// vertex_record.id(next);
// insert the new vertex record into the vertex store
auto vertices_accessor = vertices.access();
auto result =
vertices_accessor.insert_unique(next, std::move(vertex_record));
// insert the new vertex record into the vertex store
auto vertices_accessor = vertices.access();
auto result = vertices_accessor.insert(next, std::move(vertex_record));
// create new vertex
auto inserted_vertex_record = result.first;
auto vertex = inserted_vertex_record->second.insert(t);
// create new vertex
auto inserted_vertex_record = result.first;
auto vertex = inserted_vertex_record->second.insert(t);
return Vertex::Accessor(vertex, &inserted_vertex_record->second, this);
return Vertex::Accessor(vertex, &inserted_vertex_record->second, this);
}
void Vertices::update_label_index(const Label &label,
VertexIndexRecord &&index_record)
{
label_index.update(label,
std::forward<VertexIndexRecord>(index_record));
VertexIndexRecord &&index_record) {
label_index.update(label, std::forward<VertexIndexRecord>(index_record));
}
VertexIndexRecordCollection& Vertices::find_label_index(const Label& label)
{
return label_index.find(label);
VertexIndexRecordCollection &Vertices::find_label_index(const Label &label) {
return label_index.find(label);
}

View File

@ -1,25 +1,19 @@
#pragma once
template <class Derived>
struct TotalOrdering
{
friend constexpr bool operator!=(const Derived& a, const Derived& b)
{
return !(a == b);
}
template <class Derived, class Other = Derived> struct TotalOrdering {
friend constexpr bool operator!=(const Derived &a, const Other &b) {
return !(a == b);
}
friend constexpr bool operator<=(const Derived& a, const Derived& b)
{
return a < b || a == b;
}
friend constexpr bool operator<=(const Derived &a, const Other &b) {
return a < b || a == b;
}
friend constexpr bool operator>(const Derived& a, const Derived& b)
{
return !(a <= b);
}
friend constexpr bool operator>(const Derived &a, const Other &b) {
return !(a <= b);
}
friend constexpr bool operator>=(const Derived& a, const Derived& b)
{
return !(a < b);
}
friend constexpr bool operator>=(const Derived &a, const Other &b) {
return !(a < b);
}
};

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

@ -0,0 +1,135 @@
#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/skiplist.hpp"
#include "data_structures/static_array.hpp"
#include "utils/assert.hpp"
#include "utils/sysinfo/memory.hpp"
using std::cout;
using std::endl;
using skiplist_t = ConcurrentMap<int, int>;
using namespace std::chrono_literals;
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; };
}
void check_present_same(skiplist_t::Accessor &acc,
std::pair<size_t, std::vector<size_t>> &owned) {
for (auto num : owned.second) {
permanent_assert(acc.find(num)->second == owned.first,
"My data is present and my");
}
}
void check_present_same(skiplist_t::Accessor &acc, size_t owner,
std::vector<size_t> &owned) {
for (auto num : owned) {
permanent_assert(acc.find(num)->second == owner,
"My data is present and my");
}
}
void check_size(const skiplist_t::Accessor &acc, long long size) {
// check size
permanent_assert(acc.size() == size,
"Size should be " << size << ", but size is " << acc.size());
// check count
size_t iterator_counter = 0;
for (auto elem : acc) {
++iterator_counter;
}
permanent_assert(iterator_counter == size, "Iterator count should be "
<< size << ", but size is "
<< acc.size());
}
template <class R>
std::vector<std::future<std::pair<size_t, R>>>
run(size_t threads_no, skiplist_t &skiplist,
std::function<R(skiplist_t::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;
}
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;
}
template <class K, class D>
auto insert_try(skiplist_t::Accessor &acc, size_t &downcount,
std::vector<K> &owned) {
return [&](K key, D data) mutable {
if (acc.insert(key, data).second) {
downcount--;
owned.push_back(key);
}
};
}
int parseLine(char *line) {
// This assumes that a digit will be found and the line ends in " Kb".
int i = strlen(line);
const char *p = line;
while (*p < '0' || *p > '9')
p++;
line[i - 3] = '\0';
i = atoi(p);
return i;
}
int currently_used_memory() { // Note: this value is in KB!
FILE *file = fopen("/proc/self/status", "r");
int result = -1;
char line[128];
while (fgets(line, 128, file) != NULL) {
if (strncmp(line, "VmSize:", 7) == 0) {
result = parseLine(line);
break;
}
}
fclose(file);
return result;
}
void memory_check(size_t no_threads, std::function<void()> f) {
long long start = currently_used_memory();
f();
long long leaked =
currently_used_memory() - start -
no_threads * 73732; // OS sensitive, 73732 size allocated for thread
std::cout << "leaked: " << leaked << "\n";
permanent_assert(leaked <= 0, "Memory leak check");
}

View File

@ -1,85 +1,69 @@
#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;
#include "common.h"
#define THREADS_NO 1
constexpr size_t elems_per_thread = 1000;
constexpr size_t elems_per_thread = 16e5;
int main()
{
int main() {
memory_check(THREADS_NO, [&] {
ds::static_array<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);
threads[thread_i] = std::thread(
[&skiplist](size_t start, size_t end) {
auto accessor = skiplist.access();
for (size_t elem_i = 0; elem_i < elems_per_thread; ++elem_i) {
accessor.insert(elem_i, elem_i);
}
},
thread_i * elems_per_thread,
thread_i * elems_per_thread + elems_per_thread);
}
// wait all threads
for (auto &thread : threads) {
thread.join();
thread.join();
}
// get skiplist size
{
auto accessor = skiplist.access();
permanent_assert(accessor.size() == THREADS_NO * elems_per_thread,
"all elements in skiplist");
auto accessor = skiplist.access();
permanent_assert(accessor.size() == THREADS_NO * elems_per_thread,
"all elements in skiplist");
}
for (size_t thread_i = 0; thread_i < THREADS_NO; ++thread_i) {
threads[thread_i] = std::thread(
[&skiplist](size_t start, size_t end) {
auto accessor = skiplist.access();
for (size_t elem_i = start; elem_i < end; ++elem_i) {
permanent_assert(accessor.remove(elem_i) == true, "");
}
},
thread_i * elems_per_thread,
thread_i * elems_per_thread + elems_per_thread);
auto accessor = skiplist.access();
for (size_t elem_i = 0; elem_i < elems_per_thread; ++elem_i) {
permanent_assert(accessor.remove(elem_i) == true, "");
}
},
thread_i * elems_per_thread,
thread_i * elems_per_thread + elems_per_thread);
}
// wait all threads
for (auto &thread : threads) {
thread.join();
thread.join();
}
// check size
{
auto accessor = skiplist.access();
permanent_assert(accessor.size() == 0, "Size should be 0, but size is " << accessor.size());
auto accessor = skiplist.access();
permanent_assert(accessor.size() == 0, "Size should be 0, but size is "
<< accessor.size());
}
// check count
{
size_t iterator_counter = 0;
auto accessor = skiplist.access();
for (auto elem : accessor) {
++iterator_counter;
cout << elem.first << " ";
}
permanent_assert(iterator_counter == 0, "deleted elements");
size_t iterator_counter = 0;
auto accessor = skiplist.access();
for (auto elem : accessor) {
++iterator_counter;
cout << elem.first << " ";
}
permanent_assert(iterator_counter == 0, "deleted elements");
}
std::this_thread::sleep_for(1s);
// TODO: test GC and memory
return 0;
});
}

View File

@ -0,0 +1,36 @@
#include "common.h"
#define THREADS_NO 8
constexpr size_t elems_per_thread = 100000;
constexpr size_t key_range = elems_per_thread * THREADS_NO * 2;
// This test checks insert_unique method under pressure.
// Test checks for missing data and changed/overwriten data.
int main() {
memory_check(THREADS_NO, [] {
skiplist_t skiplist;
auto futures = run<std::vector<size_t>>(
THREADS_NO, skiplist, [](auto acc, auto index) {
auto rand = rand_gen(key_range);
size_t downcount = elems_per_thread;
std::vector<size_t> owned;
auto inserter = insert_try<size_t, size_t>(acc, downcount, owned);
do {
inserter(rand(), index);
} while (downcount > 0);
check_present_same(acc, index, owned);
return owned;
});
auto accessor = skiplist.access();
for (auto &owned : collect(futures)) {
check_present_same(accessor, owned);
}
check_size(accessor, THREADS_NO * elems_per_thread);
});
}

View File

@ -0,0 +1,37 @@
#include "common.h"
#define THREADS_NO 8
constexpr size_t elems_per_thread = 100000;
constexpr size_t key_range = elems_per_thread * THREADS_NO * 2;
// This test checks insert_unique method under pressure.
// Threads will try to insert keys in the same order.
// This will force threads to compete intensly with each other.
// Test checks for missing data and changed/overwriten data.
int main() {
memory_check(THREADS_NO, [] {
skiplist_t skiplist;
auto futures = run<std::vector<size_t>>(
THREADS_NO, skiplist, [](auto acc, auto index) {
auto rand = rand_gen(key_range);
size_t downcount = elems_per_thread;
std::vector<size_t> owned;
auto inserter = insert_try<size_t, size_t>(acc, downcount, owned);
for (int i = 0; downcount > 0; i++) {
inserter(i, index);
}
check_present_same(acc, index, owned);
return owned;
});
auto accessor = skiplist.access();
for (auto &owned : collect(futures)) {
check_present_same(accessor, owned);
}
check_size(accessor, THREADS_NO * elems_per_thread);
});
}

View File

@ -0,0 +1,21 @@
#include "common.h"
#define THREADS_NO 8
constexpr size_t elements = 2e6;
// Test for simple memory leaks
int main() {
memory_check(THREADS_NO, [] {
skiplist_t skiplist;
auto futures = run<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);
check_size(skiplist.access(), elements);
});
}

View File

@ -0,0 +1,62 @@
#include "common.h"
#define THREADS_NO 8
constexpr size_t op_per_thread = 1e5;
// Depending on value there is a possiblity of numerical overflow
constexpr size_t max_number = 10;
constexpr size_t no_insert_for_one_delete = 2;
// This test checks remove method under pressure.
// Threads will try to insert and remove keys aproximetly in the same order.
// This will force threads to compete intensly with each other.
// Calls of remove method are interleaved with insert calls.
int main() {
memory_check(THREADS_NO, [] {
skiplist_t skiplist;
auto futures = run<std::pair<long long, long long>>(
THREADS_NO, skiplist, [](auto acc, auto index) {
auto rand_op = rand_gen_bool(no_insert_for_one_delete);
size_t downcount = op_per_thread;
long long sum = 0;
long long count = 0;
for (int i = 0; downcount > 0; i++) {
auto data = i % max_number;
if (rand_op()) {
auto t = i;
while (t > 0) {
if (acc.remove(t)) {
sum -= t % max_number;
downcount--;
count--;
break;
}
t--;
}
} else {
if (acc.insert(i, data).second) {
sum += data;
count++;
downcount--;
}
}
}
return std::pair<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(accessor, counters);
});
}

View File

@ -0,0 +1,46 @@
#include "common.h"
#define THREADS_NO 8
constexpr size_t key_range = 1e5;
constexpr size_t op_per_thread = 1e6;
constexpr size_t no_insert_for_one_delete = 1;
// This test checks remove method under pressure.
// Each thread removes it's own data. So removes are disjoint.
// Calls of remove method are interleaved with insert calls.
int main() {
memory_check(THREADS_NO, [] {
skiplist_t skiplist;
auto futures = run<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);
size_t downcount = op_per_thread;
std::vector<size_t> owned;
auto inserter = insert_try<size_t, size_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(acc, index, owned);
return owned;
});
auto accessor = skiplist.access();
size_t count = 0;
for (auto &owned : collect(futures)) {
check_present_same(accessor, owned);
count += owned.second.size();
}
check_size(accessor, count);
});
}

View File

@ -0,0 +1,60 @@
#include "common.h"
#define THREADS_NO 8
constexpr size_t key_range = 1e5;
constexpr size_t op_per_thread = 1e5;
// Depending on value there is a possiblity of numerical overflow
constexpr size_t max_number = 10;
constexpr size_t no_insert_for_one_delete = 2;
// This test checks remove method under pressure.
// Each thread removes random data. So removes are joint.
// Calls of remove method are interleaved with insert calls.
int main() {
memory_check(THREADS_NO, [] {
skiplist_t skiplist;
auto futures = run<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);
size_t downcount = op_per_thread;
long long sum = 0;
long long count = 0;
do {
auto num = rand();
auto data = num % max_number;
if (rand_op()) {
if (acc.remove(num)) {
sum -= data;
downcount--;
count--;
}
} else {
if (acc.insert(num, data).second) {
sum += data;
downcount--;
count++;
}
}
} while (downcount > 0);
return std::pair<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(accessor, counters);
});
}

View File

@ -0,0 +1,66 @@
#include "common.h"
#define THREADS_NO 8
constexpr size_t key_range = 1e5;
constexpr size_t op_per_thread = 1e6;
// Depending on value there is a possiblity of numerical overflow
constexpr size_t max_number = 10;
constexpr size_t no_find_per_change = 5;
constexpr size_t no_insert_for_one_delete = 1;
// This test simulates behavior of transactions.
// Each thread makes a series of finds interleaved with method which change.
// Exact ratio of finds per change and insert per delete can be regulated with
// no_find_per_change and no_insert_for_one_delete.
int main() {
memory_check(THREADS_NO, [] {
skiplist_t skiplist;
auto futures = run<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(accessor, counters);
});
}

View File

@ -8,62 +8,59 @@ using std::endl;
using skiplist_t = ConcurrentMap<int, int>;
void print_skiplist(const skiplist_t::Accessor &skiplist)
{
cout << "---- skiplist now has: ";
void print_skiplist(const skiplist_t::Accessor &skiplist) {
cout << "---- skiplist now has: ";
for (auto &kv : skiplist)
cout << "(" << kv.first << ", " << kv.second << ") ";
for (auto &kv : skiplist)
cout << "(" << kv.first << ", " << kv.second << ") ";
cout << "----" << endl;
cout << "----" << endl;
}
int main(void)
{
skiplist_t skiplist;
auto accessor = skiplist.access();
int main(void) {
skiplist_t skiplist;
auto accessor = skiplist.access();
// insert 10
permanent_assert(accessor.insert_unique(1, 10).second == true,
"add first element");
// insert 10
permanent_assert(accessor.insert(1, 10).second == true, "add first element");
// try insert 10 again (should fail)
permanent_assert(accessor.insert_unique(1, 10).second == false,
"add the same element, should fail");
// try insert 10 again (should fail)
permanent_assert(accessor.insert(1, 10).second == false,
"add the same element, should fail");
// insert 20
permanent_assert(accessor.insert_unique(2, 20).second == true,
"insert new unique element");
// insert 20
permanent_assert(accessor.insert(2, 20).second == true,
"insert new unique element");
print_skiplist(accessor);
print_skiplist(accessor);
// value at key 3 shouldn't exist
permanent_assert((accessor.find(3) == accessor.end()) == true,
"try to find element which doesn't exist");
// value at key 3 shouldn't exist
permanent_assert((accessor.find(3) == accessor.end()) == true,
"try to find element which doesn't exist");
// value at key 2 should exist
permanent_assert((accessor.find(2) != accessor.end()) == true,
"find iterator");
// value at key 2 should exist
permanent_assert((accessor.find(2) != accessor.end()) == true,
"find iterator");
// at key 2 is 20 (true)
permanent_assert(accessor.find(2)->second == 20, "find element");
// at key 2 is 20 (true)
permanent_assert(accessor.find(2)->second == 20, "find element");
// removed existing (1)
permanent_assert(accessor.remove(1) == true, "try to remove element");
// removed existing (1)
permanent_assert(accessor.remove(1) == true, "try to remove element");
// removed non-existing (3)
permanent_assert(accessor.remove(3) == false,
"try to remove element which doesn't exist");
// removed non-existing (3)
permanent_assert(accessor.remove(3) == false,
"try to remove element which doesn't exist");
// insert (1, 10)
permanent_assert(accessor.insert_unique(1, 10).second == true,
"insert unique element");
// insert (1, 10)
permanent_assert(accessor.insert(1, 10).second == true,
"insert unique element");
// insert (4, 40)
permanent_assert(accessor.insert_unique(4, 40).second == true,
"insert unique element");
// insert (4, 40)
permanent_assert(accessor.insert(4, 40).second == true,
"insert unique element");
print_skiplist(accessor);
print_skiplist(accessor);
return 0;
return 0;
}