Clean SkipList up

Summary:
- Removed a lot of stuff that was incorrect and/or unnecessary
- Fixed const-correctness in the skiplist family

Reviewers: dgleich, teon.banek, buda

Reviewed By: dgleich

Subscribers: pullbot

Differential Revision: https://phabricator.memgraph.io/D1351
This commit is contained in:
florijan 2018-04-09 09:54:32 +02:00
parent f91aa8f49f
commit a01c26439b
23 changed files with 171 additions and 588 deletions

View File

@ -1,184 +0,0 @@
#include <random>
#include <thread>
#include <benchmark/benchmark_api.h>
#include <gflags/gflags.h>
#include <glog/logging.h>
#include "bloom_filter.hpp"
#include "concurrent_bloom_map.hpp"
#include "utils/hashing/fnv64.hpp"
#include "utils/random/random_generator.hpp"
/*
ConcurrentMap Benchmark Test:
- tests time of Insertion, Contain and Delete operations
- benchmarking time per operation
- test run ConcurrentMap with the following keys and values:
- <int,int>
- <int, string>
- <string, int>
- <string, string>
*/
using utils::random::NumberGenerator;
using utils::random::PairGenerator;
using utils::random::StringGenerator;
using StringHashFunction = std::function<uint64_t(const std::string &)>;
using IntegerGenerator = NumberGenerator<std::uniform_int_distribution<int>,
std::default_random_engine, int>;
DEFINE_int32(start, 0, "Range start");
DEFINE_int32(end, 1000000000, "Range end");
DEFINE_int32(threads, 1, "Number of threads");
DEFINE_int32(string_length, 128, "String length");
// Global arguments
int MAX_ELEMENTS = 1 << 18, MULTIPLIER = 2;
int THREADS, RANGE_START, RANGE_END, STRING_LENGTH;
/*
ConcurrentMap Insertion Benchmark Test
*/
template <class K, class V, class F>
static void InsertValue(benchmark::State &state,
ConcurrentBloomMap<K, V, F> *map,
const std::vector<std::pair<K, V>> &elements) {
while (state.KeepRunning()) {
for (int start = 0; start < state.range(0); start++) {
map->insert(elements[start].first, elements[start].second);
}
}
state.SetComplexityN(state.range(0));
}
/*
ConcurrentMap Contains Benchmark Test
*/
template <class K, class V, class F>
static void ContainsValue(benchmark::State &state,
ConcurrentBloomMap<K, V, F> *map,
const std::vector<std::pair<K, V>> elements) {
while (state.KeepRunning()) {
for (int start = 0; start < state.range(0); start++) {
map->contains(elements[start].first);
}
}
state.SetComplexityN(state.range(0));
}
auto BM_InsertValue = [](benchmark::State &state, auto *map, auto &elements) {
InsertValue(state, map, elements);
};
auto BM_ContainsValue = [](benchmark::State &state, auto *map, auto elements) {
ContainsValue(state, map, elements);
};
/*
Commandline Argument Parsing
Arguments:
* Integer Range Minimum
-start number
* Integer Range Maximum
- end number
* Number of threads
- threads number
* Random String lenght
-string-length number
*/
void parse_arguments() {
RANGE_START = FLAGS_start;
RANGE_END = FLAGS_end;
THREADS = std::min(FLAGS_threads,
static_cast<int>(std::thread::hardware_concurrency()));
STRING_LENGTH = FLAGS_string_length;
}
int main(int argc, char **argv) {
benchmark::Initialize(&argc, argv);
parse_arguments();
google::InitGoogleLogging(argv[0]);
StringGenerator sg(STRING_LENGTH);
IntegerGenerator ig(RANGE_START, RANGE_END);
/*
Creates RandomGenerators, ConcurentMaps and Random Element Vectors for the
following use cases:
Map elements contain keys and value for:
<int, int>,
<int, string>
<string, int>
<string, string>
*/
// random generators for tests
PairGenerator<IntegerGenerator, IntegerGenerator> piig(&ig, &ig);
PairGenerator<StringGenerator, StringGenerator> pssg(&sg, &sg);
PairGenerator<StringGenerator, IntegerGenerator> psig(&sg, &ig);
PairGenerator<IntegerGenerator, StringGenerator> pisg(&ig, &sg);
StringHashFunction hash1 = fnv64;
StringHashFunction hash2 = fnv1a64;
std::vector<StringHashFunction> funcs = {hash1, hash2};
BloomFilter<std::string, 128> bloom_filter_(funcs);
// maps used for testing
// ConcurrentBloomMap<int, int> ii_map;
// ConcurrentBloomMap<int, std::string> is_map;
using Filter = BloomFilter<std::string, 128>;
ConcurrentBloomMap<std::string, int, Filter> si_map(bloom_filter_);
ConcurrentBloomMap<std::string, std::string, Filter> ss_map(bloom_filter_);
// random elements for testing
// auto ii_elems = utils::random::generate_vector(piig, MAX_ELEMENTS);
// auto is_elems = utils::random::generate_vector(pisg, MAX_ELEMENTS);
auto si_elems = utils::random::generate_vector(psig, MAX_ELEMENTS);
auto ss_elems = utils::random::generate_vector(pssg, MAX_ELEMENTS);
/* insertion Tests */
benchmark::RegisterBenchmark("InsertValue[String, Int]", BM_InsertValue,
&si_map, si_elems)
->RangeMultiplier(MULTIPLIER)
->Range(1, MAX_ELEMENTS)
->Complexity(benchmark::oN)
->Threads(THREADS);
benchmark::RegisterBenchmark("InsertValue[String, String]", BM_InsertValue,
&ss_map, ss_elems)
->RangeMultiplier(MULTIPLIER)
->Range(1, MAX_ELEMENTS)
->Complexity(benchmark::oN)
->Threads(THREADS);
// Contains Benchmark Tests
benchmark::RegisterBenchmark("ContainsValue[String, Int]", BM_ContainsValue,
&si_map, si_elems)
->RangeMultiplier(MULTIPLIER)
->Range(1, MAX_ELEMENTS)
->Complexity(benchmark::oN)
->Threads(THREADS);
benchmark::RegisterBenchmark("ContainsValue[String, String]",
BM_ContainsValue, &ss_map, ss_elems)
->RangeMultiplier(MULTIPLIER)
->Range(1, MAX_ELEMENTS)
->Complexity(benchmark::oN)
->Threads(THREADS);
benchmark::RunSpecifiedBenchmarks();
return 0;
}

