Concurrent data structures -> multi_X stuff removed. Skiplist cleanup.
Reviewers: buda Reviewed By: buda Subscribers: pullbot Differential Revision: https://phabricator.memgraph.io/D495
This commit is contained in:
parent
a01cf408bf
commit
492b4919f1
@ -5,7 +5,7 @@
|
||||
|
||||
using std::pair;
|
||||
|
||||
// Item stored in skiplist. Used by ConcurrentMap and ConcurrentMultiMap to
|
||||
// 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>>,
|
||||
@ -72,16 +72,6 @@ class AccessorBase {
|
||||
|
||||
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:
|
||||
|
@ -1,74 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "data_structures/concurrent/skiplist.hpp"
|
||||
#include "utils/total_ordering.hpp"
|
||||
|
||||
using std::pair;
|
||||
|
||||
/**
|
||||
* Multi thread safe multi map based on skiplist.
|
||||
*
|
||||
* @tparam K is a type of key.
|
||||
* @tparam T is a type of data.
|
||||
*/
|
||||
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); }
|
||||
|
||||
// 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); }
|
||||
};
|
||||
|
||||
Accessor access() { return Accessor(&skiplist); }
|
||||
|
||||
const Accessor access() const { return Accessor(&skiplist); }
|
||||
|
||||
private:
|
||||
list skiplist;
|
||||
};
|
@ -1,58 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "data_structures/concurrent/skiplist.hpp"
|
||||
|
||||
// Multi thread safe multiset based on skiplist.
|
||||
// T - type of data.
|
||||
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); }
|
||||
|
||||
// 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 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;
|
||||
};
|
@ -121,6 +121,11 @@ class SkipList : private Lockable<lock_t> {
|
||||
|
||||
bool is_fully_linked() const { return flags.load() & FULLY_LINKED; }
|
||||
|
||||
/** Waits until the these flags don't get the "fully linked" status. */
|
||||
void wait_fully_linked() const {
|
||||
while (!is_fully_linked()) usleep(250);
|
||||
}
|
||||
|
||||
void set_fully_linked() { flags.fetch_or(FULLY_LINKED); }
|
||||
|
||||
private:
|
||||
@ -144,20 +149,22 @@ class SkipList : private Lockable<lock_t> {
|
||||
return new (allocate(height)) Node(height);
|
||||
}
|
||||
|
||||
static Node *create(const T &item, uint8_t height) {
|
||||
/**
|
||||
* Creates a new node for the given item.
|
||||
*
|
||||
* @param item - The item that is being inserted into the skiplist.
|
||||
* @param height - Node height.
|
||||
* @tparam TItem - This function is templatized so it can accept
|
||||
* a universal reference to the item. TItem should be the same
|
||||
* as T.
|
||||
*/
|
||||
template <typename TItem>
|
||||
static Node *create(TItem &&item, uint8_t height) {
|
||||
auto node = allocate(height);
|
||||
|
||||
// we have raw memory and we need to construct an object
|
||||
// of type Node on it
|
||||
return new (node) Node(item, height);
|
||||
}
|
||||
|
||||
static Node *create(T &&item, uint8_t height) {
|
||||
auto node = allocate(height);
|
||||
|
||||
// we have raw memory and we need to construct an object
|
||||
// of type Node on it
|
||||
return new (node) Node(std::move(item), height);
|
||||
return new (node) Node(std::forward<TItem>(item), height);
|
||||
}
|
||||
|
||||
template <class... Args>
|
||||
@ -169,6 +176,11 @@ class SkipList : private Lockable<lock_t> {
|
||||
return new (node) Node(height, std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
bool ok_delete(int level) const {
|
||||
return flags.is_fully_linked() && height - 1 == level &&
|
||||
!flags.is_marked();
|
||||
}
|
||||
|
||||
static void destroy(Node *node) {
|
||||
node->~Node();
|
||||
std::free(node);
|
||||
@ -422,146 +434,6 @@ class SkipList : private Lockable<lock_t> {
|
||||
Node *preds_[H];
|
||||
};
|
||||
|
||||
template <class K>
|
||||
class MultiIterator : public Crtp<MultiIterator<K>> {
|
||||
friend class SkipList;
|
||||
|
||||
MultiIterator(const K &data) : data(data), skiplist(nullptr) {
|
||||
succs[0] = nullptr;
|
||||
};
|
||||
MultiIterator(SkipList *skiplist, const K &data)
|
||||
: data(data), skiplist(skiplist) {
|
||||
// We must find the first element with K key.
|
||||
// All of logic in this loop was taken from insert method.
|
||||
while (true) {
|
||||
auto level = find_path(skiplist, H - 1, data, preds, succs);
|
||||
if (level == -1) {
|
||||
succs[0] = nullptr;
|
||||
} else if (succs[0] != succs[succs[0]->height - 1] ||
|
||||
!succs[level]->flags.is_fully_linked()) {
|
||||
usleep(250);
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
MultiIterator(const MultiIterator &) = default;
|
||||
|
||||
T &operator*() {
|
||||
debug_assert(succs[0] != nullptr, "Node is nullptr.");
|
||||
return succs[0]->value();
|
||||
}
|
||||
|
||||
T *operator->() {
|
||||
debug_assert(succs[0] != nullptr, "Node is nullptr.");
|
||||
return &succs[0]->value();
|
||||
}
|
||||
|
||||
operator T &() {
|
||||
debug_assert(succs[0] != nullptr, "Node is nullptr.");
|
||||
return succs[0]->value();
|
||||
}
|
||||
|
||||
bool has_value() { return succs[0] != nullptr; }
|
||||
|
||||
MultiIterator &operator++() {
|
||||
debug_assert(succs[0] != nullptr, "Node is nullptr.");
|
||||
// NOTE: This whole method can be optimized if it's valid to expect
|
||||
// height
|
||||
// of 1 on same key elements.
|
||||
|
||||
// First update preds and succs.
|
||||
for (int i = succs[0]->height - 1; i >= 0; i--) {
|
||||
preds[i] = succs[i];
|
||||
succs[i] = preds[i]->forward(i);
|
||||
}
|
||||
|
||||
// If there exists current value then check if it is equal to our
|
||||
// data.
|
||||
if (succs[0] != nullptr) {
|
||||
if (succs[0]->value() != data) {
|
||||
// Current data isn't equal to our data that means that this
|
||||
// is the end of list of same values.
|
||||
succs[0] = nullptr;
|
||||
} else {
|
||||
// Current value is same as our data but we must check that
|
||||
// it is valid data and if not we must wait for it to
|
||||
// become valid.
|
||||
while (succs[0] != succs[succs[0]->height - 1] ||
|
||||
!succs[0]->flags.is_fully_linked()) {
|
||||
usleep(250); // Wait to become linked
|
||||
// Reget succs.
|
||||
for (int i = succs[0]->height - 1; i >= 0; i--) {
|
||||
succs[i] = preds[i]->forward(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return this->derived();
|
||||
}
|
||||
|
||||
MultiIterator &operator++(int) { return operator++(); }
|
||||
|
||||
friend bool operator==(const MultiIterator &a, const MultiIterator &b) {
|
||||
return a.succs[0] == b.succs[0];
|
||||
}
|
||||
|
||||
friend bool operator!=(const MultiIterator &a, const MultiIterator &b) {
|
||||
return !(a == b);
|
||||
}
|
||||
|
||||
bool is_removed() {
|
||||
debug_assert(succs[0] != nullptr, "Node is nullptr.");
|
||||
return succs[0]->flags.is_marked();
|
||||
}
|
||||
|
||||
// True if this call successfuly removed value. ITERATOR IS'T ADVANCED.
|
||||
// False may mean that data has already been removed.
|
||||
bool remove() {
|
||||
debug_assert(succs[0] != nullptr, "Node is nullptr.");
|
||||
// Calls skiplist remove method.
|
||||
|
||||
return skiplist->template remove<K>(
|
||||
data, preds, succs,
|
||||
SkipList<T>::template MultiIterator<K>::update_path);
|
||||
}
|
||||
|
||||
private:
|
||||
// TODO: figure why start is unused
|
||||
static int update_path(SkipList *skiplist, int start, const K &item,
|
||||
Node *preds[], Node *succs[]) {
|
||||
// NOTE: This could be done more efficent than serching for item
|
||||
// element again. Whe just need to use infromation already present
|
||||
// in preds and succ because he know that this method is used
|
||||
// exclusively by passing it into skiplist remove method from
|
||||
// MultiIterator remove method.
|
||||
|
||||
// One optimization here would be to wait for is_fully_linked to be
|
||||
// true. That way that doesnt have to be done in constructor and
|
||||
// ++ operator.
|
||||
int level_found = succs[0]->height - 1;
|
||||
|
||||
debug_assert(succs[0] == succs[level_found], "Node is initialized.");
|
||||
|
||||
for (auto it = MultiIterator<K>(skiplist, item); it.has_value(); it++) {
|
||||
if (it.succs[0] == succs[0]) { // Found it
|
||||
std::copy(it.preds, it.preds + H, preds);
|
||||
std::copy(it.succs, it.succs + H, succs);
|
||||
return level_found;
|
||||
}
|
||||
}
|
||||
// Someone removed it
|
||||
return -1;
|
||||
}
|
||||
|
||||
const K &data;
|
||||
SkipList *skiplist;
|
||||
Node *preds[H], *succs[H];
|
||||
};
|
||||
|
||||
SkipList() : header(Node::sentinel(H)) {}
|
||||
|
||||
~SkipList() {
|
||||
@ -616,16 +488,6 @@ class SkipList : private Lockable<lock_t> {
|
||||
|
||||
ReverseIterator rend() { return skiplist->rend(); }
|
||||
|
||||
template <class K>
|
||||
MultiIterator<K> end(const K &data) {
|
||||
return skiplist->mend(data);
|
||||
}
|
||||
|
||||
template <class K>
|
||||
MultiIterator<K> mend(const K &data) {
|
||||
return skiplist->template mend<K>(data);
|
||||
}
|
||||
|
||||
std::pair<Iterator, bool> insert(const T &item) {
|
||||
return skiplist->insert(preds, succs, item);
|
||||
}
|
||||
@ -639,19 +501,6 @@ class SkipList : private Lockable<lock_t> {
|
||||
return skiplist->emplace(preds, succs, key, std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
Iterator insert_non_unique(const T &item) {
|
||||
return skiplist->insert_non_unique(item, preds, succs);
|
||||
}
|
||||
|
||||
Iterator insert_non_unique(T &&item) {
|
||||
return skiplist->insert_non_unique(std::forward<T>(item), preds, succs);
|
||||
}
|
||||
|
||||
template <class K>
|
||||
MultiIterator<K> find_multi(const K &item) const {
|
||||
return MultiIterator<K>(this->skiplist, item);
|
||||
}
|
||||
|
||||
template <class K>
|
||||
ConstIterator find(const K &item) const {
|
||||
return static_cast<const SkipList &>(*skiplist).find(item);
|
||||
@ -684,8 +533,7 @@ class SkipList : private Lockable<lock_t> {
|
||||
|
||||
template <class K>
|
||||
bool remove(const K &item) {
|
||||
return skiplist->remove(item, preds, succs,
|
||||
SkipList<T>::template find_path<K>);
|
||||
return skiplist->remove(item, preds, succs);
|
||||
}
|
||||
|
||||
size_t size() const { return skiplist->size(); }
|
||||
@ -735,23 +583,8 @@ class SkipList : private Lockable<lock_t> {
|
||||
|
||||
ConstIterator cend() const { return ConstIterator(); }
|
||||
|
||||
template <class K>
|
||||
MultiIterator<K> end(const K &data) {
|
||||
return MultiIterator<K>(data);
|
||||
}
|
||||
|
||||
template <class K>
|
||||
MultiIterator<K> mend(const K &data) {
|
||||
return MultiIterator<K>(data);
|
||||
}
|
||||
|
||||
size_t size() const { return count.load(); }
|
||||
|
||||
template <class K>
|
||||
static bool equal(const K *item, const Node *const node) {
|
||||
return node && item == node->value();
|
||||
}
|
||||
|
||||
template <class K>
|
||||
static bool greater(const K &item, const Node *const node) {
|
||||
return node && item > node->value();
|
||||
@ -786,12 +619,12 @@ class SkipList : private Lockable<lock_t> {
|
||||
}
|
||||
|
||||
Node *preds[H];
|
||||
find_path(this, H - 1, item, preds);
|
||||
find_path(item, preds);
|
||||
return std::make_pair(ReverseIterator(this, preds[0], preds), true);
|
||||
}
|
||||
|
||||
template <class It, class K>
|
||||
It find_node(const K &item) {
|
||||
It find_node(const K &item) const {
|
||||
auto it = find_or_larger<It, K>(item);
|
||||
if (it.node == nullptr || item == *it) {
|
||||
return std::move(it);
|
||||
@ -804,7 +637,7 @@ class SkipList : private Lockable<lock_t> {
|
||||
* Returns iterator on searched element or the first larger element.
|
||||
*/
|
||||
template <class It, class K>
|
||||
It find_or_larger(const K &item) {
|
||||
It find_or_larger(const K &item) const {
|
||||
Node *node, *pred = header;
|
||||
int h = static_cast<int>(pred->height) - 1;
|
||||
|
||||
@ -829,13 +662,27 @@ class SkipList : private Lockable<lock_t> {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the location in the skiplist for the given item.
|
||||
* Fills up the predecessor and successor nodes if pointers
|
||||
* are given.
|
||||
*
|
||||
* @param item - the item for which to find the location.
|
||||
* @param preds - An array of predecessor nodes. Filled up with
|
||||
* towers that would link to the new tower. If nullptr, it is
|
||||
* ignored.
|
||||
* @param succs - Like preds, for successor nodes.
|
||||
* @return - The height of the node already present in the
|
||||
* skiplist, that matches the given item (is equal to it).
|
||||
* Returns -1 if there is no matching item in the skiplist.
|
||||
*/
|
||||
template <class K>
|
||||
static int find_path(SkipList *skiplist, int start, const K &item,
|
||||
Node *preds[] = nullptr, Node *succs[] = nullptr) {
|
||||
int find_path(const K &item, Node *preds[] = nullptr,
|
||||
Node *succs[] = nullptr) const {
|
||||
int level_found = -1;
|
||||
Node *pred = skiplist->header;
|
||||
Node *pred = header;
|
||||
|
||||
for (int level = start; level >= 0; --level) {
|
||||
for (int level = H - 1; level >= 0; --level) {
|
||||
Node *node = pred->forward(level);
|
||||
|
||||
while (greater(item, node)) pred = node, node = pred->forward(level);
|
||||
@ -871,8 +718,8 @@ class SkipList : private Lockable<lock_t> {
|
||||
Node *first_preds[32];
|
||||
Node *second_preds[32];
|
||||
|
||||
find_path(this, H - 1, first, first_preds, nullptr);
|
||||
find_path(this, H - 1, second, second_preds, nullptr);
|
||||
find_path(first, first_preds, nullptr);
|
||||
find_path(second, second_preds, nullptr);
|
||||
|
||||
for (int i = level; i >= 0; i--)
|
||||
if (first_preds[i] == second_preds[i]) skiplist_size /= 2;
|
||||
@ -902,88 +749,25 @@ class SkipList : private Lockable<lock_t> {
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserts non unique data into list.
|
||||
* Insert an element into the skiplist.
|
||||
*
|
||||
* NOTE: Uses modified logic from insert method.
|
||||
* @param preds - Predecessor nodes
|
||||
* @param succs - Successor nodes
|
||||
* @param data - Item to insert into the skiplist
|
||||
* @tparam TItem - Item type. Must be the same as skiplist item
|
||||
* type <T>. This function is templatized so it can accept
|
||||
* universal references and keep the code dry.
|
||||
*/
|
||||
Iterator insert_non_unique(T &&data, Node *preds[], Node *succs[]) {
|
||||
template <typename TItem>
|
||||
std::pair<Iterator, bool> insert(Node *preds[], Node *succs[], TItem &&data) {
|
||||
while (true) {
|
||||
auto level = find_path(this, H - 1, data, preds, succs);
|
||||
|
||||
auto height = 1;
|
||||
if (level != -1) {
|
||||
auto found = succs[level];
|
||||
|
||||
if (found->flags.is_marked()) continue;
|
||||
|
||||
if (!found->flags.is_fully_linked()) {
|
||||
usleep(250);
|
||||
continue;
|
||||
}
|
||||
|
||||
// TODO Optimization for avoiding degrading of skiplist to list.
|
||||
// Somehow this operation is errornus.
|
||||
// if (found->height == 1) { // To avoid linearization new
|
||||
// element
|
||||
// // will be at least 2 height and
|
||||
// will
|
||||
// // be added in front.
|
||||
// height = rnd(H);
|
||||
// // if (height == 1) height = 2;
|
||||
// } else {
|
||||
|
||||
// Only level 0 will be used so the rest is irrelevant.
|
||||
preds[0] = found;
|
||||
succs[0] = found->forward(0);
|
||||
|
||||
// This maybe isn't necessary
|
||||
auto next = succs[0];
|
||||
if (next != nullptr) {
|
||||
if (next->flags.is_marked()) continue;
|
||||
|
||||
if (!next->flags.is_fully_linked()) {
|
||||
usleep(250);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
height = rnd(H);
|
||||
// Optimization which doesn't add any extra locking.
|
||||
if (height == 1)
|
||||
height = 2; // Same key list will be skipped more often.
|
||||
}
|
||||
|
||||
guard_t guards[H];
|
||||
|
||||
// try to acquire the locks for predecessors up to the height of
|
||||
// the new node. release the locks and try again if someone else
|
||||
// has the locks
|
||||
if (!lock_nodes<true>(height, guards, preds, succs)) continue;
|
||||
|
||||
return insert_here(Node::create(std::move(data), height), preds, succs,
|
||||
height, guards);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert unique data
|
||||
*
|
||||
* F - type of funct which will create new node if needed. Recieves height
|
||||
* of node.
|
||||
*/
|
||||
// TODO this code is not DRY w.r.t. the other insert function (rvalue ref)
|
||||
std::pair<Iterator, bool> insert(Node *preds[], Node *succs[],
|
||||
const T &data) {
|
||||
while (true) {
|
||||
// TODO: before here was data.first
|
||||
auto level = find_path(this, H - 1, data, preds, succs);
|
||||
auto level = find_path(data, preds, succs);
|
||||
|
||||
if (level != -1) {
|
||||
auto found = succs[level];
|
||||
|
||||
if (found->flags.is_marked()) continue;
|
||||
|
||||
while (!found->flags.is_fully_linked()) usleep(250);
|
||||
found->flags.wait_fully_linked();
|
||||
|
||||
return {Iterator{succs[level]}, false};
|
||||
}
|
||||
@ -996,43 +780,8 @@ class SkipList : private Lockable<lock_t> {
|
||||
// has the locks
|
||||
if (!lock_nodes<true>(height, guards, preds, succs)) continue;
|
||||
|
||||
return {
|
||||
insert_here(Node::create(data, height), preds, succs, height, guards),
|
||||
true};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert unique data
|
||||
*
|
||||
* F - type of funct which will create new node if needed. Recieves height
|
||||
* of node.
|
||||
*/
|
||||
std::pair<Iterator, bool> insert(Node *preds[], Node *succs[], T &&data) {
|
||||
while (true) {
|
||||
// TODO: before here was data.first
|
||||
auto level = find_path(this, H - 1, data, preds, succs);
|
||||
|
||||
if (level != -1) {
|
||||
auto found = succs[level];
|
||||
|
||||
if (found->flags.is_marked()) continue;
|
||||
|
||||
while (!found->flags.is_fully_linked()) usleep(250);
|
||||
|
||||
return {Iterator{succs[level]}, false};
|
||||
}
|
||||
|
||||
auto height = rnd(H);
|
||||
guard_t guards[H];
|
||||
|
||||
// try to acquire the locks for predecessors up to the height of
|
||||
// the new node. release the locks and try again if someone else
|
||||
// has the locks
|
||||
if (!lock_nodes<true>(height, guards, preds, succs)) continue;
|
||||
|
||||
return {insert_here(Node::create(std::move(data), height), preds, succs,
|
||||
height, guards),
|
||||
return {insert_here(Node::create(std::forward<TItem>(data), height),
|
||||
preds, succs, height, guards),
|
||||
true};
|
||||
}
|
||||
}
|
||||
@ -1046,15 +795,13 @@ class SkipList : private Lockable<lock_t> {
|
||||
std::pair<Iterator, bool> emplace(Node *preds[], Node *succs[], K &key,
|
||||
Args &&... args) {
|
||||
while (true) {
|
||||
// TODO: before here was data.first
|
||||
auto level = find_path(this, H - 1, key, preds, succs);
|
||||
auto level = find_path(key, preds, succs);
|
||||
|
||||
if (level != -1) {
|
||||
auto found = succs[level];
|
||||
|
||||
if (found->flags.is_marked()) continue;
|
||||
|
||||
while (!found->flags.is_fully_linked()) usleep(250);
|
||||
found->flags.wait_fully_linked();
|
||||
|
||||
return {Iterator{succs[level]}, false};
|
||||
}
|
||||
@ -1077,9 +824,8 @@ class SkipList : private Lockable<lock_t> {
|
||||
* Inserts data to specified locked location.
|
||||
*/
|
||||
Iterator insert_here(Node *new_node, Node *preds[], Node *succs[], int height,
|
||||
guard_t guards[]) // TODO: querds unused
|
||||
{
|
||||
// Node::create(std::move(data), height)
|
||||
guard_t guards[]) {
|
||||
// TODO: guards unused
|
||||
// link the predecessors and successors, e.g.
|
||||
//
|
||||
// 4 HEAD ... P ------------------------> S ... NULL
|
||||
@ -1092,34 +838,25 @@ class SkipList : private Lockable<lock_t> {
|
||||
}
|
||||
|
||||
new_node->flags.set_fully_linked();
|
||||
count.fetch_add(1);
|
||||
count++;
|
||||
|
||||
return Iterator{new_node};
|
||||
}
|
||||
|
||||
static bool ok_delete(Node *node, int level) {
|
||||
return node->flags.is_fully_linked() && node->height - 1 == level &&
|
||||
!node->flags.is_marked();
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes item found with fp with arguments skiplist, preds and succs.
|
||||
* fp has to fill preds and succs which reflect location of item or return
|
||||
* -1 as in not found otherwise returns level on which the item was first
|
||||
* found.
|
||||
*/
|
||||
template <class K>
|
||||
bool remove(const K &item, Node *preds[], Node *succs[],
|
||||
int (*fp)(SkipList *, int, const K &, Node *[], Node *[])) {
|
||||
bool remove(const K &item, Node *preds[], Node *succs[]) {
|
||||
Node *node = nullptr;
|
||||
guard_t node_guard;
|
||||
bool marked = false;
|
||||
int height = 0;
|
||||
|
||||
while (true) {
|
||||
auto level = fp(this, H - 1, item, preds, succs);
|
||||
auto level = find_path(item, preds, succs);
|
||||
|
||||
if (!marked && (level == -1 || !ok_delete(succs[level], level)))
|
||||
if (!marked && (level == -1 || !succs[level]->ok_delete(level)))
|
||||
return false;
|
||||
|
||||
if (!marked) {
|
||||
@ -1142,7 +879,7 @@ class SkipList : private Lockable<lock_t> {
|
||||
|
||||
gc.Collect(node);
|
||||
|
||||
count.fetch_sub(1);
|
||||
count--;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -1,350 +0,0 @@
|
||||
#pragma mark
|
||||
|
||||
#include <cstring>
|
||||
#include <functional>
|
||||
#include "data_structures/map/rh_common.hpp"
|
||||
#include "utils/assert.hpp"
|
||||
#include "utils/assert.hpp"
|
||||
#include "utils/crtp.hpp"
|
||||
#include "utils/likely.hpp"
|
||||
#include "utils/option.hpp"
|
||||
#include "utils/option_ptr.hpp"
|
||||
|
||||
/**
|
||||
* HashMultiMap with RobinHood collision resolution policy.
|
||||
* Single threaded.
|
||||
* Entries are POINTERS alligned to 8B.
|
||||
* Entries must know thers key.
|
||||
* D must have method K& get_key()
|
||||
* K must be comparable with ==.
|
||||
* HashMap behaves as if it isn't owner of entries.
|
||||
* BE CAREFUL - this structure assumes that the pointer to Data is
|
||||
* 8-alligned!
|
||||
*
|
||||
* Main idea of this MultiMap is a tweak of logic in RobinHood.
|
||||
* RobinHood offset from prefered slot is equal to the number of slots between
|
||||
* [current slot and prefered slot>.
|
||||
* While in this flavour of "multi RobinHood" offset from prefered slot is
|
||||
* equal
|
||||
* to the number of different keyed elements between his current slot and
|
||||
* prefered slot.
|
||||
* In the following examples slots will have keys as caracters. So something
|
||||
* like this: |a| will mean that in this slot there is data with key 'a'.
|
||||
* like this: | | will mean empty slot.
|
||||
* like this: |...| will mean arbitary number of slots.
|
||||
* like this: |b:a| will mean that a want's to be in slot but b is in't.
|
||||
*
|
||||
* Examples:
|
||||
* |...|a:a|...| => off(a) = 0
|
||||
* |...|a:a|a|...|a|...| => off(a) = 0
|
||||
* |...|b:a|a|...| => off(a) = 1
|
||||
* |...|b:a|b|...|b|a|...| => off(a) = 1
|
||||
* |...|c:a|b|a|...| => off(a) = 2
|
||||
* |...|c:a|c|...|c|b|...|b||a|...|a|...| => off(a) = 2
|
||||
* ...
|
||||
*/
|
||||
template <class K, class D, size_t init_size_pow2 = 2>
|
||||
class RhHashMultiMap : public RhBase<K, D, init_size_pow2> {
|
||||
typedef RhBase<K, D, init_size_pow2> base;
|
||||
using base::array;
|
||||
using base::index;
|
||||
using base::capacity;
|
||||
using base::count;
|
||||
using typename base::Combined;
|
||||
using base::before_index;
|
||||
using base::create_it;
|
||||
|
||||
void increase_size() {
|
||||
size_t old_size = capacity;
|
||||
auto a = array;
|
||||
if (base::increase_size()) {
|
||||
for (int i = 0; i < old_size; i++) {
|
||||
if (a[i].valid()) {
|
||||
add(a[i].ptr());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
free(a);
|
||||
}
|
||||
|
||||
public:
|
||||
using base::RhBase;
|
||||
using base::end;
|
||||
using typename base::ConstIterator;
|
||||
using typename base::Iterator;
|
||||
|
||||
bool contains(const K &key) const { return find_index(key).is_present(); }
|
||||
|
||||
Iterator find(const K &key_in) {
|
||||
auto index = find_index(key_in);
|
||||
if (index) {
|
||||
return create_it(index.get());
|
||||
} else {
|
||||
return end();
|
||||
}
|
||||
}
|
||||
|
||||
ConstIterator find(const K &key_in) const {
|
||||
auto index = find_index(key_in);
|
||||
if (index) {
|
||||
return create_it(index.get());
|
||||
} else {
|
||||
return end();
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
Option<size_t> find_index(const K &key_in) const {
|
||||
if (count > 0) {
|
||||
auto key = std::ref(key_in);
|
||||
size_t mask = this->mask();
|
||||
size_t now = index(key, mask);
|
||||
size_t off = 0;
|
||||
size_t border = 8 <= capacity ? 8 : capacity;
|
||||
Combined other = array[now];
|
||||
while (other.valid() && off < border) {
|
||||
auto other_off = other.off();
|
||||
if (other_off == off && key == other.ptr()->get_key()) {
|
||||
return Option<size_t>(now);
|
||||
|
||||
} else if (other_off < off) { // Other is rich
|
||||
break;
|
||||
|
||||
} else { // Else other has equal or greater off, so he is poor.
|
||||
if (UNLIKELY(skip(now, other, other_off, mask))) {
|
||||
break;
|
||||
}
|
||||
off++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Option<size_t>();
|
||||
}
|
||||
|
||||
public:
|
||||
// Inserts element.
|
||||
void add(D *data) { add(data->get_key(), data); }
|
||||
|
||||
// Inserts element with the given key.
|
||||
void add(const K &key_in, D *data) {
|
||||
debug_assert(key_in == data->get_key(), "Key doesn't match data key.");
|
||||
permanent_assert(!((uint64_t)(static_cast<void *>(data)) & 7),
|
||||
"Data is not 8-alligned.");
|
||||
|
||||
if (count < capacity) {
|
||||
auto key = std::ref(key_in);
|
||||
size_t mask = this->mask();
|
||||
size_t now = index(key, mask);
|
||||
size_t start = now;
|
||||
size_t off = 0;
|
||||
size_t border = 8 <= capacity ? 8 : capacity;
|
||||
|
||||
Combined other = array[now];
|
||||
while (off < border) {
|
||||
if (other.valid()) {
|
||||
const size_t other_off = other.off();
|
||||
bool multi = false;
|
||||
if (other_off == off && other.ptr()->get_key() == key) {
|
||||
// Found the same
|
||||
// Must skip same keyd values to insert new value at the
|
||||
// end.
|
||||
do {
|
||||
now = (now + 1) & mask;
|
||||
other = array[now];
|
||||
if (!other.valid()) {
|
||||
// Found empty slot in which data ca be added.
|
||||
set(now, data, off);
|
||||
return;
|
||||
}
|
||||
} while (other.equal(key, off));
|
||||
// There is no empty slot after same keyed values.
|
||||
multi = true;
|
||||
} else if (other_off > off ||
|
||||
other_poor(other, mask, start,
|
||||
now)) { // Else other has equal or
|
||||
// greater off, so he is poor.
|
||||
skip(now, other, other_off, mask); // TRUE IS IMPOSSIBLE
|
||||
off++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Data will be insrted at current slot and all other data
|
||||
// will be displaced for one slot.
|
||||
array[now] = Combined(data, off);
|
||||
auto start_insert = now;
|
||||
while (is_off_adjusted(other, mask, start_insert, now, multi) ||
|
||||
other.increment_off()) {
|
||||
now = (now + 1) & mask;
|
||||
auto tmp = array[now];
|
||||
array[now] = other;
|
||||
other = tmp;
|
||||
if (!other.valid()) {
|
||||
// Found empty slot which means i can finish now.
|
||||
count++;
|
||||
return;
|
||||
}
|
||||
}
|
||||
data = other.ptr();
|
||||
break; // Cant insert removed element
|
||||
} else {
|
||||
// Found empty slot for data.
|
||||
set(now, data, off);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// There is't enough space for data.
|
||||
increase_size();
|
||||
add(data);
|
||||
}
|
||||
|
||||
// Removes element equal by key and value. Returns true if it existed.
|
||||
bool remove(D *data) {
|
||||
if (count > 0) {
|
||||
auto key = std::ref(data->get_key());
|
||||
size_t mask = this->mask();
|
||||
size_t now = index(key, mask);
|
||||
size_t off = 0;
|
||||
size_t border = 8 <= capacity ? 8 : capacity;
|
||||
Combined other = array[now];
|
||||
|
||||
while (other.valid() && off < border) {
|
||||
const size_t other_off = other.off();
|
||||
if (other_off == off && key == other.ptr()->get_key()) {
|
||||
// Found same key data.
|
||||
auto founded = capacity;
|
||||
size_t started = now;
|
||||
bool multi = false;
|
||||
// Must find slot with searched data.
|
||||
do {
|
||||
if (other.ptr() == data) {
|
||||
// founded it.
|
||||
founded = now;
|
||||
}
|
||||
now = (now + 1) & mask;
|
||||
other = array[now];
|
||||
if (!other.valid() || UNLIKELY(started == now)) {
|
||||
// Reason is possibility of map full of same values.
|
||||
break;
|
||||
}
|
||||
} while (other.equal(key, off) && (multi = true));
|
||||
|
||||
if (founded == capacity) {
|
||||
// Didn't found the data.
|
||||
return false;
|
||||
}
|
||||
|
||||
// Data will be removed by moving other data by one slot
|
||||
// before.
|
||||
auto bef = before_index(now, mask);
|
||||
array[founded] = array[bef];
|
||||
|
||||
auto start_rem = bef;
|
||||
while (other.valid() && (is_off_adjusted_rem(other, mask, start_rem,
|
||||
bef, now, multi) ||
|
||||
other.decrement_off())) {
|
||||
array[bef] = other;
|
||||
bef = now;
|
||||
now = (now + 1) & mask;
|
||||
other = array[now];
|
||||
}
|
||||
|
||||
array[bef] = Combined();
|
||||
count--;
|
||||
return true;
|
||||
|
||||
} else if (other_off < off) { // Other is rich
|
||||
break;
|
||||
|
||||
} else { // Else other has equal or greater off, so he is poor.
|
||||
// Must skip values of same keys but different key than
|
||||
// data.
|
||||
if (UNLIKELY(skip(now, other, other_off, mask))) {
|
||||
break;
|
||||
}
|
||||
off++;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private:
|
||||
// Skips same key valus as other. true if whole map is full of same key
|
||||
// values.
|
||||
bool skip(size_t &now, Combined &other, size_t other_off, size_t mask) const {
|
||||
auto other_key = other.ptr()->get_key();
|
||||
size_t start = now;
|
||||
do {
|
||||
now = (now + 1) & mask;
|
||||
other = array[now];
|
||||
if (UNLIKELY(start == now)) { // Reason is possibility of map
|
||||
// full of same values.
|
||||
return true;
|
||||
}
|
||||
} while (other.valid() && other.equal(other_key, other_off));
|
||||
return false;
|
||||
}
|
||||
|
||||
void set(size_t now, D *data, size_t off) {
|
||||
array[now] = Combined(data, off);
|
||||
count++;
|
||||
}
|
||||
|
||||
// True if no adjusment is needed, false otherwise.
|
||||
bool is_off_adjusted(Combined &com, size_t mask, size_t start, size_t now,
|
||||
bool multi) {
|
||||
if (com.off() == 0) { // Must be adjusted
|
||||
return false;
|
||||
}
|
||||
size_t cin = index(com.ptr()->get_key(), mask);
|
||||
if (outside(start, now, cin)) { // Outside [start,now] interval
|
||||
return multi;
|
||||
}
|
||||
auto a = array[cin];
|
||||
auto b = array[(cin + 1) & mask];
|
||||
return a == b;
|
||||
// Check if different key has eneterd in to
|
||||
// range of other.
|
||||
}
|
||||
|
||||
bool other_poor(Combined other, size_t mask, size_t start, size_t now) {
|
||||
// If other index is smaller then he is poorer.
|
||||
return outside_left_weak(start, now, index(other.ptr()->get_key(), mask));
|
||||
}
|
||||
|
||||
// True if no adjusment is needed, false otherwise.
|
||||
bool is_off_adjusted_rem(Combined &com, size_t mask, size_t start, size_t bef,
|
||||
size_t now, bool multi) {
|
||||
if (com.off() == 0) { // Must be adjusted
|
||||
return false;
|
||||
}
|
||||
size_t cin = index(com.ptr()->get_key(), mask);
|
||||
if (cin == bef) {
|
||||
return false;
|
||||
}
|
||||
if (outside(start, now, cin)) {
|
||||
return multi;
|
||||
}
|
||||
auto a = array[cin];
|
||||
auto b = array[before_index(cin, mask)];
|
||||
return b.valid() && a == b;
|
||||
// Check if different key has eneterd in to
|
||||
// range of other.
|
||||
}
|
||||
|
||||
// True if p is uutside [start,end] interval
|
||||
bool outside(size_t start, size_t end, size_t p) {
|
||||
return (start <= end && (p < start || p > end)) ||
|
||||
(end < start && p < start && p > end);
|
||||
}
|
||||
|
||||
// True if p is outside <start,end] interval
|
||||
bool outside_left_weak(size_t start, size_t end, size_t p) {
|
||||
return (start <= end && (p <= start || p > end)) ||
|
||||
(end < start && p <= start && p > end);
|
||||
}
|
||||
};
|
@ -7,8 +7,6 @@
|
||||
#include "data_structures/bitset/dynamic_bitset.hpp"
|
||||
#include "data_structures/concurrent/concurrent_list.hpp"
|
||||
#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"
|
||||
@ -27,8 +25,6 @@ using std::cout;
|
||||
using std::endl;
|
||||
using map_t = ConcurrentMap<int, int>;
|
||||
using set_t = ConcurrentSet<int>;
|
||||
using multiset_t = ConcurrentMultiSet<int>;
|
||||
using multimap_t = ConcurrentMultiMap<int, int>;
|
||||
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
@ -129,37 +125,6 @@ void check_set(DynamicBitset<> &db, std::vector<bool> &set) {
|
||||
}
|
||||
}
|
||||
|
||||
// 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.
|
||||
|
@ -1,58 +0,0 @@
|
||||
#include "common.h"
|
||||
|
||||
constexpr size_t THREADS_NO = std::min(max_no_threads, 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() {
|
||||
init_log();
|
||||
|
||||
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);
|
||||
});
|
||||
}
|
@ -1,88 +0,0 @@
|
||||
#include "common.h"
|
||||
|
||||
constexpr size_t THREADS_NO = std::min(max_no_threads, 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() {
|
||||
init_log();
|
||||
|
||||
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);
|
||||
});
|
||||
}
|
@ -1,82 +0,0 @@
|
||||
#include "common.h"
|
||||
|
||||
constexpr size_t THREADS_NO = std::min(max_no_threads, 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 for a random key. So removes are joined and scattered on the
|
||||
* same key values. Calls of remove method are interleaved with insert calls
|
||||
* which always succeed.
|
||||
*/
|
||||
int main() {
|
||||
init_log();
|
||||
|
||||
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);
|
||||
});
|
||||
}
|
@ -1,119 +0,0 @@
|
||||
#include "common.h"
|
||||
|
||||
constexpr size_t THREADS_NO = std::min(max_no_threads, 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;
|
||||
|
||||
// TODO: document the test
|
||||
|
||||
// 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() {
|
||||
init_log();
|
||||
|
||||
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");
|
||||
});
|
||||
}
|
@ -1,72 +0,0 @@
|
||||
#include "common.h"
|
||||
|
||||
constexpr size_t THREADS_NO = std::min(max_no_threads, 8);
|
||||
constexpr size_t key_range = 1e4;
|
||||
constexpr size_t op_per_thread = 1e5;
|
||||
constexpr size_t no_insert_for_one_delete = 1;
|
||||
|
||||
// TODO: document the test
|
||||
|
||||
// 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() {
|
||||
init_log();
|
||||
|
||||
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");
|
||||
|
||||
});
|
||||
}
|
@ -1,295 +0,0 @@
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
#include "data_structures/map/rh_hashmultimap.hpp"
|
||||
|
||||
class Data {
|
||||
private:
|
||||
int key;
|
||||
|
||||
public:
|
||||
Data(int key) : key(key) {}
|
||||
|
||||
const int &get_key() { return key; }
|
||||
};
|
||||
|
||||
void cross_validate(RhHashMultiMap<int, Data> &map,
|
||||
std::multimap<int, Data *> &s_map);
|
||||
|
||||
void cross_validate_weak(RhHashMultiMap<int, Data> &map,
|
||||
std::multimap<int, Data *> &s_map);
|
||||
|
||||
TEST(RobinHoodHashmultimap, BasicFunctionality) {
|
||||
RhHashMultiMap<int, Data> map;
|
||||
|
||||
ASSERT_EQ(map.size(), 0);
|
||||
Data d0(0);
|
||||
map.add(&d0);
|
||||
ASSERT_EQ(map.size(), 1);
|
||||
}
|
||||
|
||||
TEST(RobinHoodHashmultimap, InsertGetCheck) {
|
||||
RhHashMultiMap<int, Data> map;
|
||||
|
||||
ASSERT_EQ(map.find(0), map.end());
|
||||
Data d0(0);
|
||||
map.add(&d0);
|
||||
ASSERT_NE(map.find(0), map.end());
|
||||
ASSERT_EQ(*map.find(0), &d0);
|
||||
}
|
||||
|
||||
TEST(RobinHoodHashmultimap, ExtremeSameKeyValusFull) {
|
||||
RhHashMultiMap<int, Data> map;
|
||||
|
||||
std::vector<std::unique_ptr<Data>> di;
|
||||
di.reserve(128);
|
||||
for (int i = 0; i < 128; i++) {
|
||||
di.emplace_back(std::make_unique<Data>(7));
|
||||
map.add(di[i].get());
|
||||
}
|
||||
ASSERT_EQ(map.size(), 128);
|
||||
ASSERT_NE(map.find(7), map.end());
|
||||
ASSERT_EQ(map.find(0), map.end());
|
||||
Data d0(0);
|
||||
map.add(&d0);
|
||||
ASSERT_NE(map.find(0), map.end());
|
||||
ASSERT_EQ(*map.find(0), &d0);
|
||||
}
|
||||
|
||||
TEST(RobinHoodHashmultimap, ExtremeSameKeyValusFullWithRemove) {
|
||||
RhHashMultiMap<int, Data> map;
|
||||
|
||||
std::vector<std::unique_ptr<Data>> di;
|
||||
di.reserve(128);
|
||||
for (int i = 0; i < 127; i++) {
|
||||
di.emplace_back(std::make_unique<Data>(7));
|
||||
map.add(di[i].get());
|
||||
}
|
||||
Data d7(7);
|
||||
map.add(&d7);
|
||||
ASSERT_EQ(map.size(), 128);
|
||||
Data d0(0);
|
||||
ASSERT_EQ(!map.remove(&d0), true);
|
||||
ASSERT_EQ(map.remove(&d7), true);
|
||||
}
|
||||
|
||||
TEST(RobinHoodHasmultihmap, RemoveFunctionality) {
|
||||
RhHashMultiMap<int, Data> map;
|
||||
|
||||
ASSERT_EQ(map.find(0), map.end());
|
||||
Data d0(0);
|
||||
map.add(&d0);
|
||||
ASSERT_NE(map.find(0), map.end());
|
||||
ASSERT_EQ(*map.find(0), &d0);
|
||||
ASSERT_EQ(map.remove(&d0), true);
|
||||
ASSERT_EQ(map.find(0), map.end());
|
||||
}
|
||||
|
||||
TEST(RobinHoodHashmultimap, DoubleInsert) {
|
||||
RhHashMultiMap<int, Data> map;
|
||||
|
||||
Data d0(0);
|
||||
Data d01(0);
|
||||
map.add(&d0);
|
||||
map.add(&d01);
|
||||
|
||||
for (auto e : map) {
|
||||
if (&d0 == e) {
|
||||
continue;
|
||||
}
|
||||
if (&d01 == e) {
|
||||
continue;
|
||||
}
|
||||
ASSERT_EQ(true, false);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(RobinHoodHashmultimap, FindAddFind) {
|
||||
RhHashMultiMap<int, Data> map;
|
||||
|
||||
std::vector<std::unique_ptr<Data>> di;
|
||||
di.reserve(128);
|
||||
for (int i = 0; i < 128; i++) {
|
||||
ASSERT_EQ(map.find(i), map.end());
|
||||
di.emplace_back(std::make_unique<Data>(i));
|
||||
map.add(di[i].get());
|
||||
ASSERT_NE(map.find(i), map.end());
|
||||
}
|
||||
|
||||
for (int i = 0; i < 128; i++) {
|
||||
ASSERT_NE(map.find(i), map.end());
|
||||
ASSERT_EQ(map.find(i)->get_key(), i);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(RobinHoodHashmultimap, Iterate) {
|
||||
RhHashMultiMap<int, Data> map;
|
||||
|
||||
std::vector<std::unique_ptr<Data>> di;
|
||||
di.reserve(128);
|
||||
for (int i = 0; i < 128; i++) {
|
||||
ASSERT_EQ(map.find(i), map.end());
|
||||
di.emplace_back(std::make_unique<Data>(i));
|
||||
map.add(di[i].get());
|
||||
ASSERT_NE(map.find(i), map.end());
|
||||
}
|
||||
|
||||
bool seen[128] = {false};
|
||||
for (auto e : map) {
|
||||
auto key = e->get_key();
|
||||
ASSERT_EQ(!seen[key], true);
|
||||
seen[key] = true;
|
||||
}
|
||||
for (int i = 0; i < 128; i++) {
|
||||
ASSERT_EQ(seen[i], true);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(RobinHoodHashmultimap, Checked) {
|
||||
RhHashMultiMap<int, Data> map;
|
||||
std::multimap<int, Data *> s_map;
|
||||
|
||||
std::vector<std::unique_ptr<Data>> di;
|
||||
std::vector<int> keys;
|
||||
di.reserve(1638);
|
||||
for (int i = 0; i < 1638; ++i) {
|
||||
const int key = (std::rand() % 100) << 3;
|
||||
keys.push_back(key);
|
||||
di.emplace_back(std::make_unique<Data>(key));
|
||||
}
|
||||
|
||||
for (int i = 0; i < 1638; i++) {
|
||||
map.add(di[i].get());
|
||||
s_map.insert(std::pair<int, Data *>(keys[i], di[i].get()));
|
||||
}
|
||||
cross_validate(map, s_map);
|
||||
}
|
||||
|
||||
TEST(RobinHoodHashmultimap, CheckedRand) {
|
||||
RhHashMultiMap<int, Data> map;
|
||||
std::multimap<int, Data *> s_map;
|
||||
std::srand(std::time(0));
|
||||
|
||||
std::vector<std::unique_ptr<Data>> di;
|
||||
std::vector<int> keys;
|
||||
di.reserve(164308);
|
||||
for (int i = 0; i < 164308; ++i) {
|
||||
const int key = (std::rand() % 10000) << 3;
|
||||
keys.push_back(key);
|
||||
di.emplace_back(std::make_unique<Data>(key));
|
||||
}
|
||||
|
||||
for (int i = 0; i < 164308; i++) {
|
||||
map.add(di[i].get());
|
||||
s_map.insert(std::pair<int, Data *>(keys[i], di[i].get()));
|
||||
}
|
||||
cross_validate(map, s_map);
|
||||
}
|
||||
|
||||
TEST(RobinHoodHashmultimap, WithRemoveDataChecked) {
|
||||
RhHashMultiMap<int, Data> map;
|
||||
std::multimap<int, Data *> s_map;
|
||||
|
||||
std::srand(std::time(0));
|
||||
std::vector<std::unique_ptr<Data>> di;
|
||||
for (int i = 0; i < 162638; i++) {
|
||||
int key = (std::rand() % 10000) << 3;
|
||||
if ((std::rand() % 2) == 0) {
|
||||
auto it = s_map.find(key);
|
||||
if (it == s_map.end()) {
|
||||
ASSERT_EQ(map.find(key), map.end());
|
||||
} else {
|
||||
s_map.erase(it);
|
||||
ASSERT_EQ(map.remove(it->second), true);
|
||||
}
|
||||
} else {
|
||||
di.emplace_back(std::make_unique<Data>(key));
|
||||
map.add(di.back().get());
|
||||
s_map.insert(std::pair<int, Data *>(key, di.back().get()));
|
||||
}
|
||||
}
|
||||
|
||||
cross_validate(map, s_map);
|
||||
}
|
||||
|
||||
TEST(RobinhoodHashmultimap, AlignmentCheck) {
|
||||
RhHashMultiMap<int, Data> map;
|
||||
char *block = static_cast<char *>(std::malloc(20));
|
||||
++block; // not alligned - offset 1
|
||||
EXPECT_DEATH(map.add((Data *)(block)), "not 8-alligned");
|
||||
}
|
||||
|
||||
void cross_validate(RhHashMultiMap<int, Data> &map,
|
||||
std::multimap<int, Data *> &s_map) {
|
||||
for (auto e : map) {
|
||||
auto it = s_map.find(e->get_key());
|
||||
|
||||
while (it != s_map.end() && it->second != e) {
|
||||
it++;
|
||||
}
|
||||
ASSERT_NE(it, s_map.end());
|
||||
}
|
||||
|
||||
for (auto e : s_map) {
|
||||
auto it = map.find(e.first);
|
||||
|
||||
while (it != map.end() && *it != e.second) {
|
||||
it++;
|
||||
}
|
||||
ASSERT_NE(it, map.end());
|
||||
}
|
||||
}
|
||||
|
||||
void cross_validate_weak(RhHashMultiMap<int, Data> &map,
|
||||
std::multimap<int, Data *> &s_map) {
|
||||
int count = 0;
|
||||
int key = 0;
|
||||
for (auto e : map) {
|
||||
if (e->get_key() == key) {
|
||||
count++;
|
||||
} else {
|
||||
auto it = s_map.find(key);
|
||||
|
||||
while (it != s_map.end() && it->first == key) {
|
||||
it++;
|
||||
count--;
|
||||
}
|
||||
ASSERT_EQ(count, 0);
|
||||
key = e->get_key();
|
||||
count = 1;
|
||||
}
|
||||
}
|
||||
{
|
||||
auto it = s_map.find(key);
|
||||
|
||||
while (it != s_map.end() && it->first == key) {
|
||||
it++;
|
||||
count--;
|
||||
}
|
||||
ASSERT_EQ(count, 0);
|
||||
}
|
||||
|
||||
for (auto e : s_map) {
|
||||
if (e.first == key) {
|
||||
count++;
|
||||
} else {
|
||||
auto it = map.find(key);
|
||||
|
||||
while (it != map.end() && it->get_key() == key) {
|
||||
it++;
|
||||
count--;
|
||||
}
|
||||
ASSERT_EQ(count, 0);
|
||||
key = e.first;
|
||||
count = 1;
|
||||
}
|
||||
}
|
||||
{
|
||||
auto it = map.find(key);
|
||||
|
||||
while (it != map.end() && it->get_key() == key) {
|
||||
it++;
|
||||
count--;
|
||||
}
|
||||
ASSERT_EQ(count, 0);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user