Containers: AVL tree node helpers and a map that reuses deleted slots #4
5 changed files with 150 additions and 24 deletions
|
|
@ -45,20 +45,56 @@ void tp::AvlTree<Key, Data, Allocator>::insert(KeyArg key, DataArg data) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template <typename Key, typename Data, class Allocator>
|
||||||
|
auto tp::AvlTree<Key, Data, Allocator>::insertAfter(Node* predecessor, KeyArg key, DataArg data) -> Node* {
|
||||||
|
Node* inserted = newNode(key, data);
|
||||||
|
|
||||||
|
if (mRoot) {
|
||||||
|
attachAfter(predecessor, inserted);
|
||||||
|
}
|
||||||
|
|
||||||
|
restoreInvariants(inserted);
|
||||||
|
return inserted;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename Key, typename Data, class Allocator>
|
||||||
|
void tp::AvlTree<Key, Data, Allocator>::attachAfter(Node* predecessor, Node* inserted) {
|
||||||
|
if (!predecessor) {
|
||||||
|
Node* first = minNode(mRoot);
|
||||||
|
first->mLeft = inserted;
|
||||||
|
inserted->mParent = first;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (predecessor->mRight == &gNullNode) {
|
||||||
|
predecessor->mRight = inserted;
|
||||||
|
inserted->mParent = predecessor;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Node* successor = minNode(predecessor->mRight);
|
||||||
|
successor->mLeft = inserted;
|
||||||
|
inserted->mParent = successor;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename Key, typename Data, class Allocator>
|
||||||
|
void tp::AvlTree<Key, Data, Allocator>::refreshNode(Node* node) {
|
||||||
|
restoreInvariants(node);
|
||||||
|
}
|
||||||
|
|
||||||
template <typename Key, typename Data, class Allocator>
|
template <typename Key, typename Data, class Allocator>
|
||||||
void tp::AvlTree<Key, Data, Allocator>::remove(KeyArg key) {
|
void tp::AvlTree<Key, Data, Allocator>::remove(KeyArg key) {
|
||||||
|
if (auto node = findSubTree(mRoot, key)) {
|
||||||
|
removeNode(node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
auto node = findSubTree(mRoot, key);
|
template <typename Key, typename Data, class Allocator>
|
||||||
|
void tp::AvlTree<Key, Data, Allocator>::removeNode(Node* node) {
|
||||||
if (!node) return;
|
DEBUG_ASSERT(node);
|
||||||
|
|
||||||
if (node->mRight != &gNullNode && node->mLeft != &gNullNode) {
|
if (node->mRight != &gNullNode && node->mLeft != &gNullNode) {
|
||||||
Node* min = minNode(node->mRight);
|
injectNodeInstead(node, minNode(node->mRight));
|
||||||
injectNodeInstead(node, min);
|
|
||||||
std::swap(node, min);
|
|
||||||
// auto const& newKey = min->key.getFindKey(head->mRight);
|
|
||||||
node = findSubTree(node->mRight, key);
|
|
||||||
DEBUG_ASSERT(node);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (node->mRight != &gNullNode) {
|
if (node->mRight != &gNullNode) {
|
||||||
|
|
@ -115,6 +151,28 @@ auto tp::AvlTree<Key, Data, Allocator>::minNode(Node* head) const -> Node* {
|
||||||
return head;
|
return head;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template <typename Key, typename Data, class Allocator>
|
||||||
|
auto tp::AvlTree<Key, Data, Allocator>::next(Node* node) const -> Node* {
|
||||||
|
if (node->mRight != &gNullNode) return minNode(node->mRight);
|
||||||
|
|
||||||
|
while (node->mParent && node->mParent->mRight == node) {
|
||||||
|
node = node->mParent;
|
||||||
|
}
|
||||||
|
|
||||||
|
return node->mParent;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename Key, typename Data, class Allocator>
|
||||||
|
auto tp::AvlTree<Key, Data, Allocator>::prev(Node* node) const -> Node* {
|
||||||
|
if (node->mLeft != &gNullNode) return maxNode(node->mLeft);
|
||||||
|
|
||||||
|
while (node->mParent && node->mParent->mLeft == node) {
|
||||||
|
node = node->mParent;
|
||||||
|
}
|
||||||
|
|
||||||
|
return node->mParent;
|
||||||
|
}
|
||||||
|
|
||||||
template <typename Key, typename Data, class Allocator>
|
template <typename Key, typename Data, class Allocator>
|
||||||
auto tp::AvlTree<Key, Data, Allocator>::find(KeyArg key) const -> Node* {
|
auto tp::AvlTree<Key, Data, Allocator>::find(KeyArg key) const -> Node* {
|
||||||
return findSubTree(mRoot, key);
|
return findSubTree(mRoot, key);
|
||||||
|
|
@ -122,14 +180,16 @@ auto tp::AvlTree<Key, Data, Allocator>::find(KeyArg key) const -> Node* {
|
||||||
|
|
||||||
template <typename Key, typename Data, class Allocator>
|
template <typename Key, typename Data, class Allocator>
|
||||||
auto tp::AvlTree<Key, Data, Allocator>::findSubTree(Node* iter, KeyArg key) const -> Node* {
|
auto tp::AvlTree<Key, Data, Allocator>::findSubTree(Node* iter, KeyArg key) const -> Node* {
|
||||||
|
Key query = key;
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
if (iter == &gNullNode) return nullptr;
|
if (!iter || iter == &gNullNode) return nullptr;
|
||||||
if (iter->key.exactNode(key)) return iter;
|
if (iter->key.exactNode(query)) return iter;
|
||||||
if (iter->key.descentRight(key)) {
|
if (iter->key.descentRight(query)) {
|
||||||
// key = iter->key.keyInRightSubtree(key);
|
query = iter->key.keyInRightSubtree(query);
|
||||||
iter = iter->mRight;
|
iter = iter->mRight;
|
||||||
} else {
|
} else {
|
||||||
// key = iter->key.keyInLeftSubtree(key);
|
query = iter->key.keyInLeftSubtree(query);
|
||||||
iter = iter->mLeft;
|
iter = iter->mLeft;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@
|
||||||
#include <fcntl.h>
|
#include <fcntl.h>
|
||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
namespace lrbm {
|
namespace lrbm {
|
||||||
static int create_memfd(const char* name) {
|
static int create_memfd(const char* name) {
|
||||||
|
|
|
||||||
|
|
@ -50,10 +50,11 @@ namespace tp {
|
||||||
tAllocator mAlloc;
|
tAllocator mAlloc;
|
||||||
Node** mTable = nullptr;
|
Node** mTable = nullptr;
|
||||||
ualni mNSlots = 0;
|
ualni mNSlots = 0;
|
||||||
|
ualni mNDeleted = 0;
|
||||||
ualni mNEntries = 0;
|
ualni mNEntries = 0;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
constexpr halnf maxLoadFactor() { return halnf(MAP_MAX_LOAD_PERCENTAGE) / 100.f; }
|
static constexpr halnf maxLoadFactor() { return halnf(MAP_MAX_LOAD_PERCENTAGE) / 100.f; }
|
||||||
|
|
||||||
inline Node** newTable(const ualni len) { return new (mAlloc.allocate(sizeof(Node*) * len)) Node*[len](); }
|
inline Node** newTable(const ualni len) { return new (mAlloc.allocate(sizeof(Node*) * len)) Node*[len](); }
|
||||||
|
|
||||||
|
|
@ -79,6 +80,7 @@ namespace tp {
|
||||||
mNSlots = next2pow((uhalni) ((1.f / (maxLoadFactor())) * mNEntries + 1));
|
mNSlots = next2pow((uhalni) ((1.f / (maxLoadFactor())) * mNEntries + 1));
|
||||||
mTable = newTable(mNSlots);
|
mTable = newTable(mNSlots);
|
||||||
mNEntries = 0;
|
mNEntries = 0;
|
||||||
|
mNDeleted = 0;
|
||||||
|
|
||||||
for (alni i = 0; i < nSlotsOld; i++) {
|
for (alni i = 0; i < nSlotsOld; i++) {
|
||||||
if (!tableOld[i] || isDeletedNode(tableOld[i])) {
|
if (!tableOld[i] || isDeletedNode(tableOld[i])) {
|
||||||
|
|
@ -142,27 +144,37 @@ namespace tp {
|
||||||
ualni const mask = mNSlots - 1;
|
ualni const mask = mNSlots - 1;
|
||||||
ualni const shift = (hashed_key >> MAP_PERTURB_SHIFT) & ~1;
|
ualni const shift = (hashed_key >> MAP_PERTURB_SHIFT) & ~1;
|
||||||
ualni idx = hashed_key & mask;
|
ualni idx = hashed_key & mask;
|
||||||
|
alni reusable = -1;
|
||||||
NEXT:
|
NEXT:
|
||||||
if (isDeletedNode(mTable[idx]) || !mTable[idx]) {
|
if (!mTable[idx]) {
|
||||||
return idx;
|
return reusable >= 0 ? (ualni) reusable : idx;
|
||||||
}
|
}
|
||||||
if (mTable[idx]->key == key) {
|
if (isDeletedNode(mTable[idx])) {
|
||||||
|
if (reusable < 0) reusable = (alni) idx;
|
||||||
|
} else if (mTable[idx]->key == key) {
|
||||||
return idx;
|
return idx;
|
||||||
}
|
}
|
||||||
idx = ((5 * idx) + 1 + shift) & mask;
|
idx = ((5 * idx) + 1 + shift) & mask;
|
||||||
goto NEXT;
|
goto NEXT;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void occupy(ualni idx) {
|
||||||
|
if (isDeletedNode(mTable[idx])) mNDeleted--;
|
||||||
|
mNEntries++;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] bool overloaded() const { return (halnf) (mNEntries + mNDeleted) / mNSlots > maxLoadFactor(); }
|
||||||
|
|
||||||
void put(Node* node) {
|
void put(Node* node) {
|
||||||
const ualni idx = findSlotWrite(node->key);
|
const ualni idx = findSlotWrite(node->key);
|
||||||
|
|
||||||
if (!mTable[idx] || isDeletedNode(mTable[idx])) {
|
if (!mTable[idx] || isDeletedNode(mTable[idx])) {
|
||||||
mNEntries++;
|
occupy(idx);
|
||||||
}
|
}
|
||||||
|
|
||||||
mTable[idx] = node;
|
mTable[idx] = node;
|
||||||
|
|
||||||
if ((halnf) mNEntries / mNSlots > maxLoadFactor()) {
|
if (overloaded()) {
|
||||||
rehash();
|
rehash();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -186,12 +198,12 @@ namespace tp {
|
||||||
void put(KeyArg key, ValArg val) {
|
void put(KeyArg key, ValArg val) {
|
||||||
const ualni idx = findSlotWrite(key);
|
const ualni idx = findSlotWrite(key);
|
||||||
if (!mTable[idx] || isDeletedNode(mTable[idx])) {
|
if (!mTable[idx] || isDeletedNode(mTable[idx])) {
|
||||||
|
occupy(idx);
|
||||||
mTable[idx] = newNode(key, val);
|
mTable[idx] = newNode(key, val);
|
||||||
mNEntries++;
|
|
||||||
} else {
|
} else {
|
||||||
mTable[idx]->val = val;
|
mTable[idx]->val = val;
|
||||||
}
|
}
|
||||||
if ((halnf) mNEntries / mNSlots > maxLoadFactor()) {
|
if (overloaded()) {
|
||||||
rehash();
|
rehash();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -218,7 +230,9 @@ namespace tp {
|
||||||
markDeletedSlot(idx);
|
markDeletedSlot(idx);
|
||||||
|
|
||||||
mNEntries--;
|
mNEntries--;
|
||||||
if (halnf(mNEntries / mNSlots) < 1.f - maxLoadFactor()) {
|
mNDeleted++;
|
||||||
|
const bool sparse = mNSlots > MAP_MIN_SIZE * 4 && (halnf) mNEntries < (halnf) mNSlots * maxLoadFactor() * 0.25f;
|
||||||
|
if (sparse || overloaded()) {
|
||||||
rehash();
|
rehash();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -261,6 +275,7 @@ namespace tp {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
mNEntries = 0;
|
mNEntries = 0;
|
||||||
|
mNDeleted = 0;
|
||||||
mNSlots = in.mNSlots;
|
mNSlots = in.mNSlots;
|
||||||
deleteTable(mTable);
|
deleteTable(mTable);
|
||||||
mTable = newTable(mNSlots);
|
mTable = newTable(mNSlots);
|
||||||
|
|
@ -297,6 +312,7 @@ namespace tp {
|
||||||
mTable = newTable(tTableInitialSize);
|
mTable = newTable(tTableInitialSize);
|
||||||
mNSlots = tTableInitialSize;
|
mNSlots = tTableInitialSize;
|
||||||
mNEntries = 0;
|
mNEntries = 0;
|
||||||
|
mNDeleted = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
[[nodiscard]] alni slotIdx(alni entry_idx_in) const {
|
[[nodiscard]] alni slotIdx(alni entry_idx_in) const {
|
||||||
|
|
|
||||||
|
|
@ -65,12 +65,18 @@ namespace tp {
|
||||||
Node* head() const;
|
Node* head() const;
|
||||||
|
|
||||||
void insert(KeyArg key, DataArg data);
|
void insert(KeyArg key, DataArg data);
|
||||||
|
Node* insertAfter(Node* predecessor, KeyArg key, DataArg data);
|
||||||
void remove(KeyArg key);
|
void remove(KeyArg key);
|
||||||
|
void removeNode(Node* node);
|
||||||
void removeAll();
|
void removeAll();
|
||||||
|
void refreshNode(Node* node);
|
||||||
|
|
||||||
Node* maxNode(Node* head) const;
|
Node* maxNode(Node* head) const;
|
||||||
Node* minNode(Node* head) const;
|
Node* minNode(Node* head) const;
|
||||||
|
|
||||||
|
Node* next(Node* node) const;
|
||||||
|
Node* prev(Node* node) const;
|
||||||
|
|
||||||
Node* find(KeyArg key) const;
|
Node* find(KeyArg key) const;
|
||||||
Node* findSubTree(Node* iter, KeyArg key) const;
|
Node* findSubTree(Node* iter, KeyArg key) const;
|
||||||
Node* findLessOrEq(KeyArg key) const;
|
Node* findLessOrEq(KeyArg key) const;
|
||||||
|
|
@ -97,6 +103,7 @@ namespace tp {
|
||||||
|
|
||||||
inline auto insertNode(Node* head, KeyArg key, DataArg data) -> Node*;
|
inline auto insertNode(Node* head, KeyArg key, DataArg data) -> Node*;
|
||||||
inline auto findInsertParent(Node* head, KeyArg key) -> Node*;
|
inline auto findInsertParent(Node* head, KeyArg key) -> Node*;
|
||||||
|
inline void attachAfter(Node* predecessor, Node* inserted);
|
||||||
|
|
||||||
inline void injectNodeInstead(Node* target, Node* from);
|
inline void injectNodeInstead(Node* target, Node* from);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,8 @@
|
||||||
#include "Map.hpp"
|
#include "Map.hpp"
|
||||||
#include "Tests.hpp"
|
#include "Tests.hpp"
|
||||||
|
|
||||||
|
#include <chrono>
|
||||||
|
|
||||||
using namespace tp;
|
using namespace tp;
|
||||||
|
|
||||||
SUITE(HashTable) {
|
SUITE(HashTable) {
|
||||||
|
|
@ -121,4 +123,44 @@ SUITE(HashTable) {
|
||||||
|
|
||||||
map.removeAll();
|
map.removeAll();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST(RemovingKeysTakesLinearTime) {
|
||||||
|
tp::Map<tp::ualni, tp::ualni> map;
|
||||||
|
const tp::ualni count = 200000;
|
||||||
|
for (tp::ualni key = 0; key < count; key++)
|
||||||
|
map.put(key, key);
|
||||||
|
const auto start = std::chrono::steady_clock::now();
|
||||||
|
for (tp::ualni key = 0; key < count / 2; key++)
|
||||||
|
map.remove(key);
|
||||||
|
const double ms = std::chrono::duration<double, std::milli>(std::chrono::steady_clock::now() - start).count();
|
||||||
|
CHECK(ms < 200.0);
|
||||||
|
CHECK_EQUAL(count / 2, map.size());
|
||||||
|
for (tp::ualni key = count / 2; key < count; key += 997)
|
||||||
|
CHECK_EQUAL(key, map.get(key));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(MostlyEmptyMapShrinks) {
|
||||||
|
tp::Map<tp::ualni, tp::ualni> map;
|
||||||
|
for (tp::ualni key = 0; key < 4096; key++)
|
||||||
|
map.put(key, key);
|
||||||
|
const tp::ualni slots = map.end();
|
||||||
|
for (tp::ualni key = 0; key < 4000; key++)
|
||||||
|
map.remove(key);
|
||||||
|
CHECK(map.end() < slots);
|
||||||
|
for (tp::ualni key = 4000; key < 4096; key++)
|
||||||
|
CHECK_EQUAL(key, map.get(key));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(LookupsTerminateAfterMassRemoval) {
|
||||||
|
tp::Map<tp::ualni, bool> map;
|
||||||
|
for (tp::ualni key = 0; key < 1000; key++)
|
||||||
|
map.put(key, true);
|
||||||
|
for (tp::ualni key = 0; key < 990; key++)
|
||||||
|
map.remove(key);
|
||||||
|
for (tp::ualni key = 990; key < 1000; key++)
|
||||||
|
map.remove(key);
|
||||||
|
for (tp::ualni key = 0; key < 100; key++)
|
||||||
|
CHECK(!map.presents(key + 5000).isValid());
|
||||||
|
CHECK_EQUAL(0u, map.size());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue