From fac7968405f5a1ca8ec086e4643b671fc4bb19cc Mon Sep 17 00:00:00 2001 From: Andrew Noyes Date: Mon, 11 Mar 2024 17:11:37 -0700 Subject: [PATCH] Bound memory, and disable free list for now CC #9 --- ConflictSet.cpp | 149 ++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 138 insertions(+), 11 deletions(-) diff --git a/ConflictSet.cpp b/ConflictSet.cpp index 74061ad..87cad89 100644 --- a/ConflictSet.cpp +++ b/ConflictSet.cpp @@ -275,8 +275,8 @@ static_assert(sizeof(Node16) < kMinChildrenNode16 * kBytesPerKey); static_assert(sizeof(Node4) < kMinChildrenNode4 * kBytesPerKey); static_assert(sizeof(Node0) < kBytesPerKey); -template +// TODO revive freeList? It's making bounding memory usage tricky +template struct BoundedFreeListAllocator { static_assert(sizeof(T) >= sizeof(void *)); static_assert(std::derived_from); @@ -464,6 +464,8 @@ int64_t getChildMaxVersion(Node *self, uint8_t index) { // Precondition - an entry for index must exist in the node int64_t &maxVersion(Node *n, ConflictSet::Impl *); +Node *&getRoot(ConflictSet::Impl *); + Node *getChild(Node *self, uint8_t index) { if (self->type <= Type::Node16) { auto *self16 = static_cast(self); @@ -711,8 +713,107 @@ Node *&getOrCreateChild(Node *&self, uint8_t index, } } +namespace { +std::string getSearchPathPrintable(Node *n); +} + +void maybeDecreaseCapacity(Node *&self, Node *&root, + NodeAllocators *allocators) { + if (self->numChildren > 0 && + self->numChildren * self->partialKeyLen >= self->partialKeyCapacity) { + return; + } + + int newCapacity = + std::max(self->partialKeyLen, self->numChildren * self->partialKeyLen); + + switch (self->type) { + case Type::Node0: { + auto *self0 = (Node0 *)self; + auto *newSelf = allocators->node0.allocate(newCapacity); + memcpy((char *)newSelf + kNodeCopyBegin, (char *)self + kNodeCopyBegin, + kNodeCopySize); + memcpy(newSelf->partialKey(), self0->partialKey(), self->partialKeyLen); + (self->parent ? getChildExists(self->parent, self->parentsIndex) : root) = + newSelf; + allocators->node0.release(self0); + self = newSelf; + } break; + case Type::Node4: { + auto *self4 = (Node4 *)self; + auto *newSelf = allocators->node4.allocate(newCapacity); + memcpy((char *)newSelf + kNodeCopyBegin, (char *)self + kNodeCopyBegin, + kNodeCopySize); + memcpy(newSelf->partialKey(), self4->partialKey(), self->partialKeyLen); + // TODO replace with memcpy? + for (int i = 0; i < 4; ++i) { + newSelf->index[i] = self4->index[i]; + newSelf->children[i] = self4->children[i]; + } + (self->parent ? getChildExists(self->parent, self->parentsIndex) : root) = + newSelf; + setChildrenParents(newSelf); + allocators->node4.release(self4); + self = newSelf; + } break; + case Type::Node16: { + auto *self16 = (Node16 *)self; + auto *newSelf = allocators->node16.allocate(newCapacity); + memcpy((char *)newSelf + kNodeCopyBegin, (char *)self + kNodeCopyBegin, + kNodeCopySize); + memcpy(newSelf->partialKey(), self16->partialKey(), self->partialKeyLen); + // TODO replace with memcpy? + for (int i = 0; i < 16; ++i) { + newSelf->index[i] = self16->index[i]; + newSelf->children[i] = self16->children[i]; + } + (self->parent ? getChildExists(self->parent, self->parentsIndex) : root) = + newSelf; + setChildrenParents(newSelf); + allocators->node16.release(self16); + self = newSelf; + } break; + case Type::Node48: { + auto *self48 = (Node48 *)self; + auto *newSelf = allocators->node48.allocate(newCapacity); + memcpy((char *)newSelf + kNodeCopyBegin, (char *)self + kNodeCopyBegin, + kNodeCopySize); + memcpy(newSelf->partialKey(), self48->partialKey(), self->partialKeyLen); + newSelf->bitSet = self48->bitSet; + newSelf->bitSet.forEachInRange( + [&](int c) { + int index = newSelf->nextFree; + newSelf->index[c] = index; + newSelf->children[index] = self48->children[self48->index[c]]; + ++newSelf->nextFree; + }, + 0, 256); + (self->parent ? getChildExists(self->parent, self->parentsIndex) : root) = + newSelf; + setChildrenParents(newSelf); + allocators->node48.release(self48); + self = newSelf; + } break; + case Type::Node256: { + auto *self256 = (Node256 *)self; + auto *newSelf = allocators->node256.allocate(newCapacity); + memcpy((char *)newSelf + kNodeCopyBegin, (char *)self + kNodeCopyBegin, + kNodeCopySize); + memcpy(newSelf->partialKey(), self256->partialKey(), self->partialKeyLen); + newSelf->bitSet = self256->bitSet; + newSelf->bitSet.forEachInRange( + [&](int c) { newSelf->children[c] = self256->children[c]; }, 0, 256); + (self->parent ? getChildExists(self->parent, self->parentsIndex) : root) = + newSelf; + setChildrenParents(newSelf); + allocators->node256.release(self256); + self = newSelf; + } break; + } +} + // TODO fuse into erase child so we don't need to repeat branches on type -void maybeDownsize(Node *self, Node *&root, NodeAllocators *allocators, +void maybeDownsize(Node *&self, Node *&root, NodeAllocators *allocators, ConflictSet::Impl *impl) { switch (self->type) { case Type::Node0: @@ -732,6 +833,7 @@ void maybeDownsize(Node *self, Node *&root, NodeAllocators *allocators, } allocators->node4.release(self4); + self = newSelf; } else if (self->numChildren == 1) { if (!self->entryPresent) { auto *child = self4->children[0].child; @@ -773,6 +875,7 @@ void maybeDownsize(Node *self, Node *&root, NodeAllocators *allocators, maxVersion(child, impl) = childMaxVersion; allocators->node4.release(self4); + self = child; } } } break; @@ -795,6 +898,7 @@ void maybeDownsize(Node *self, Node *&root, NodeAllocators *allocators, } else { getChildExists(newSelf->parent, newSelf->parentsIndex) = newSelf; } + self = newSelf; } break; case Type::Node48: @@ -821,6 +925,7 @@ void maybeDownsize(Node *self, Node *&root, NodeAllocators *allocators, } else { getChildExists(newSelf->parent, newSelf->parentsIndex) = newSelf; } + self = newSelf; } break; case Type::Node256: @@ -847,6 +952,7 @@ void maybeDownsize(Node *self, Node *&root, NodeAllocators *allocators, } else { getChildExists(newSelf->parent, newSelf->parentsIndex) = newSelf; } + self = newSelf; } break; } @@ -907,6 +1013,7 @@ void eraseChild(Node *self, uint8_t index, NodeAllocators *allocators, eraseChild(self->parent, self->parentsIndex, allocators, root, impl); } else { maybeDownsize(self, root, allocators, impl); + maybeDecreaseCapacity(self, root, allocators); } } @@ -1131,10 +1238,6 @@ struct SearchStepWise { } }; -namespace { -std::string getSearchPathPrintable(Node *n); -} - // Logically this is the same as performing firstGeq and then checking against // point or range version according to cmp, but this version short circuits as // soon as it can prove that there's no conflict. @@ -1750,6 +1853,19 @@ template memmove(old->partialKey(), old->partialKey() + partialKeyIndex + 1, old->partialKeyLen - (partialKeyIndex + 1)); old->partialKeyLen -= partialKeyIndex + 1; + + [[maybe_unused]] int oldCap = old->partialKeyCapacity; + maybeDecreaseCapacity(old, getRoot(impl), allocators); + +#if DEBUG_VERBOSE && !defined(NDEBUG) + if (old->partialKeyCapacity < oldCap) { + fprintf(stderr, + "%s: Length: %d, capacity: %d, numChildren: %d, oldCapacity: " + "%d\n", + getSearchPathPrintable(old).c_str(), old->partialKeyLen, + old->partialKeyCapacity, old->numChildren, oldCap); + } +#endif } key = key.subspan(partialKeyIndex, key.size() - partialKeyIndex); @@ -2106,6 +2222,8 @@ int64_t &maxVersion(Node *n, ConflictSet::Impl *impl) { } } +Node *&getRoot(ConflictSet::Impl *impl) { return impl->root; } + // ==================== END IMPLEMENTATION ==================== // GCOVR_EXCL_START @@ -2377,7 +2495,7 @@ Iterator firstGeq(Node *n, std::string_view key) { return total; } -[[maybe_unused]] void checkMinChildCount(Node *node, bool &success) { +[[maybe_unused]] void checkMemoryBoundInvariants(Node *node, bool &success) { int minNumChildren; switch (node->type) { case Type::Node0: @@ -2397,16 +2515,25 @@ Iterator firstGeq(Node *n, std::string_view key) { break; } if (node->numChildren < minNumChildren) { - Arena arena; fprintf(stderr, "%s has %d children, which is less than the minimum required %d\n", getSearchPathPrintable(node).c_str(), node->numChildren, minNumChildren); success = false; } + if (node->numChildren > 0 && + node->numChildren * node->partialKeyLen < node->partialKeyCapacity) { + fprintf(stderr, + "%s has %d children, partial key length %d, and partial key " + "capacity %d. It's required that nodes with children have children " + "* length >= capacity\n", + getSearchPathPrintable(node).c_str(), node->numChildren, + node->partialKeyLen, node->partialKeyCapacity); + success = false; + } for (int i = getChildGeq(node, 0); i >= 0; i = getChildGeq(node, i + 1)) { auto *child = getChildExists(node, i); - checkMinChildCount(child, success); + checkMemoryBoundInvariants(child, success); } } @@ -2417,7 +2544,7 @@ bool checkCorrectness(Node *node, int64_t oldestVersion, checkParentPointers(node, success); checkMaxVersion(node, node, oldestVersion, success, impl); checkEntriesExist(node, success); - checkMinChildCount(node, success); + checkMemoryBoundInvariants(node, success); return success; }