Added support for multiset.
Minor fixes to tests. MultiIterator & MultiMap work. There are some unfinished optimizations in the code commented. All tests pass. Some common parts from tests have been moved.
This commit is contained in:
parent
bf174644de
commit
e16b576a6e
@ -54,7 +54,7 @@ IncludeCategories:
|
||||
- Regex: '.*'
|
||||
Priority: 1
|
||||
IndentCaseLabels: false
|
||||
IndentWidth: 2
|
||||
IndentWidth: 4
|
||||
IndentWrappedFunctionNames: false
|
||||
KeepEmptyLinesAtTheStartOfBlocks: true
|
||||
MacroBlockBegin: ''
|
||||
|
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,100 +1,45 @@
|
||||
#pragma once
|
||||
|
||||
#include "data_structures/concurrent/common.hpp"
|
||||
#include "data_structures/concurrent/skiplist.hpp"
|
||||
#include "utils/total_ordering.hpp"
|
||||
|
||||
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;
|
||||
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() {}
|
||||
|
||||
friend class Accessor;
|
||||
class Accessor
|
||||
class Accessor : public AccessorBase<item_t>
|
||||
{
|
||||
friend class ConcurrentMap;
|
||||
|
||||
Accessor(list *skiplist) : accessor(skiplist->access()) {}
|
||||
using AccessorBase<item_t>::AccessorBase;
|
||||
|
||||
private:
|
||||
using AccessorBase<item_t>::accessor;
|
||||
|
||||
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));
|
||||
return accessor.insert(item_t(key, data));
|
||||
}
|
||||
|
||||
std::pair<list_it, bool> insert(const K &key, T &&data)
|
||||
{
|
||||
return accessor.insert(Item(key, std::forward<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(std::forward<K>(key), std::forward<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); }
|
||||
@ -104,11 +49,6 @@ public:
|
||||
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); }
|
||||
|
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
@ -10,45 +10,48 @@
|
||||
template <class T, class lock_t = SpinLock>
|
||||
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()
|
||||
{
|
||||
std::vector<T *> local_freelist;
|
||||
|
||||
// take freelist if there is no more threads
|
||||
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()
|
||||
{
|
||||
auto lock = this->acquire_unique();
|
||||
--this->count;
|
||||
if (this->count == 0) {
|
||||
freelist.swap(local_freelist);
|
||||
}
|
||||
}
|
||||
std::vector<T *> local_freelist;
|
||||
|
||||
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++;
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
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;
|
||||
private:
|
||||
FreeList<T> freelist;
|
||||
};
|
||||
|
@ -10,17 +10,17 @@
|
||||
template <class Derived, class lock_t = SpinLock>
|
||||
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;
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
protected:
|
||||
size_t count{0};
|
||||
protected:
|
||||
size_t count{0};
|
||||
};
|
||||
|
@ -2,46 +2,46 @@
|
||||
|
||||
const Vertex::Accessor Vertices::find(tx::Transaction &t, const Id &id)
|
||||
{
|
||||
auto vertices_accessor = vertices.access();
|
||||
auto vertices_iterator = vertices_accessor.find(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();
|
||||
// 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(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));
|
||||
label_index.update(label, std::forward<VertexIndexRecord>(index_record));
|
||||
}
|
||||
|
||||
VertexIndexRecordCollection &Vertices::find_label_index(const Label &label)
|
||||
{
|
||||
return label_index.find(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!
|
||||
|
@ -8,6 +8,9 @@
|
||||
#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"
|
||||
@ -15,133 +18,203 @@
|
||||
|
||||
using std::cout;
|
||||
using std::endl;
|
||||
using skiplist_t = ConcurrentMap<int, int>;
|
||||
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);
|
||||
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; };
|
||||
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.
|
||||
void check_present_same(skiplist_t::Accessor &acc,
|
||||
template <typename S>
|
||||
void check_present_same(typename S::Accessor &acc,
|
||||
std::pair<size_t, std::vector<size_t>> &owned)
|
||||
{
|
||||
check_present_same(acc, owned.first, owned.second);
|
||||
}
|
||||
// Checks for all owned keys if there data is data.
|
||||
void check_present_same(skiplist_t::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");
|
||||
}
|
||||
check_present_same<S>(acc, owned.first, owned.second);
|
||||
}
|
||||
|
||||
// Checks if reported size and traversed size are equal to given size.
|
||||
void check_size(const skiplist_t::Accessor &acc, long long size)
|
||||
template <typename S>
|
||||
void check_size(typename S::Accessor &acc, long long size)
|
||||
{
|
||||
// check size
|
||||
// check size
|
||||
|
||||
permanent_assert(acc.size() == size,
|
||||
"Size should be " << size << ", but size is " << acc.size());
|
||||
permanent_assert(acc.size() == size, "Size should be " << size
|
||||
<< ", but size is "
|
||||
<< acc.size());
|
||||
|
||||
// check count
|
||||
// check count
|
||||
|
||||
size_t iterator_counter = 0;
|
||||
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());
|
||||
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>
|
||||
template <class R, typename S>
|
||||
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)
|
||||
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;
|
||||
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;
|
||||
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;
|
||||
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>
|
||||
auto insert_try(skiplist_t::Accessor &acc, size_t &downcount,
|
||||
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);
|
||||
}
|
||||
};
|
||||
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;
|
||||
// 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];
|
||||
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;
|
||||
while (fgets(line, 128, file) != NULL) {
|
||||
if (strncmp(line, "VmSize:", 7) == 0) {
|
||||
result = parseLine(line);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
fclose(file);
|
||||
return result;
|
||||
fclose(file);
|
||||
return result;
|
||||
}
|
||||
|
||||
// Performs memory check to determine if memory usage before calling given
|
||||
@ -150,11 +223,11 @@ int currently_used_memory()
|
||||
// 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");
|
||||
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");
|
||||
}
|
||||
|
@ -1,70 +0,0 @@
|
||||
#include "common.h"
|
||||
|
||||
#define THREADS_NO 1
|
||||
constexpr size_t elems_per_thread = 16e5;
|
||||
|
||||
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 = 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();
|
||||
}
|
||||
|
||||
// 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 = 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();
|
||||
}
|
||||
|
||||
// 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");
|
||||
}
|
||||
});
|
||||
}
|
@ -9,29 +9,31 @@ constexpr size_t key_range = elems_per_thread * THREADS_NO * 2;
|
||||
// Test checks for missing data and changed/overwriten data.
|
||||
int main()
|
||||
{
|
||||
memory_check(THREADS_NO, [] {
|
||||
skiplist_t skiplist;
|
||||
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);
|
||||
size_t downcount = elems_per_thread;
|
||||
std::vector<size_t> owned;
|
||||
auto inserter = insert_try<size_t, size_t>(acc, downcount, owned);
|
||||
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);
|
||||
do {
|
||||
inserter(rand(), index);
|
||||
} while (downcount > 0);
|
||||
|
||||
check_present_same(acc, index, owned);
|
||||
return owned;
|
||||
});
|
||||
check_present_same<map_t>(acc, index, owned);
|
||||
return owned;
|
||||
});
|
||||
|
||||
auto accessor = skiplist.access();
|
||||
for (auto &owned : collect(futures)) {
|
||||
check_present_same(accessor, owned);
|
||||
}
|
||||
auto accessor = skiplist.access();
|
||||
for (auto &owned : collect(futures)) {
|
||||
check_present_same<map_t>(accessor, owned);
|
||||
}
|
||||
|
||||
check_size(accessor, THREADS_NO * elems_per_thread);
|
||||
});
|
||||
check_size<map_t>(accessor, THREADS_NO * elems_per_thread);
|
||||
check_order<map_t>(accessor);
|
||||
});
|
||||
}
|
||||
|
@ -10,29 +10,31 @@ constexpr size_t key_range = elems_per_thread * THREADS_NO * 2;
|
||||
// Test checks for missing data and changed/overwriten data.
|
||||
int main()
|
||||
{
|
||||
memory_check(THREADS_NO, [] {
|
||||
skiplist_t skiplist;
|
||||
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);
|
||||
size_t downcount = elems_per_thread;
|
||||
std::vector<size_t> owned;
|
||||
auto inserter = insert_try<size_t, size_t>(acc, downcount, owned);
|
||||
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);
|
||||
}
|
||||
for (int i = 0; downcount > 0; i++) {
|
||||
inserter(i, index);
|
||||
}
|
||||
|
||||
check_present_same(acc, index, owned);
|
||||
return owned;
|
||||
});
|
||||
check_present_same<map_t>(acc, index, owned);
|
||||
return owned;
|
||||
});
|
||||
|
||||
auto accessor = skiplist.access();
|
||||
for (auto &owned : collect(futures)) {
|
||||
check_present_same(accessor, owned);
|
||||
}
|
||||
auto accessor = skiplist.access();
|
||||
for (auto &owned : collect(futures)) {
|
||||
check_present_same<map_t>(accessor, owned);
|
||||
}
|
||||
|
||||
check_size(accessor, THREADS_NO * elems_per_thread);
|
||||
});
|
||||
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);
|
||||
}
|
||||
});
|
||||
}
|
@ -8,7 +8,7 @@ constexpr size_t elements = 2e6;
|
||||
int main()
|
||||
{
|
||||
memory_check(THREADS_NO, [] {
|
||||
skiplist_t skiplist;
|
||||
map_t skiplist;
|
||||
|
||||
auto futures = run<size_t>(THREADS_NO, skiplist, [](auto acc, auto index) {
|
||||
for (size_t i = 0; i < elements; i++) {
|
||||
@ -17,6 +17,8 @@ int main()
|
||||
return index;
|
||||
});
|
||||
collect(futures);
|
||||
check_size(skiplist.access(), elements);
|
||||
|
||||
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");
|
||||
|
||||
});
|
||||
}
|
@ -12,52 +12,53 @@ constexpr size_t no_insert_for_one_delete = 2;
|
||||
// Calls of remove method are interleaved with insert calls.
|
||||
int main()
|
||||
{
|
||||
memory_check(THREADS_NO, [] {
|
||||
skiplist_t skiplist;
|
||||
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);
|
||||
size_t downcount = op_per_thread;
|
||||
long long sum = 0;
|
||||
long long count = 0;
|
||||
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;
|
||||
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--;
|
||||
}
|
||||
}
|
||||
}
|
||||
t--;
|
||||
}
|
||||
} else {
|
||||
if (acc.insert(i, data).second) {
|
||||
sum += data;
|
||||
count++;
|
||||
downcount--;
|
||||
}
|
||||
}
|
||||
}
|
||||
return std::pair<long long, long long>(sum, count);
|
||||
});
|
||||
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;
|
||||
}
|
||||
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);
|
||||
});
|
||||
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);
|
||||
});
|
||||
}
|
||||
|
@ -10,38 +10,41 @@ constexpr size_t no_insert_for_one_delete = 1;
|
||||
// Calls of remove method are interleaved with insert calls.
|
||||
int main()
|
||||
{
|
||||
memory_check(THREADS_NO, [] {
|
||||
skiplist_t skiplist;
|
||||
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);
|
||||
size_t downcount = op_per_thread;
|
||||
std::vector<size_t> owned;
|
||||
auto inserter = insert_try<size_t, size_t>(acc, downcount, owned);
|
||||
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);
|
||||
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;
|
||||
});
|
||||
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(accessor, owned);
|
||||
count += owned.second.size();
|
||||
}
|
||||
check_size(accessor, count);
|
||||
});
|
||||
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);
|
||||
});
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
#include "common.h"
|
||||
|
||||
#define THREADS_NO 8
|
||||
constexpr size_t key_range = 1e5;
|
||||
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;
|
||||
@ -12,50 +12,51 @@ constexpr size_t no_insert_for_one_delete = 2;
|
||||
// Calls of remove method are interleaved with insert calls.
|
||||
int main()
|
||||
{
|
||||
memory_check(THREADS_NO, [] {
|
||||
skiplist_t skiplist;
|
||||
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);
|
||||
size_t downcount = op_per_thread;
|
||||
long long sum = 0;
|
||||
long long count = 0;
|
||||
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);
|
||||
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);
|
||||
});
|
||||
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;
|
||||
}
|
||||
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);
|
||||
});
|
||||
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");
|
||||
});
|
||||
}
|
@ -14,54 +14,56 @@ constexpr size_t no_insert_for_one_delete = 1;
|
||||
// no_find_per_change and no_insert_for_one_delete.
|
||||
int main()
|
||||
{
|
||||
memory_check(THREADS_NO, [] {
|
||||
skiplist_t skiplist;
|
||||
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;
|
||||
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--;
|
||||
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");
|
||||
}
|
||||
}
|
||||
} 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);
|
||||
});
|
||||
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;
|
||||
}
|
||||
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);
|
||||
});
|
||||
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);
|
||||
});
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user