diff --git a/src/data_structures/concurrent/common.hpp b/src/data_structures/concurrent/common.hpp index 81ccd62d2..633c69cc6 100644 --- a/src/data_structures/concurrent/common.hpp +++ b/src/data_structures/concurrent/common.hpp @@ -5,7 +5,7 @@ using std::pair; -// Item stored in skiplist. Used by ConcurrentMap and ConcurrentMultiMap to +// Item stored in skiplist. Used by ConcurrentMap to // store key and value but to make ordering on keys. template class Item : public TotalOrdering>, @@ -72,16 +72,6 @@ class AccessorBase { list_it_con cend() const { return accessor.cend(); } - template - typename SkipList::template MultiIterator end(const K &data) { - return accessor.template mend(data); - } - - template - typename SkipList::template MultiIterator mend(const K &data) { - return accessor.template mend(data); - } - size_t size() const { return accessor.size(); } protected: diff --git a/src/data_structures/concurrent/concurrent_multimap.hpp b/src/data_structures/concurrent/concurrent_multimap.hpp deleted file mode 100644 index 3d9d350ec..000000000 --- a/src/data_structures/concurrent/concurrent_multimap.hpp +++ /dev/null @@ -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 -class ConcurrentMultiMap { - typedef Item item_t; - typedef SkipList list; - typedef typename SkipList::Iterator list_it; - typedef typename SkipList::ConstIterator list_it_con; - typedef typename SkipList::template MultiIterator list_it_multi; - - public: - ConcurrentMultiMap() {} - - class Accessor : public AccessorBase { - friend class ConcurrentMultiMap; - - using AccessorBase::AccessorBase; - - private: - using AccessorBase::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(data))); - } - - list_it insert(K &&key, T &&data) { - return accessor.insert_non_unique( - item_t(std::forward(key), std::forward(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; -}; diff --git a/src/data_structures/concurrent/concurrent_multiset.hpp b/src/data_structures/concurrent/concurrent_multiset.hpp deleted file mode 100644 index 1b4d32092..000000000 --- a/src/data_structures/concurrent/concurrent_multiset.hpp +++ /dev/null @@ -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 ConcurrentMultiSet { - typedef SkipList list; - typedef typename SkipList::Iterator list_it; - typedef typename SkipList::ConstIterator list_it_con; - - public: - ConcurrentMultiSet() {} - - class Accessor : public AccessorBase { - friend class ConcurrentMultiSet; - - using AccessorBase::AccessorBase; - - private: - using AccessorBase::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(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; -}; diff --git a/src/data_structures/concurrent/skiplist.hpp b/src/data_structures/concurrent/skiplist.hpp index cd149fd31..25c3b3044 100644 --- a/src/data_structures/concurrent/skiplist.hpp +++ b/src/data_structures/concurrent/skiplist.hpp @@ -121,6 +121,11 @@ class SkipList : private Lockable { bool is_fully_linked() const { return flags.load() & FULLY_LINKED; } + /** Waits until the these flags don't get the "fully linked" status. */ + void wait_fully_linked() const { + while (!is_fully_linked()) usleep(250); + } + void set_fully_linked() { flags.fetch_or(FULLY_LINKED); } private: @@ -144,20 +149,22 @@ class SkipList : private Lockable { 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 + static Node *create(TItem &&item, uint8_t height) { auto node = allocate(height); // we have raw memory and we need to construct an object // of type Node on it - return new (node) Node(item, height); - } - - static Node *create(T &&item, uint8_t height) { - auto node = allocate(height); - - // we have raw memory and we need to construct an object - // of type Node on it - return new (node) Node(std::move(item), height); + return new (node) Node(std::forward(item), height); } template @@ -169,6 +176,11 @@ class SkipList : private Lockable { return new (node) Node(height, std::forward(args)...); } + bool ok_delete(int level) const { + return flags.is_fully_linked() && height - 1 == level && + !flags.is_marked(); + } + static void destroy(Node *node) { node->~Node(); std::free(node); @@ -422,146 +434,6 @@ class SkipList : private Lockable { Node *preds_[H]; }; - template - class MultiIterator : public Crtp> { - 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( - data, preds, succs, - SkipList::template MultiIterator::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(skiplist, item); it.has_value(); it++) { - if (it.succs[0] == succs[0]) { // Found it - std::copy(it.preds, it.preds + H, preds); - std::copy(it.succs, it.succs + H, succs); - return level_found; - } - } - // Someone removed it - return -1; - } - - const K &data; - SkipList *skiplist; - Node *preds[H], *succs[H]; - }; - SkipList() : header(Node::sentinel(H)) {} ~SkipList() { @@ -616,16 +488,6 @@ class SkipList : private Lockable { ReverseIterator rend() { return skiplist->rend(); } - template - MultiIterator end(const K &data) { - return skiplist->mend(data); - } - - template - MultiIterator mend(const K &data) { - return skiplist->template mend(data); - } - std::pair insert(const T &item) { return skiplist->insert(preds, succs, item); } @@ -639,19 +501,6 @@ class SkipList : private Lockable { return skiplist->emplace(preds, succs, key, std::forward(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(item), preds, succs); - } - - template - MultiIterator find_multi(const K &item) const { - return MultiIterator(this->skiplist, item); - } - template ConstIterator find(const K &item) const { return static_cast(*skiplist).find(item); @@ -684,8 +533,7 @@ class SkipList : private Lockable { template bool remove(const K &item) { - return skiplist->remove(item, preds, succs, - SkipList::template find_path); + return skiplist->remove(item, preds, succs); } size_t size() const { return skiplist->size(); } @@ -735,23 +583,8 @@ class SkipList : private Lockable { ConstIterator cend() const { return ConstIterator(); } - template - MultiIterator end(const K &data) { - return MultiIterator(data); - } - - template - MultiIterator mend(const K &data) { - return MultiIterator(data); - } - size_t size() const { return count.load(); } - template - static bool equal(const K *item, const Node *const node) { - return node && item == node->value(); - } - template static bool greater(const K &item, const Node *const node) { return node && item > node->value(); @@ -786,12 +619,12 @@ class SkipList : private Lockable { } Node *preds[H]; - find_path(this, H - 1, item, preds); + find_path(item, preds); return std::make_pair(ReverseIterator(this, preds[0], preds), true); } template - It find_node(const K &item) { + It find_node(const K &item) const { auto it = find_or_larger(item); if (it.node == nullptr || item == *it) { return std::move(it); @@ -804,7 +637,7 @@ class SkipList : private Lockable { * Returns iterator on searched element or the first larger element. */ template - It find_or_larger(const K &item) { + It find_or_larger(const K &item) const { Node *node, *pred = header; int h = static_cast(pred->height) - 1; @@ -829,13 +662,27 @@ class SkipList : private Lockable { } } + /** + * 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 - static int find_path(SkipList *skiplist, int start, const K &item, - Node *preds[] = nullptr, Node *succs[] = nullptr) { + int find_path(const K &item, Node *preds[] = nullptr, + Node *succs[] = nullptr) const { int level_found = -1; - Node *pred = skiplist->header; + Node *pred = header; - for (int level = start; level >= 0; --level) { + for (int level = H - 1; level >= 0; --level) { Node *node = pred->forward(level); while (greater(item, node)) pred = node, node = pred->forward(level); @@ -871,8 +718,8 @@ class SkipList : private Lockable { Node *first_preds[32]; Node *second_preds[32]; - find_path(this, H - 1, first, first_preds, nullptr); - find_path(this, H - 1, second, second_preds, nullptr); + find_path(first, first_preds, nullptr); + find_path(second, second_preds, nullptr); for (int i = level; i >= 0; i--) if (first_preds[i] == second_preds[i]) skiplist_size /= 2; @@ -902,88 +749,25 @@ class SkipList : private Lockable { } /** - * 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 . 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 + std::pair insert(Node *preds[], Node *succs[], TItem &&data) { while (true) { - auto level = find_path(this, H - 1, data, preds, succs); - - auto height = 1; - if (level != -1) { - auto found = succs[level]; - - if (found->flags.is_marked()) continue; - - if (!found->flags.is_fully_linked()) { - usleep(250); - continue; - } - - // TODO Optimization for avoiding degrading of skiplist to list. - // Somehow this operation is errornus. - // if (found->height == 1) { // To avoid linearization new - // element - // // will be at least 2 height and - // will - // // be added in front. - // height = rnd(H); - // // if (height == 1) height = 2; - // } else { - - // Only level 0 will be used so the rest is irrelevant. - preds[0] = found; - succs[0] = found->forward(0); - - // This maybe isn't necessary - auto next = succs[0]; - if (next != nullptr) { - if (next->flags.is_marked()) continue; - - if (!next->flags.is_fully_linked()) { - usleep(250); - continue; - } - } - } else { - height = rnd(H); - // Optimization which doesn't add any extra locking. - if (height == 1) - height = 2; // Same key list will be skipped more often. - } - - guard_t guards[H]; - - // try to acquire the locks for predecessors up to the height of - // the new node. release the locks and try again if someone else - // has the locks - if (!lock_nodes(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 insert(Node *preds[], Node *succs[], - const T &data) { - while (true) { - // TODO: before here was data.first - auto level = find_path(this, H - 1, data, preds, succs); + auto level = find_path(data, preds, succs); if (level != -1) { auto found = succs[level]; if (found->flags.is_marked()) continue; - - while (!found->flags.is_fully_linked()) usleep(250); + found->flags.wait_fully_linked(); return {Iterator{succs[level]}, false}; } @@ -996,43 +780,8 @@ class SkipList : private Lockable { // has the locks if (!lock_nodes(height, guards, preds, succs)) continue; - return { - insert_here(Node::create(data, height), preds, succs, height, guards), - true}; - } - } - - /** - * Insert unique data - * - * F - type of funct which will create new node if needed. Recieves height - * of node. - */ - std::pair 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(height, guards, preds, succs)) continue; - - return {insert_here(Node::create(std::move(data), height), preds, succs, - height, guards), + return {insert_here(Node::create(std::forward(data), height), + preds, succs, height, guards), true}; } } @@ -1046,15 +795,13 @@ class SkipList : private Lockable { std::pair emplace(Node *preds[], Node *succs[], K &key, Args &&... args) { while (true) { - // TODO: before here was data.first - auto level = find_path(this, H - 1, key, preds, succs); + auto level = find_path(key, preds, succs); if (level != -1) { auto found = succs[level]; if (found->flags.is_marked()) continue; - - while (!found->flags.is_fully_linked()) usleep(250); + found->flags.wait_fully_linked(); return {Iterator{succs[level]}, false}; } @@ -1077,9 +824,8 @@ class SkipList : private Lockable { * Inserts data to specified locked location. */ Iterator insert_here(Node *new_node, Node *preds[], Node *succs[], int height, - guard_t guards[]) // TODO: querds unused - { - // Node::create(std::move(data), height) + guard_t guards[]) { + // TODO: guards unused // link the predecessors and successors, e.g. // // 4 HEAD ... P ------------------------> S ... NULL @@ -1092,34 +838,25 @@ class SkipList : private Lockable { } new_node->flags.set_fully_linked(); - count.fetch_add(1); + count++; return Iterator{new_node}; } - static bool ok_delete(Node *node, int level) { - return node->flags.is_fully_linked() && node->height - 1 == level && - !node->flags.is_marked(); - } - /** * Removes item found with fp with arguments skiplist, preds and succs. - * fp has to fill preds and succs which reflect location of item or return - * -1 as in not found otherwise returns level on which the item was first - * found. */ template - bool remove(const K &item, Node *preds[], Node *succs[], - int (*fp)(SkipList *, int, const K &, Node *[], Node *[])) { + bool remove(const K &item, Node *preds[], Node *succs[]) { Node *node = nullptr; guard_t node_guard; bool marked = false; int height = 0; while (true) { - auto level = fp(this, H - 1, item, preds, succs); + auto level = find_path(item, preds, succs); - if (!marked && (level == -1 || !ok_delete(succs[level], level))) + if (!marked && (level == -1 || !succs[level]->ok_delete(level))) return false; if (!marked) { @@ -1142,7 +879,7 @@ class SkipList : private Lockable { gc.Collect(node); - count.fetch_sub(1); + count--; return true; } } diff --git a/src/data_structures/map/rh_hashmultimap.hpp b/src/data_structures/map/rh_hashmultimap.hpp deleted file mode 100644 index 85c332e6c..000000000 --- a/src/data_structures/map/rh_hashmultimap.hpp +++ /dev/null @@ -1,350 +0,0 @@ -#pragma mark - -#include -#include -#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 RhHashMultiMap : public RhBase { - typedef RhBase 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 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(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(); - } - - 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(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 end)) || - (end < start && p <= start && p > end); - } -}; diff --git a/tests/concurrent/common.h b/tests/concurrent/common.h index 4173eb0ce..48766c9f4 100644 --- a/tests/concurrent/common.h +++ b/tests/concurrent/common.h @@ -7,8 +7,6 @@ #include "data_structures/bitset/dynamic_bitset.hpp" #include "data_structures/concurrent/concurrent_list.hpp" #include "data_structures/concurrent/concurrent_map.hpp" -#include "data_structures/concurrent/concurrent_multimap.hpp" -#include "data_structures/concurrent/concurrent_multiset.hpp" #include "data_structures/concurrent/concurrent_set.hpp" #include "data_structures/concurrent/skiplist.hpp" #include "data_structures/static_array.hpp" @@ -27,8 +25,6 @@ using std::cout; using std::endl; using map_t = ConcurrentMap; using set_t = ConcurrentSet; -using multiset_t = ConcurrentMultiSet; -using multimap_t = ConcurrentMultiMap; using namespace std::chrono_literals; @@ -129,37 +125,6 @@ void check_set(DynamicBitset<> &db, std::vector &set) { } } -// Checks multiIterator and iterator guarantees -void check_multi_iterator(multimap_t::Accessor &accessor, size_t key_range, - long set[]) { - for (int i = 0; i < key_range; i++) { - auto it = accessor.find(i); - auto it_m = accessor.find_multi(i); - permanent_assert(!(it_m != accessor.end(i) && it == accessor.end()), - "MultiIterator ended before Iterator. Set: " << set[i]); - permanent_assert(!(it_m == accessor.end(i) && it != accessor.end()), - "Iterator ended before MultiIterator. Set: " << set[i]); - permanent_assert((it_m == accessor.end(i) && it == accessor.end()) || - it->second == it_m->second, - "MultiIterator didn't found the same " - "first element. Set: " - << set[i]); - if (set[i] > 0) { - for (int j = 0; j < set[i]; j++) { - permanent_assert(it->second == it_m->second, - "MultiIterator and iterator aren't on the same " - "element."); - permanent_assert(it_m->first == i, - "MultiIterator is showing illegal data") it++; - it_m++; - } - } - permanent_assert(it_m == accessor.end(i), - "There is more data than it should be in MultiIterator. " - << it_m->first << "\n"); - } -} - // Runs given function in threads_no threads and returns vector of futures for // there // results. diff --git a/tests/concurrent/sl_multiiterator.cpp b/tests/concurrent/sl_multiiterator.cpp deleted file mode 100644 index 406bc74ce..000000000 --- a/tests/concurrent/sl_multiiterator.cpp +++ /dev/null @@ -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>( - 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 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(accessor); - }); -} diff --git a/tests/concurrent/sl_multiiterator_remove.cpp b/tests/concurrent/sl_multiiterator_remove.cpp deleted file mode 100644 index 14dce18b1..000000000 --- a/tests/concurrent/sl_multiiterator_remove.cpp +++ /dev/null @@ -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>>( - 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 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>(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(accessor); - }); -} diff --git a/tests/concurrent/sl_multiiterator_remove_duplicates.cpp b/tests/concurrent/sl_multiiterator_remove_duplicates.cpp deleted file mode 100644 index 083832e1a..000000000 --- a/tests/concurrent/sl_multiiterator_remove_duplicates.cpp +++ /dev/null @@ -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>>( - 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 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>(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(accessor); - }); -} diff --git a/tests/concurrent/sl_multimap.cpp b/tests/concurrent/sl_multimap.cpp deleted file mode 100644 index c794f3537..000000000 --- a/tests/concurrent/sl_multimap.cpp +++ /dev/null @@ -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 size(0); - - auto futures = run>>( - 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 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>(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(accessor, size); - check_order(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"); - }); -} diff --git a/tests/concurrent/sl_multiset.cpp b/tests/concurrent/sl_multiset.cpp deleted file mode 100644 index bdccc0947..000000000 --- a/tests/concurrent/sl_multiset.cpp +++ /dev/null @@ -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>(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 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"); - - }); -} diff --git a/tests/unit/rh_hashmultimap.cpp b/tests/unit/rh_hashmultimap.cpp deleted file mode 100644 index f324a07bf..000000000 --- a/tests/unit/rh_hashmultimap.cpp +++ /dev/null @@ -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 &map, - std::multimap &s_map); - -void cross_validate_weak(RhHashMultiMap &map, - std::multimap &s_map); - -TEST(RobinHoodHashmultimap, BasicFunctionality) { - RhHashMultiMap map; - - ASSERT_EQ(map.size(), 0); - Data d0(0); - map.add(&d0); - ASSERT_EQ(map.size(), 1); -} - -TEST(RobinHoodHashmultimap, InsertGetCheck) { - RhHashMultiMap 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 map; - - std::vector> di; - di.reserve(128); - for (int i = 0; i < 128; i++) { - di.emplace_back(std::make_unique(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 map; - - std::vector> di; - di.reserve(128); - for (int i = 0; i < 127; i++) { - di.emplace_back(std::make_unique(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 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 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 map; - - std::vector> di; - di.reserve(128); - for (int i = 0; i < 128; i++) { - ASSERT_EQ(map.find(i), map.end()); - di.emplace_back(std::make_unique(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 map; - - std::vector> di; - di.reserve(128); - for (int i = 0; i < 128; i++) { - ASSERT_EQ(map.find(i), map.end()); - di.emplace_back(std::make_unique(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 map; - std::multimap s_map; - - std::vector> di; - std::vector 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(key)); - } - - for (int i = 0; i < 1638; i++) { - map.add(di[i].get()); - s_map.insert(std::pair(keys[i], di[i].get())); - } - cross_validate(map, s_map); -} - -TEST(RobinHoodHashmultimap, CheckedRand) { - RhHashMultiMap map; - std::multimap s_map; - std::srand(std::time(0)); - - std::vector> di; - std::vector 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(key)); - } - - for (int i = 0; i < 164308; i++) { - map.add(di[i].get()); - s_map.insert(std::pair(keys[i], di[i].get())); - } - cross_validate(map, s_map); -} - -TEST(RobinHoodHashmultimap, WithRemoveDataChecked) { - RhHashMultiMap map; - std::multimap s_map; - - std::srand(std::time(0)); - std::vector> 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(key)); - map.add(di.back().get()); - s_map.insert(std::pair(key, di.back().get())); - } - } - - cross_validate(map, s_map); -} - -TEST(RobinhoodHashmultimap, AlignmentCheck) { - RhHashMultiMap map; - char *block = static_cast(std::malloc(20)); - ++block; // not alligned - offset 1 - EXPECT_DEATH(map.add((Data *)(block)), "not 8-alligned"); -} - -void cross_validate(RhHashMultiMap &map, - std::multimap &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 &map, - std::multimap &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); - } -}