4 Commits

Author SHA1 Message Date
5371c2bede Change kMinChildrenNode4 to 2, fixing the induction
All checks were successful
Tests / Release [gcc] total: 932, passed: 932
GNU C Compiler (gcc) |Total|New|Outstanding|Fixed|Trend |:-:|:-:|:-:|:-:|:-: |0|0|0|0|:clap:
Tests / Release [gcc,aarch64] total: 931, passed: 931
Tests / Coverage total: 930, passed: 930
weaselab/conflict-set/pipeline/head This commit looks good
2024-03-12 16:34:20 -07:00
75c304bbe7 WIP my understanding of the memory bound induction was wrong 2024-03-12 15:56:34 -07:00
aefb83dbc6 Prepare for erase to invalidate children of parent 2024-03-12 15:54:21 -07:00
b0ac7e41b9 Update corpus 2024-03-12 14:52:43 -07:00
578 changed files with 80 additions and 47 deletions

View File

@@ -259,19 +259,26 @@ struct Node256 : Node {
uint8_t *partialKey() { return (uint8_t *)(this + 1); } uint8_t *partialKey() { return (uint8_t *)(this + 1); }
}; };
namespace {
std::string getSearchPathPrintable(Node *n);
}
// Bound memory usage following the analysis in the ART paper // Bound memory usage following the analysis in the ART paper
constexpr int kBytesPerKey = 121; constexpr int kBytesPerKey = 120;
constexpr int kMinChildrenNode4 = 1; constexpr int kMinChildrenNode4 = 2;
constexpr int kMinChildrenNode16 = 5; constexpr int kMinChildrenNode16 = 5;
constexpr int kMinChildrenNode48 = 17; constexpr int kMinChildrenNode48 = 17;
constexpr int kMinChildrenNode256 = 49; constexpr int kMinChildrenNode256 = 49;
static_assert(sizeof(Node256) < kMinChildrenNode256 * kBytesPerKey); static_assert(sizeof(Node256) + kBytesPerKey <=
static_assert(sizeof(Node48) < kMinChildrenNode48 * kBytesPerKey); kMinChildrenNode256 * kBytesPerKey);
static_assert(sizeof(Node16) < kMinChildrenNode16 * kBytesPerKey); static_assert(sizeof(Node48) + kBytesPerKey <=
static_assert(sizeof(Node4) < kMinChildrenNode4 * kBytesPerKey); kMinChildrenNode48 * kBytesPerKey);
static_assert(sizeof(Node0) < kBytesPerKey); static_assert(sizeof(Node16) + kBytesPerKey <=
kMinChildrenNode16 * kBytesPerKey);
static_assert(sizeof(Node4) + kBytesPerKey <= kMinChildrenNode4 * kBytesPerKey);
static_assert(sizeof(Node0) <= kBytesPerKey);
// setOldestVersion will additionally try to maintain this property: // setOldestVersion will additionally try to maintain this property:
// `max(children, 1) * length >= capacity` // `max(children, 1) * length >= capacity`
@@ -736,21 +743,14 @@ Node *nextLogical(Node *node) {
return node; return node;
} }
// Fix larger-than-desired capacities. Does not return nodes to freelists, // Invalidates `self`, replacing it with a node of at least capacity.
// since that wouldn't actually reclaim the memory used for partial key // Does not return nodes to freelists.
// capacity. void makeCapacityAtLeast(Node *&self, int capacity, NodeAllocators *allocators,
void maybeDecreaseCapacity(Node *&self, NodeAllocators *allocators, ConflictSet::Impl *impl) {
ConflictSet::Impl *impl) {
const int maxCapacity =
std::max<int>(self->numChildren, 1) * self->partialKeyLen;
if (self->partialKeyCapacity <= maxCapacity) {
return;
}
switch (self->type) { switch (self->type) {
case Type::Node0: { case Type::Node0: {
auto *self0 = (Node0 *)self; auto *self0 = (Node0 *)self;
auto *newSelf = allocators->node0.allocate(maxCapacity); auto *newSelf = allocators->node0.allocate(capacity);
memcpy((char *)newSelf + kNodeCopyBegin, (char *)self + kNodeCopyBegin, memcpy((char *)newSelf + kNodeCopyBegin, (char *)self + kNodeCopyBegin,
kNodeCopySize); kNodeCopySize);
memcpy(newSelf->partialKey(), self0->partialKey(), self->partialKeyLen); memcpy(newSelf->partialKey(), self0->partialKey(), self->partialKeyLen);
@@ -760,7 +760,7 @@ void maybeDecreaseCapacity(Node *&self, NodeAllocators *allocators,
} break; } break;
case Type::Node4: { case Type::Node4: {
auto *self4 = (Node4 *)self; auto *self4 = (Node4 *)self;
auto *newSelf = allocators->node4.allocate(maxCapacity); auto *newSelf = allocators->node4.allocate(capacity);
memcpy((char *)newSelf + kNodeCopyBegin, (char *)self + kNodeCopyBegin, memcpy((char *)newSelf + kNodeCopyBegin, (char *)self + kNodeCopyBegin,
kNodeCopySize); kNodeCopySize);
memcpy(newSelf->partialKey(), self4->partialKey(), self->partialKeyLen); memcpy(newSelf->partialKey(), self4->partialKey(), self->partialKeyLen);
@@ -776,7 +776,7 @@ void maybeDecreaseCapacity(Node *&self, NodeAllocators *allocators,
} break; } break;
case Type::Node16: { case Type::Node16: {
auto *self16 = (Node16 *)self; auto *self16 = (Node16 *)self;
auto *newSelf = allocators->node16.allocate(maxCapacity); auto *newSelf = allocators->node16.allocate(capacity);
memcpy((char *)newSelf + kNodeCopyBegin, (char *)self + kNodeCopyBegin, memcpy((char *)newSelf + kNodeCopyBegin, (char *)self + kNodeCopyBegin,
kNodeCopySize); kNodeCopySize);
memcpy(newSelf->partialKey(), self16->partialKey(), self->partialKeyLen); memcpy(newSelf->partialKey(), self16->partialKey(), self->partialKeyLen);
@@ -792,7 +792,7 @@ void maybeDecreaseCapacity(Node *&self, NodeAllocators *allocators,
} break; } break;
case Type::Node48: { case Type::Node48: {
auto *self48 = (Node48 *)self; auto *self48 = (Node48 *)self;
auto *newSelf = allocators->node48.allocate(maxCapacity); auto *newSelf = allocators->node48.allocate(capacity);
memcpy((char *)newSelf + kNodeCopyBegin, (char *)self + kNodeCopyBegin, memcpy((char *)newSelf + kNodeCopyBegin, (char *)self + kNodeCopyBegin,
kNodeCopySize); kNodeCopySize);
memcpy(newSelf->partialKey(), self48->partialKey(), self->partialKeyLen); memcpy(newSelf->partialKey(), self48->partialKey(), self->partialKeyLen);
@@ -812,7 +812,7 @@ void maybeDecreaseCapacity(Node *&self, NodeAllocators *allocators,
} break; } break;
case Type::Node256: { case Type::Node256: {
auto *self256 = (Node256 *)self; auto *self256 = (Node256 *)self;
auto *newSelf = allocators->node256.allocate(maxCapacity); auto *newSelf = allocators->node256.allocate(capacity);
memcpy((char *)newSelf + kNodeCopyBegin, (char *)self + kNodeCopyBegin, memcpy((char *)newSelf + kNodeCopyBegin, (char *)self + kNodeCopyBegin,
kNodeCopySize); kNodeCopySize);
memcpy(newSelf->partialKey(), self256->partialKey(), self->partialKeyLen); memcpy(newSelf->partialKey(), self256->partialKey(), self->partialKeyLen);
@@ -827,9 +827,27 @@ void maybeDecreaseCapacity(Node *&self, NodeAllocators *allocators,
} }
} }
// Fix larger-than-desired capacities. Does not return nodes to freelists,
// since that wouldn't actually reclaim the memory used for partial key
// capacity.
void maybeDecreaseCapacity(Node *&self, NodeAllocators *allocators,
ConflictSet::Impl *impl) {
const int maxCapacity =
std::max<int>(self->numChildren, 1) * self->partialKeyLen;
if (self->partialKeyCapacity <= maxCapacity) {
return;
}
makeCapacityAtLeast(self, maxCapacity, allocators, impl);
}
// TODO fuse into erase child so we don't need to repeat branches on type // TODO fuse into erase child so we don't need to repeat branches on type
void maybeDownsize(Node *self, NodeAllocators *allocators, void maybeDownsize(Node *self, NodeAllocators *allocators,
ConflictSet::Impl *impl) { ConflictSet::Impl *impl, Node *&dontInvalidate) {
#if DEBUG_VERBOSE && !defined(NDEBUG)
fprintf(stderr, "maybeDownsize: %s\n", getSearchPathPrintable(self).c_str());
#endif
switch (self->type) { switch (self->type) {
case Type::Node0: case Type::Node0:
__builtin_unreachable(); // GCOVR_EXCL_LINE __builtin_unreachable(); // GCOVR_EXCL_LINE
@@ -849,11 +867,11 @@ void maybeDownsize(Node *self, NodeAllocators *allocators,
int minCapacity = self4->partialKeyLen + 1 + child->partialKeyLen; int minCapacity = self4->partialKeyLen + 1 + child->partialKeyLen;
if (minCapacity > child->partialKeyCapacity) { if (minCapacity > child->partialKeyCapacity) {
// TODO resize child? It seems to be quite challenging to implement, const bool update = child == dontInvalidate;
// since callers would now have to account for erase invalidating makeCapacityAtLeast(child, minCapacity, allocators, impl);
// not on the search path. We could lower kBytesPerKey by doing this if (update) {
// though. dontInvalidate = child;
return; }
} }
// Merge partial key with child // Merge partial key with child
@@ -885,7 +903,7 @@ void maybeDownsize(Node *self, NodeAllocators *allocators,
} }
} break; } break;
case Type::Node16: case Type::Node16:
if (self->numChildren < kMinChildrenNode16) { if (self->numChildren + int(self->entryPresent) < kMinChildrenNode16) {
auto *self16 = (Node16 *)self; auto *self16 = (Node16 *)self;
auto *newSelf = allocators->node4.allocate(self->partialKeyLen); auto *newSelf = allocators->node4.allocate(self->partialKeyLen);
memcpy((char *)newSelf + kNodeCopyBegin, (char *)self + kNodeCopyBegin, memcpy((char *)newSelf + kNodeCopyBegin, (char *)self + kNodeCopyBegin,
@@ -902,7 +920,7 @@ void maybeDownsize(Node *self, NodeAllocators *allocators,
} }
break; break;
case Type::Node48: case Type::Node48:
if (self->numChildren < kMinChildrenNode48) { if (self->numChildren + int(self->entryPresent) < kMinChildrenNode48) {
auto *self48 = (Node48 *)self; auto *self48 = (Node48 *)self;
auto *newSelf = allocators->node16.allocate(self->partialKeyLen); auto *newSelf = allocators->node16.allocate(self->partialKeyLen);
memcpy((char *)newSelf + kNodeCopyBegin, (char *)self + kNodeCopyBegin, memcpy((char *)newSelf + kNodeCopyBegin, (char *)self + kNodeCopyBegin,
@@ -915,7 +933,7 @@ void maybeDownsize(Node *self, NodeAllocators *allocators,
// Suppress a false positive -Waggressive-loop-optimizations warning // Suppress a false positive -Waggressive-loop-optimizations warning
// in gcc. `assume` doesn't work for some reason. // in gcc. `assume` doesn't work for some reason.
if (!(i < 16)) { if (!(i < 16)) {
__builtin_unreachable(); __builtin_unreachable(); // GCOVR_EXCL_LINE
} }
newSelf->index[i] = c; newSelf->index[i] = c;
newSelf->children[i] = self48->children[self48->index[c]]; newSelf->children[i] = self48->children[self48->index[c]];
@@ -929,7 +947,7 @@ void maybeDownsize(Node *self, NodeAllocators *allocators,
} }
break; break;
case Type::Node256: case Type::Node256:
if (self->numChildren < kMinChildrenNode256) { if (self->numChildren + int(self->entryPresent) < kMinChildrenNode256) {
auto *self256 = (Node256 *)self; auto *self256 = (Node256 *)self;
auto *newSelf = allocators->node48.allocate(self->partialKeyLen); auto *newSelf = allocators->node48.allocate(self->partialKeyLen);
memcpy((char *)newSelf + kNodeCopyBegin, (char *)self + kNodeCopyBegin, memcpy((char *)newSelf + kNodeCopyBegin, (char *)self + kNodeCopyBegin,
@@ -954,16 +972,28 @@ void maybeDownsize(Node *self, NodeAllocators *allocators,
} }
// Precondition: self is not the root. May invalidate nodes along the search // Precondition: self is not the root. May invalidate nodes along the search
// path to self. // path to self. May invalidate children of self->parent. Returns a pointer to
Node *erase(Node *self, NodeAllocators *allocators, ConflictSet::Impl *impl) { // the node after self. If erase invalidates the pointee of `dontInvalidate`, it
// will update it to its new pointee as well.
Node *erase(Node *self, NodeAllocators *allocators, ConflictSet::Impl *impl,
Node *&dontInvalidate) {
assert(self->parent != nullptr); assert(self->parent != nullptr);
#if DEBUG_VERBOSE && !defined(NDEBUG)
fprintf(stderr, "Erase: %s\n", getSearchPathPrintable(self).c_str());
#endif
Node *parent = self->parent; Node *parent = self->parent;
uint8_t parentsIndex = self->parentsIndex; uint8_t parentsIndex = self->parentsIndex;
auto *result = nextLogical(self); auto *result = nextLogical(self);
self->entryPresent = false; self->entryPresent = false;
if (self->numChildren != 0) { if (self->numChildren != 0) {
const bool update = result == dontInvalidate;
maybeDownsize(self, allocators, impl, result);
if (update) {
dontInvalidate = result;
}
return result; return result;
} }
@@ -1017,9 +1047,13 @@ Node *erase(Node *self, NodeAllocators *allocators, ConflictSet::Impl *impl) {
--parent->numChildren; --parent->numChildren;
if (parent->numChildren == 0 && !parent->entryPresent && if (parent->numChildren == 0 && !parent->entryPresent &&
parent->parent != nullptr) { parent->parent != nullptr) {
erase(parent, allocators, impl); return erase(parent, allocators, impl, dontInvalidate);
} else { } else {
maybeDownsize(parent, allocators, impl); const bool update = result == dontInvalidate;
maybeDownsize(parent, allocators, impl, result);
if (update) {
dontInvalidate = result;
}
} }
return result; return result;
} }
@@ -1223,10 +1257,6 @@ struct SearchStepWise {
} }
}; };
namespace {
std::string getSearchPathPrintable(Node *n);
}
// Logically this is the same as performing firstGeq and then checking against // 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 // point or range version according to cmp, but this version short circuits as
// soon as it can prove that there's no conflict. // soon as it can prove that there's no conflict.
@@ -2015,7 +2045,7 @@ void addWriteRange(Node *&root, int64_t oldestVersion,
} }
for (beginNode = nextLogical(beginNode); beginNode != endNode; for (beginNode = nextLogical(beginNode); beginNode != endNode;
beginNode = erase(beginNode, allocators, impl)) { beginNode = erase(beginNode, allocators, impl, endNode)) {
} }
} }
@@ -2141,7 +2171,8 @@ struct __attribute__((visibility("hidden"))) ConflictSet::Impl {
// There's no way to insert a range such that range version of the right // There's no way to insert a range such that range version of the right
// node is greater than the point version of the left node // node is greater than the point version of the left node
assert(n->entry.rangeVersion <= oldestVersion); assert(n->entry.rangeVersion <= oldestVersion);
n = erase(n, &allocators, this); Node *dummy = nullptr;
n = erase(n, &allocators, this, dummy);
} else { } else {
maybeDecreaseCapacity(n, &allocators, this); maybeDecreaseCapacity(n, &allocators, this);
n = nextLogical(n); n = nextLogical(n);
@@ -2493,11 +2524,12 @@ Iterator firstGeq(Node *n, std::string_view key) {
minNumChildren = kMinChildrenNode256; minNumChildren = kMinChildrenNode256;
break; break;
} }
if (node->numChildren < minNumChildren) { if (node->numChildren + int(node->entryPresent) < minNumChildren) {
fprintf(stderr, fprintf(stderr,
"%s has %d children, which is less than the minimum required %d\n", "%s has %d children + %d entries, which is less than the minimum "
"required %d\n",
getSearchPathPrintable(node).c_str(), node->numChildren, getSearchPathPrintable(node).c_str(), node->numChildren,
minNumChildren); int(node->entryPresent), minNumChildren);
success = false; success = false;
} }
// TODO check that the max capacity property eventually holds // TODO check that the max capacity property eventually holds

View File

@@ -482,7 +482,7 @@ template <class ConflictSetImpl> struct TestDriver {
ConflictSetImpl cs{oldestVersion}; ConflictSetImpl cs{oldestVersion};
ReferenceImpl refImpl{oldestVersion}; ReferenceImpl refImpl{oldestVersion};
constexpr static auto kMaxKeyLen = 64; constexpr static auto kMaxKeyLen = 128;
bool ok = true; bool ok = true;

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1 @@
:

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Some files were not shown because too many files have changed in this diff Show More