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:
parent
f91aa8f49f
commit
a01c26439b
@ -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;
|
||||
}
|
@ -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);
|
||||
}
|
||||
};
|
@ -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;
|
||||
};
|
@ -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;
|
||||
};
|
||||
|
@ -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;
|
||||
};
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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>
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
@ -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) {
|
||||
|
Loading…
Reference in New Issue
Block a user