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:
florijan 2017-06-19 12:11:01 +02:00
parent a01cf408bf
commit 492b4919f1
12 changed files with 69 additions and 1573 deletions

View File

@ -5,7 +5,7 @@
using std::pair; 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. // store key and value but to make ordering on keys.
template <typename K, typename T> template <typename K, typename T>
class Item : public TotalOrdering<Item<K, T>>, class Item : public TotalOrdering<Item<K, T>>,
@ -72,16 +72,6 @@ class AccessorBase {
list_it_con cend() const { return accessor.cend(); } list_it_con cend() const { return accessor.cend(); }
template <class K>
typename SkipList<T>::template MultiIterator<K> end(const K &data) {
return accessor.template mend<K>(data);
}
template <class K>
typename SkipList<T>::template MultiIterator<K> mend(const K &data) {
return accessor.template mend<K>(data);
}
size_t size() const { return accessor.size(); } size_t size() const { return accessor.size(); }
protected: protected:

View File

@ -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;
};

View File

@ -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;
};

View File

@ -121,6 +121,11 @@ class SkipList : private Lockable<lock_t> {
bool is_fully_linked() const { return flags.load() & FULLY_LINKED; } 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); } void set_fully_linked() { flags.fetch_or(FULLY_LINKED); }
private: private:
@ -144,20 +149,22 @@ class SkipList : private Lockable<lock_t> {
return new (allocate(height)) Node(height); 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); auto node = allocate(height);
// we have raw memory and we need to construct an object // we have raw memory and we need to construct an object
// of type Node on it // of type Node on it
return new (node) Node(item, height); return new (node) Node(std::forward<TItem>(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);
} }
template <class... Args> template <class... Args>
@ -169,6 +176,11 @@ class SkipList : private Lockable<lock_t> {
return new (node) Node(height, std::forward<Args>(args)...); 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) { static void destroy(Node *node) {
node->~Node(); node->~Node();
std::free(node); std::free(node);
@ -422,146 +434,6 @@ class SkipList : private Lockable<lock_t> {
Node *preds_[H]; 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() : header(Node::sentinel(H)) {}
~SkipList() { ~SkipList() {
@ -616,16 +488,6 @@ class SkipList : private Lockable<lock_t> {
ReverseIterator rend() { return skiplist->rend(); } 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) { std::pair<Iterator, bool> insert(const T &item) {
return skiplist->insert(preds, succs, 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)...); 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> template <class K>
ConstIterator find(const K &item) const { ConstIterator find(const K &item) const {
return static_cast<const SkipList &>(*skiplist).find(item); return static_cast<const SkipList &>(*skiplist).find(item);
@ -684,8 +533,7 @@ class SkipList : private Lockable<lock_t> {
template <class K> template <class K>
bool remove(const K &item) { bool remove(const K &item) {
return skiplist->remove(item, preds, succs, return skiplist->remove(item, preds, succs);
SkipList<T>::template find_path<K>);
} }
size_t size() const { return skiplist->size(); } size_t size() const { return skiplist->size(); }
@ -735,23 +583,8 @@ class SkipList : private Lockable<lock_t> {
ConstIterator cend() const { return ConstIterator(); } 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(); } 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> template <class K>
static bool greater(const K &item, const Node *const node) { static bool greater(const K &item, const Node *const node) {
return node && item > node->value(); return node && item > node->value();
@ -786,12 +619,12 @@ class SkipList : private Lockable<lock_t> {
} }
Node *preds[H]; Node *preds[H];
find_path(this, H - 1, item, preds); find_path(item, preds);
return std::make_pair(ReverseIterator(this, preds[0], preds), true); return std::make_pair(ReverseIterator(this, preds[0], preds), true);
} }
template <class It, class K> 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); auto it = find_or_larger<It, K>(item);
if (it.node == nullptr || item == *it) { if (it.node == nullptr || item == *it) {
return std::move(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. * Returns iterator on searched element or the first larger element.
*/ */
template <class It, class K> template <class It, class K>
It find_or_larger(const K &item) { It find_or_larger(const K &item) const {
Node *node, *pred = header; Node *node, *pred = header;
int h = static_cast<int>(pred->height) - 1; 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> template <class K>
static int find_path(SkipList *skiplist, int start, const K &item, int find_path(const K &item, Node *preds[] = nullptr,
Node *preds[] = nullptr, Node *succs[] = nullptr) { Node *succs[] = nullptr) const {
int level_found = -1; 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); Node *node = pred->forward(level);
while (greater(item, node)) pred = 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 *first_preds[32];
Node *second_preds[32]; Node *second_preds[32];
find_path(this, H - 1, first, first_preds, nullptr); find_path(first, first_preds, nullptr);
find_path(this, H - 1, second, second_preds, nullptr); find_path(second, second_preds, nullptr);
for (int i = level; i >= 0; i--) for (int i = level; i >= 0; i--)
if (first_preds[i] == second_preds[i]) skiplist_size /= 2; 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) { while (true) {
auto level = find_path(this, H - 1, data, preds, succs); auto level = find_path(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);
if (level != -1) { if (level != -1) {
auto found = succs[level]; auto found = succs[level];
if (found->flags.is_marked()) continue; if (found->flags.is_marked()) continue;
found->flags.wait_fully_linked();
while (!found->flags.is_fully_linked()) usleep(250);
return {Iterator{succs[level]}, false}; return {Iterator{succs[level]}, false};
} }
@ -996,43 +780,8 @@ class SkipList : private Lockable<lock_t> {
// has the locks // has the locks
if (!lock_nodes<true>(height, guards, preds, succs)) continue; if (!lock_nodes<true>(height, guards, preds, succs)) continue;
return { return {insert_here(Node::create(std::forward<TItem>(data), height),
insert_here(Node::create(data, height), preds, succs, height, guards), 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),
true}; true};
} }
} }
@ -1046,15 +795,13 @@ class SkipList : private Lockable<lock_t> {
std::pair<Iterator, bool> emplace(Node *preds[], Node *succs[], K &key, std::pair<Iterator, bool> emplace(Node *preds[], Node *succs[], K &key,
Args &&... args) { Args &&... args) {
while (true) { while (true) {
// TODO: before here was data.first auto level = find_path(key, preds, succs);
auto level = find_path(this, H - 1, key, preds, succs);
if (level != -1) { if (level != -1) {
auto found = succs[level]; auto found = succs[level];
if (found->flags.is_marked()) continue; if (found->flags.is_marked()) continue;
found->flags.wait_fully_linked();
while (!found->flags.is_fully_linked()) usleep(250);
return {Iterator{succs[level]}, false}; return {Iterator{succs[level]}, false};
} }
@ -1077,9 +824,8 @@ class SkipList : private Lockable<lock_t> {
* Inserts data to specified locked location. * Inserts data to specified locked location.
*/ */
Iterator insert_here(Node *new_node, Node *preds[], Node *succs[], int height, Iterator insert_here(Node *new_node, Node *preds[], Node *succs[], int height,
guard_t guards[]) // TODO: querds unused guard_t guards[]) {
{ // TODO: guards unused
// Node::create(std::move(data), height)
// link the predecessors and successors, e.g. // link the predecessors and successors, e.g.
// //
// 4 HEAD ... P ------------------------> S ... NULL // 4 HEAD ... P ------------------------> S ... NULL
@ -1092,34 +838,25 @@ class SkipList : private Lockable<lock_t> {
} }
new_node->flags.set_fully_linked(); new_node->flags.set_fully_linked();
count.fetch_add(1); count++;
return Iterator{new_node}; 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. * 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> template <class K>
bool remove(const K &item, Node *preds[], Node *succs[], bool remove(const K &item, Node *preds[], Node *succs[]) {
int (*fp)(SkipList *, int, const K &, Node *[], Node *[])) {
Node *node = nullptr; Node *node = nullptr;
guard_t node_guard; guard_t node_guard;
bool marked = false; bool marked = false;
int height = 0; int height = 0;
while (true) { 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; return false;
if (!marked) { if (!marked) {
@ -1142,7 +879,7 @@ class SkipList : private Lockable<lock_t> {
gc.Collect(node); gc.Collect(node);
count.fetch_sub(1); count--;
return true; return true;
} }
} }

View File

@ -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);
}
};

View File

@ -7,8 +7,6 @@
#include "data_structures/bitset/dynamic_bitset.hpp" #include "data_structures/bitset/dynamic_bitset.hpp"
#include "data_structures/concurrent/concurrent_list.hpp" #include "data_structures/concurrent/concurrent_list.hpp"
#include "data_structures/concurrent/concurrent_map.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/concurrent_set.hpp"
#include "data_structures/concurrent/skiplist.hpp" #include "data_structures/concurrent/skiplist.hpp"
#include "data_structures/static_array.hpp" #include "data_structures/static_array.hpp"
@ -27,8 +25,6 @@ using std::cout;
using std::endl; using std::endl;
using map_t = ConcurrentMap<int, int>; using map_t = ConcurrentMap<int, int>;
using set_t = ConcurrentSet<int>; using set_t = ConcurrentSet<int>;
using multiset_t = ConcurrentMultiSet<int>;
using multimap_t = ConcurrentMultiMap<int, int>;
using namespace std::chrono_literals; 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 // Runs given function in threads_no threads and returns vector of futures for
// there // there
// results. // results.

View File

@ -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);
});
}

View File

@ -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);
});
}

View File

@ -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);
});
}

View File

@ -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");
});
}

View File

@ -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");
});
}

View File

@ -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);
}
}