Containers: AVL tree node helpers and a map that reuses deleted slots #4

Open
agent wants to merge 3 commits from Containers into master
5 changed files with 150 additions and 24 deletions

View file

@ -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>
void tp::AvlTree<Key, Data, Allocator>::remove(KeyArg key) {
if (auto node = findSubTree(mRoot, key)) {
removeNode(node);
}
}
auto node = findSubTree(mRoot, key);
if (!node) return;
template <typename Key, typename Data, class Allocator>
void tp::AvlTree<Key, Data, Allocator>::removeNode(Node* node) {
DEBUG_ASSERT(node);
if (node->mRight != &gNullNode && node->mLeft != &gNullNode) {
Node* min = 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);
injectNodeInstead(node, minNode(node->mRight));
}
if (node->mRight != &gNullNode) {
@ -115,6 +151,28 @@ auto tp::AvlTree<Key, Data, Allocator>::minNode(Node* head) const -> Node* {
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>
auto tp::AvlTree<Key, Data, Allocator>::find(KeyArg key) const -> Node* {
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>
auto tp::AvlTree<Key, Data, Allocator>::findSubTree(Node* iter, KeyArg key) const -> Node* {
Key query = key;
while (true) {
if (iter == &gNullNode) return nullptr;
if (iter->key.exactNode(key)) return iter;
if (iter->key.descentRight(key)) {
// key = iter->key.keyInRightSubtree(key);
if (!iter || iter == &gNullNode) return nullptr;
if (iter->key.exactNode(query)) return iter;
if (iter->key.descentRight(query)) {
query = iter->key.keyInRightSubtree(query);
iter = iter->mRight;
} else {
// key = iter->key.keyInLeftSubtree(key);
query = iter->key.keyInLeftSubtree(query);
iter = iter->mLeft;
}
}

View file

@ -5,6 +5,7 @@
#include <fcntl.h>
#include <stdexcept>
#include <cstring>
#include <utility>
namespace lrbm {
static int create_memfd(const char* name) {

View file

@ -50,10 +50,11 @@ namespace tp {
tAllocator mAlloc;
Node** mTable = nullptr;
ualni mNSlots = 0;
ualni mNDeleted = 0;
ualni mNEntries = 0;
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](); }
@ -79,6 +80,7 @@ namespace tp {
mNSlots = next2pow((uhalni) ((1.f / (maxLoadFactor())) * mNEntries + 1));
mTable = newTable(mNSlots);
mNEntries = 0;
mNDeleted = 0;
for (alni i = 0; i < nSlotsOld; i++) {
if (!tableOld[i] || isDeletedNode(tableOld[i])) {
@ -142,27 +144,37 @@ namespace tp {
ualni const mask = mNSlots - 1;
ualni const shift = (hashed_key >> MAP_PERTURB_SHIFT) & ~1;
ualni idx = hashed_key & mask;
alni reusable = -1;
NEXT:
if (isDeletedNode(mTable[idx]) || !mTable[idx]) {
return idx;
if (!mTable[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;
}
idx = ((5 * idx) + 1 + shift) & mask;
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) {
const ualni idx = findSlotWrite(node->key);
if (!mTable[idx] || isDeletedNode(mTable[idx])) {
mNEntries++;
occupy(idx);
}
mTable[idx] = node;
if ((halnf) mNEntries / mNSlots > maxLoadFactor()) {
if (overloaded()) {
rehash();
}
}
@ -186,12 +198,12 @@ namespace tp {
void put(KeyArg key, ValArg val) {
const ualni idx = findSlotWrite(key);
if (!mTable[idx] || isDeletedNode(mTable[idx])) {
occupy(idx);
mTable[idx] = newNode(key, val);
mNEntries++;
} else {
mTable[idx]->val = val;
}
if ((halnf) mNEntries / mNSlots > maxLoadFactor()) {
if (overloaded()) {
rehash();
}
}
@ -218,7 +230,9 @@ namespace tp {
markDeletedSlot(idx);
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();
}
}
@ -261,6 +275,7 @@ namespace tp {
}
}
mNEntries = 0;
mNDeleted = 0;
mNSlots = in.mNSlots;
deleteTable(mTable);
mTable = newTable(mNSlots);
@ -297,6 +312,7 @@ namespace tp {
mTable = newTable(tTableInitialSize);
mNSlots = tTableInitialSize;
mNEntries = 0;
mNDeleted = 0;
}
[[nodiscard]] alni slotIdx(alni entry_idx_in) const {

View file

@ -65,12 +65,18 @@ namespace tp {
Node* head() const;
void insert(KeyArg key, DataArg data);
Node* insertAfter(Node* predecessor, KeyArg key, DataArg data);
void remove(KeyArg key);
void removeNode(Node* node);
void removeAll();
void refreshNode(Node* node);
Node* maxNode(Node* head) const;
Node* minNode(Node* head) const;
Node* next(Node* node) const;
Node* prev(Node* node) const;
Node* find(KeyArg key) const;
Node* findSubTree(Node* iter, 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 findInsertParent(Node* head, KeyArg key) -> Node*;
inline void attachAfter(Node* predecessor, Node* inserted);
inline void injectNodeInstead(Node* target, Node* from);

View file

@ -2,6 +2,8 @@
#include "Map.hpp"
#include "Tests.hpp"
#include <chrono>
using namespace tp;
SUITE(HashTable) {
@ -121,4 +123,44 @@ SUITE(HashTable) {
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());
}
}