View File

@ -1,35 +0,0 @@
#pragma once
#include "data_structures/concurrent/common.hpp"
#include "data_structures/concurrent/concurrent_map.hpp"
#include "data_structures/concurrent/skiplist.hpp"
using std::pair;
template <class Key, class Value, class BloomFilter>
class ConcurrentBloomMap {
using item_t = Item<Key, Value>;
using list_it = typename SkipList<item_t>::Iterator;
private:
ConcurrentMap<Key, Value> map_;
BloomFilter filter_;
public:
ConcurrentBloomMap(BloomFilter filter) : filter_(filter) {}
std::pair<list_it, bool> insert(const Key &key, const Value &data) {
filter_.insert(key);
auto accessor = std::move(map_.access());
return accessor.insert(key, data);
}
bool contains(const Key &key) {
if (!filter_.contains(key)) return false;
auto accessor = map_.access();
return accessor.contains(key);
}
};

View File

@ -1,81 +0,0 @@
#pragma once
#include "data_structures/concurrent/skiplist.hpp"
#include "utils/total_ordering.hpp"
using std::pair;
// Item stored in skiplist. Used by ConcurrentMap to
// store key and value but to make ordering on keys.
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) {
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;
}
};
// Common base for accessor of all derived containers(ConcurrentMap,
// ConcurrentSet, ...) from SkipList.
template <typename T, bool IsConst>
class AccessorBase {
typedef
typename std::conditional<IsConst, const SkipList<T>, SkipList<T>>::type
list;
typedef typename SkipList<T>::Iterator list_it;
typedef typename SkipList<T>::ConstIterator list_it_con;
protected:
explicit AccessorBase(list *skiplist) : accessor(skiplist->access()) {}
public:
AccessorBase(const AccessorBase &) = delete;
AccessorBase(AccessorBase &&other) : accessor(std::move(other.accessor)) {}
~AccessorBase() {}
size_t size() { return accessor.size(); };
auto begin() { return accessor.begin(); }
auto begin() const { return accessor.cbegin(); }
list_it_con cbegin() const { return accessor.cbegin(); }
auto end() { return accessor.end(); }
auto end() const { return accessor.cend(); }
list_it_con cend() const { return accessor.cend(); }
size_t size() const { return accessor.size(); }
protected:
decltype(std::declval<list>().access()) accessor;
};

View File

@ -1,80 +1,89 @@
#pragma once
#include "data_structures/concurrent/common.hpp"
#include "data_structures/concurrent/skiplist.hpp"
#include "utils/total_ordering.hpp"
using std::pair;
/**
* Multi thread safe map based on skiplist.
*
* @tparam K is a type of key.
* @tparam T is a type of data.
*/
template <typename K, typename T>
/// Thread-safe map intended for high concurrent throughput.
///
/// @tparam TKey is a type of key.
/// @tparam TValue is a type of data.
template <typename TKey, typename TValue>
class ConcurrentMap {
typedef Item<K, T> item_t;
typedef SkipList<item_t> list;
typedef typename SkipList<item_t>::Iterator list_it;
typedef typename SkipList<item_t>::ConstIterator list_it_con;
/// At item in the concurrent map. A pair of <TKey, TValue> that compares on
/// the first value (key). Comparable to another Item, or only to the TKey.
class Item : public TotalOrdering<Item>,
public TotalOrdering<TKey, Item>,
public TotalOrdering<Item, TKey>,
public std::pair<const TKey, TValue> {
public:
using std::pair<const TKey, TValue>::pair;
friend constexpr bool operator<(const Item &lhs, const Item &rhs) {
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 TKey &lhs, const Item &rhs) {
return lhs < rhs.first;
}
friend constexpr bool operator==(const TKey &lhs, const Item &rhs) {
return lhs == rhs.first;
}
friend constexpr bool operator<(const Item &lhs, const TKey &rhs) {
return lhs.first < rhs;
}
friend constexpr bool operator==(const Item &lhs, const TKey &rhs) {
return lhs.first == rhs;
}
};
using Iterator = typename SkipList<Item>::Iterator;
public:
ConcurrentMap() {}
template <bool IsConst = false>
class Accessor : public AccessorBase<item_t, IsConst> {
template <typename TSkipList>
class Accessor : public SkipList<Item>::template Accessor<TSkipList> {
public:
friend class ConcurrentMap;
using AccessorBase<item_t, IsConst>::AccessorBase;
using SkipList<Item>::template Accessor<TSkipList>::Accessor;
private:
using AccessorBase<item_t, IsConst>::accessor;
public:
std::pair<list_it, bool> insert(const K &key, const T &data) {
return accessor.insert(item_t(key, data));
std::pair<Iterator, bool> insert(const TKey &key, const TValue &data) {
return SkipList<Item>::template Accessor<TSkipList>::insert(
Item(key, data));
}
std::pair<list_it, bool> insert(const K &key, T &&data) {
return accessor.insert(item_t(key, std::move(data)));
std::pair<Iterator, bool> insert(const TKey &key, TValue &&data) {
return SkipList<Item>::template Accessor<TSkipList>::insert(
Item(key, std::move(data)));
}
std::pair<list_it, bool> insert(K &&key, T &&data) {
return accessor.insert(
item_t(std::forward<K>(key), std::forward<T>(data)));
std::pair<Iterator, bool> insert(TKey &&key, TValue &&data) {
return SkipList<Item>::template Accessor<TSkipList>::insert(
Item(std::forward<TKey>(key), std::forward<TValue>(data)));
}
template <class... Args1, class... Args2>
std::pair<list_it, bool> emplace(const K &key,
std::pair<Iterator, bool> emplace(const TKey &key,
std::tuple<Args1...> first_args,
std::tuple<Args2...> second_args) {
return accessor.emplace(key, std::piecewise_construct,
return SkipList<Item>::template Accessor<TSkipList>::emplace(
key, std::piecewise_construct,
std::forward<std::tuple<Args1...>>(first_args),
std::forward<std::tuple<Args2...>>(second_args));
}
list_it_con find(const K &key) const { return accessor.find(key); }
list_it find(const K &key) { return accessor.find(key); }
// Returns iterator to item or first larger if it doesn't exist.
list_it_con find_or_larger(const T &item) const {
return accessor.find_or_larger(item);
}
// Returns iterator to item or first larger if it doesn't exist.
list_it find_or_larger(const T &item) {
return accessor.find_or_larger(item);
}
bool contains(const K &key) const { return this->find(key) != this->end(); }
bool remove(const K &key) { return accessor.remove(key); }
};
auto access() { return Accessor<false>(&skiplist); }
auto access() const { return Accessor<true>(&skiplist); }
auto access() { return Accessor<SkipList<Item>>(&skiplist); }
auto access() const { return Accessor<const SkipList<Item>>(&skiplist); }
private:
list skiplist;
SkipList<Item> skiplist;
};

View File

@ -1,69 +0,0 @@
#pragma once
#include "data_structures/concurrent/common.hpp"
#include "data_structures/concurrent/skiplist.hpp"
// Multi thread safe set based on skiplist.
// T - type of data.
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() {}
template <bool IsConst = false>
class Accessor : public AccessorBase<T, IsConst> {
friend class ConcurrentSet;
using AccessorBase<T, IsConst>::AccessorBase;
private:
using AccessorBase<T, IsConst>::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::move(item));
}
list_it_con find(const T &item) const { return accessor.find(item); }
list_it find(const T &item) { return accessor.find(item); }
// Returns iterator to item or first larger if it doesn't exist.
template <class K>
list_it_con find_or_larger(const K &item) const {
return accessor.find_or_larger(item);
}
// Returns iterator to item or first larger if it doesn't exist.
template <class K>
list_it find_or_larger(const K &item) {
return accessor.find_or_larger(item);
}
// Returns iterator to item or first larger if it doesn't exist.
template <class K>
list_it_con cfind_or_larger(const K &item) {
return accessor.template find_or_larger<list_it_con, K>(item);
}
bool contains(const T &item) const {
return this->find(item) != this->end();
}
bool remove(const T &item) { return accessor.remove(item); }
};
auto access() { return Accessor<false>(&skiplist); }
auto access() const { return Accessor<true>(&skiplist); }
private:
list skiplist;
};

View File

@ -456,6 +456,13 @@ class SkipList : private Lockable<lock_t> {
class Accessor {
friend class SkipList;
// The iterator type that will be return from non-const iterator returning
// methods (begin, end, find...). Must be ConstIterator if the SkipList is
// const, even if the Accessor is not const.
using IteratorT = typename std::conditional<std::is_const<TSkipList>::value,
ConstIterator, Iterator>::type;
protected:
explicit Accessor(TSkipList *skiplist)
: skiplist(skiplist), status_(skiplist->gc.CreateNewAccessor()) {
DCHECK(skiplist != nullptr) << "Skiplist is nullptr.";
@ -476,16 +483,15 @@ class SkipList : private Lockable<lock_t> {
status_.alive_ = false;
}
auto begin() const { return skiplist->begin(); }
IteratorT begin() { return skiplist->begin(); }
ConstIterator begin() const { return skiplist->cbegin(); }
ConstIterator cbegin() const { return skiplist->cbegin(); }
auto end() const { return skiplist->end(); }
IteratorT end() { return skiplist->end(); }
ConstIterator end() const { return skiplist->cend(); }
ConstIterator cend() const { return skiplist->cend(); }
ReverseIterator rbegin() { return skiplist->rbegin(); }
ReverseIterator rend() { return skiplist->rend(); }
std::pair<Iterator, bool> insert(const T &item) {
@ -502,13 +508,13 @@ class SkipList : private Lockable<lock_t> {
}
template <class K>
ConstIterator find(const K &item) const {
return static_cast<const SkipList &>(*skiplist).find(item);
IteratorT find(const K &item) {
return skiplist->find(item);
}
template <class K>
Iterator find(const K &item) {
return skiplist->find(item);
ConstIterator find(const K &item) const {
return static_cast<const SkipList &>(*skiplist).find(item);
}
template <class K>
@ -524,7 +530,7 @@ class SkipList : private Lockable<lock_t> {
* @tparam TItem item type
*/
template <class TItem>
Iterator find_or_larger(const TItem &item) {
IteratorT find_or_larger(const TItem &item) {
return skiplist->template find_or_larger<Iterator, TItem>(item);
}

View File

@ -3,7 +3,7 @@
#include <experimental/optional>
#include "data_structures/concurrent/concurrent_map.hpp"
#include "data_structures/concurrent/concurrent_set.hpp"
#include "data_structures/concurrent/skiplist.hpp"
#include "database/indexes/index_common.hpp"
#include "mvcc/version_list.hpp"
#include "storage/edge.hpp"
@ -531,6 +531,6 @@ class LabelPropertyIndex {
}
ConcurrentMap<Key, std::unique_ptr<SkipList<IndexEntry>>> indices_;
ConcurrentSet<Key> ready_for_use_;
SkipList<Key> ready_for_use_;
};
} // namespace database

View File

@ -3,7 +3,7 @@
#include <experimental/filesystem>
#include "data_structures/concurrent/concurrent_map.hpp"
#include "data_structures/concurrent/concurrent_set.hpp"
#include "data_structures/concurrent/skiplist.hpp"
#include "database/indexes/key_index.hpp"
#include "database/indexes/label_property_index.hpp"
#include "mvcc/version_list.hpp"
@ -54,9 +54,7 @@ class Storage {
template <typename TRecord>
mvcc::VersionList<TRecord> *LocalAddress(gid::Gid gid) const {
const auto &map = GetMap<TRecord>();
// TODO the access must be explicitly const due to a bug in in
// ConcurrentMap::Access::find
const auto access = map.access();
auto access = map.access();
auto found = access.find(gid);
CHECK(found != access.end())
<< "Failed to find "
@ -107,7 +105,7 @@ class Storage {
LabelPropertyIndex label_property_index_;
// Set of transactions ids which are building indexes currently
ConcurrentSet<tx::transaction_id_t> index_build_tx_in_progress_;
SkipList<tx::transaction_id_t> index_build_tx_in_progress_;
/// Gets the Vertex/Edge main storage map.
template <typename TRecord>

View File

@ -35,7 +35,7 @@ class SingleNodeConcurrentIdMapper : public ConcurrentIdMapper<TId> {
}
const std::string &id_to_value(const TId &id) override {
const auto id_to_value_acc = id_to_value_.access();
auto id_to_value_acc = id_to_value_.access();
auto result = id_to_value_acc.find(id);
DCHECK(result != id_to_value_acc.end());
return result->second;

View File

@ -16,10 +16,9 @@ namespace {
// Finds lock cycle that start transaction is a part of and returns id of oldest
// transaction in that cycle. If start transaction is not in a cycle nullopt is
// returned.
template <typename TAccessor>
std::experimental::optional<tx::transaction_id_t> FindOldestTxInLockCycle(
tx::transaction_id_t start,
ConcurrentMap<tx::transaction_id_t, tx::transaction_id_t>::Accessor<>
&graph_accessor) {
tx::transaction_id_t start, TAccessor &graph_accessor) {
std::vector<tx::transaction_id_t> path;
std::unordered_set<tx::transaction_id_t> visited;

View File

@ -8,7 +8,6 @@
#include "data_structures/bitset/dynamic_bitset.hpp"
#include "data_structures/concurrent/concurrent_map.hpp"
#include "data_structures/concurrent/concurrent_set.hpp"
#include "data_structures/concurrent/skiplist.hpp"
// NOTE: this file is highly coupled to data_structures
@ -20,7 +19,6 @@ constexpr int max_no_threads = 8;
using std::cout;
using std::endl;
using map_t = ConcurrentMap<int, int>;
using set_t = ConcurrentSet<int>;
using namespace std::chrono_literals;
@ -37,29 +35,29 @@ auto rand_gen_bool(size_t 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::template Accessor<> &acc, size_t data,
// Checks for all owned keys if their data is data.
template <typename TAccessor>
void check_present_same(TAccessor &acc, size_t data,
std::vector<size_t> &owned) {
for (auto num : owned) {
CHECK(acc.find(num)->second == data) << "My data is present and my";
}
}
// Checks for all owned.second keys if there data is owned.first.
template <typename S>
void check_present_same(typename S::template Accessor<> &acc,
// Checks for all owned.second keys if their data is owned.first.
template <typename TAccessor>
void check_present_same(TAccessor &acc,
std::pair<size_t, std::vector<size_t>> &owned) {
check_present_same<S>(acc, owned.first, owned.second);
check_present_same(acc, owned.first, owned.second);
}
// Checks if reported size and traversed size are equal to given size.
template <typename S>
void check_size_list(S &acc, long long size) {
template <typename TAccessor>
void check_size_list(TAccessor &acc, long long size) {
// check size
CHECK(acc.size() == size)
<< "Size should be " << size << ", but size is " << acc.size();
CHECK(acc.size() == size) << "Size should be " << size << ", but size is "
<< acc.size();
// check count
@ -72,12 +70,12 @@ void check_size_list(S &acc, long long size) {
<< "Iterator count should be " << size << ", but size is "
<< iterator_counter;
}
template <typename S>
void check_size(typename S::template Accessor<> &acc, long long size) {
template <typename TAccessor>
void check_size(TAccessor &acc, long long size) {
// check size
CHECK(acc.size() == size)
<< "Size should be " << size << ", but size is " << acc.size();
CHECK(acc.size() == size) << "Size should be " << size << ", but size is "
<< acc.size();
// check count
@ -92,8 +90,8 @@ void check_size(typename S::template Accessor<> &acc, long long size) {
}
// Checks if order in list is maintened. It expects map
template <typename S>
void check_order(typename S::template Accessor<> &acc) {
template <typename TAccessor>
void check_order(TAccessor &acc) {
if (acc.begin() != acc.end()) {
auto last = acc.begin()->first;
for (auto elem : acc) {
@ -119,8 +117,7 @@ void check_set(DynamicBitset<> &db, std::vector<bool> &set) {
}
// Runs given function in threads_no threads and returns vector of futures for
// there
// results.
// their results.
template <class R, typename S, class FunT>
std::vector<std::future<std::pair<size_t, R>>> run(size_t threads_no,
S &skiplist, FunT f) {
@ -137,8 +134,7 @@ std::vector<std::future<std::pair<size_t, R>>> run(size_t threads_no,
}
// Runs given function in threads_no threads and returns vector of futures for
// there
// results.
// their results.
template <class R>
std::vector<std::future<std::pair<size_t, R>>> run(size_t threads_no,
std::function<R(size_t)> f) {
@ -175,16 +171,3 @@ std::vector<bool> collect_set(
}
return set;
}
// Returns object which tracs in owned which (key,data) where added and
// downcounts.
template <class K, class D, class S>
auto insert_try(typename S::template 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);
}
};
}

View File

@ -9,7 +9,7 @@ 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(int argc, char **argv) {
int main(int, char **argv) {
google::InitGoogleLogging(argv[0]);
map_t skiplist;
@ -18,22 +18,24 @@ int main(int argc, char **argv) {
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);
auto key = rand();
if (acc.insert(key, index).second) {
downcount--;
owned.push_back(key);
}
} while (downcount > 0);
check_present_same<map_t>(acc, index, owned);
check_present_same(acc, index, owned);
return owned;
});
auto accessor = skiplist.access();
for (auto &owned : collect(futures)) {
check_present_same<map_t>(accessor, owned);
check_present_same(accessor, owned);
}
check_size<map_t>(accessor, THREADS_NO * elems_per_thread);
check_order<map_t>(accessor);
check_size(accessor, THREADS_NO * elems_per_thread);
check_order(accessor);
}

View File

@ -9,7 +9,7 @@ constexpr size_t elems_per_thread = 100000;
// 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(int argc, char **argv) {
int main(int, char **argv) {
google::InitGoogleLogging(argv[0]);
map_t skiplist;
@ -17,24 +17,25 @@ int main(int argc, char **argv) {
run<std::vector<size_t>>(THREADS_NO, skiplist, [](auto acc, auto index) {
long long downcount = elems_per_thread;
std::vector<size_t> owned;
auto inserter =
insert_try<size_t, size_t, map_t>(acc, downcount, owned);
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wfor-loop-analysis"
for (int i = 0; downcount > 0; i++) {
inserter(i, index);
if (acc.insert(i, index).second) {
downcount--;
owned.push_back(i);
}
}
#pragma GCC diagnostic pop
check_present_same<map_t>(acc, index, owned);
check_present_same(acc, index, owned);
return owned;
});
auto accessor = skiplist.access();
for (auto &owned : collect(futures)) {
check_present_same<map_t>(accessor, owned);
check_present_same(accessor, owned);
}
check_size<map_t>(accessor, THREADS_NO * elems_per_thread);
check_order<map_t>(accessor);
check_size(accessor, THREADS_NO * elems_per_thread);
check_order(accessor);
}

View File

@ -53,8 +53,8 @@ int main(int, char **argv) {
// check size
{
auto accessor = skiplist.access();
CHECK(accessor.size() == 0)
<< "Size should be 0, but size is " << accessor.size();
CHECK(accessor.size() == 0) << "Size should be 0, but size is "
<< accessor.size();
}
// check count
@ -70,7 +70,7 @@ int main(int, char **argv) {
{
auto accessor = skiplist.access();
check_order<map_t>(accessor);
check_order(accessor);
}
return 0;
}

View File

@ -7,7 +7,7 @@ constexpr size_t elements = 2e6;
* Put elements number of elements in the skiplist per each thread and see
* is there any memory leak
*/
int main(int argc, char **argv) {
int main(int, char **argv) {
google::InitGoogleLogging(argv[0]);
map_t skiplist;
@ -20,5 +20,5 @@ int main(int argc, char **argv) {
collect(futures);
auto accessor = skiplist.access();
check_size<map_t>(accessor, elements);
check_size(accessor, elements);
}

View File

@ -12,7 +12,7 @@ constexpr size_t no_insert_for_one_delete = 2;
// 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(int argc, char **argv) {
int main(int, char **argv) {
google::InitGoogleLogging(argv[0]);
map_t skiplist;
@ -59,7 +59,7 @@ int main(int argc, char **argv) {
sums -= e.second;
}
CHECK(sums == 0) << "Aproximetly Same values are present";
check_size<map_t>(accessor, counters);
check_order<map_t>(accessor);
check_size(accessor, counters);
check_order(accessor);
return 0;
}

View File

@ -10,7 +10,7 @@ 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(int argc, char **argv) {
int main(int, char **argv) {
google::InitGoogleLogging(argv[0]);
map_t skiplist;
@ -20,8 +20,6 @@ int main(int argc, char **argv) {
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()) {
@ -30,21 +28,25 @@ int main(int argc, char **argv) {
owned.erase(owned.begin() + rem);
downcount--;
} else {
inserter(rand(), index);
auto key = rand();
if (acc.insert(key, index).second) {
downcount--;
owned.push_back(key);
}
}
} while (downcount > 0);
check_present_same<map_t>(acc, index, owned);
check_present_same(acc, index, owned);
return owned;
});
auto accessor = skiplist.access();
size_t count = 0;
for (auto &owned : collect(futures)) {
check_present_same<map_t>(accessor, owned);
check_present_same(accessor, owned);
count += owned.second.size();
}
check_size<map_t>(accessor, count);
check_order<map_t>(accessor);
check_size(accessor, count);
check_order(accessor);
return 0;
}

View File

@ -12,7 +12,7 @@ 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(int argc, char **argv) {
int main(int, char **argv) {
google::InitGoogleLogging(argv[0]);
map_t skiplist;
@ -57,7 +57,7 @@ int main(int argc, char **argv) {
sums -= e.second;
}
CHECK(sums == 0) << "Aproximetly Same values are present";
check_size<map_t>(accessor, counters);
check_order<map_t>(accessor);
check_size(accessor, counters);
check_order(accessor);
return 0;
}

View File

@ -10,9 +10,9 @@ 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(int argc, char **argv) {
int main(int, char **argv) {
google::InitGoogleLogging(argv[0]);
ConcurrentSet<std::string> skiplist;
SkipList<std::string> skiplist;
auto futures =
run<std::vector<long>>(THREADS_NO, skiplist, [](auto acc, auto index) {

View File

@ -14,7 +14,7 @@ constexpr size_t no_insert_for_one_delete = 1;
// 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(int argc, char **argv) {
int main(int, char **argv) {
google::InitGoogleLogging(argv[0]);
map_t skiplist;
@ -63,6 +63,6 @@ int main(int argc, char **argv) {
sums -= e.second;
}
CHECK(sums == 0) << "Same values aren't present";
check_size<map_t>(accessor, counters);
check_order<map_t>(accessor);
check_size(accessor, counters);
check_order(accessor);
}

View File

@ -8,9 +8,10 @@
using concurrent_map_t = ConcurrentMap<int, int>;
void print_skiplist(const concurrent_map_t::Accessor<false> &map) {
template <typename TAccessor>
void print_skiplist(const TAccessor &access) {
DLOG(INFO) << "Map now has: ";
for (auto &kv : map)
for (auto &kv : access)
DLOG(INFO) << fmt::format(" ({}, {})", kv.first, kv.second);
}
@ -53,9 +54,18 @@ TEST(ConcurrentMapSkiplist, Mix) {
print_skiplist(accessor);
}
int main(int argc, char **argv) {
google::InitGoogleLogging(argv[0]);
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
TEST(ConcurrentMapSkiplist, ConstFind) {
ConcurrentMap<int, int> map;
{
auto access = map.access();
for (int i = 0; i < 10; ++i) access.insert(i, i);
}
{
const auto &const_map = map;
auto access = const_map.access();
auto it = access.find(4);
EXPECT_NE(it, access.end());
it = access.find(12);
EXPECT_EQ(it, access.end());
}
}

View File

@ -1,58 +0,0 @@
#include <iostream>
#include <glog/logging.h>
#include <gtest/gtest.h>
#include "data_structures/concurrent/concurrent_set.hpp"
void print_skiplist(const ConcurrentSet<int>::Accessor<false> &skiplist) {
DLOG(INFO) << "Skiplist set now has:";
for (auto &item : skiplist) DLOG(INFO) << item;
}
TEST(ConcurrentSet, Mix) {
ConcurrentSet<int> set;
auto accessor = set.access();
// added non-existing 1? (true)
EXPECT_TRUE(accessor.insert(1).second);
// added already existing 1? (false)
EXPECT_FALSE(accessor.insert(1).second);
// added non-existing 2? (true)
EXPECT_TRUE(accessor.insert(2).second);
// item 3 doesn't exist? (true)
EXPECT_EQ(accessor.find(3), accessor.end());
// item 3 exists? (false)
EXPECT_FALSE(accessor.contains(3));
// item 2 exists? (true)
EXPECT_NE(accessor.find(2), accessor.end());
// find item 2
EXPECT_EQ(*accessor.find(2), 2);
// removed existing 1? (true)
EXPECT_TRUE(accessor.remove(1));
// try to remove non existing element
EXPECT_FALSE(accessor.remove(3));
// add 1 again
EXPECT_TRUE(accessor.insert(1).second);
// add 4
EXPECT_TRUE(accessor.insert(4).second);
print_skiplist(accessor);
}
int main(int argc, char **argv) {
google::InitGoogleLogging(argv[0]);
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}

View File

@ -3,7 +3,7 @@
#include <thread>
#include <vector>
#include "data_structures/concurrent/concurrent_set.hpp"
#include "data_structures/concurrent/skiplist.hpp"
#include "transactions/engine_single_node.hpp"
#include "transactions/transaction.hpp"
@ -53,7 +53,7 @@ TEST(Engine, Advance) {
TEST(Engine, ConcurrentBegin) {
SingleNodeEngine engine;
std::vector<std::thread> threads;
ConcurrentSet<transaction_id_t> tx_ids;
SkipList<transaction_id_t> tx_ids;
for (int i = 0; i < 10; ++i) {
threads.emplace_back([&engine, accessor = tx_ids.access() ]() mutable {
for (int j = 0; j < 100; ++j) {