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;
|
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:
|
||||||
|
@ -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; }
|
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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/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.
|
||||||
|
@ -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