9 Commits

Author SHA1 Message Date
359f6f0042 Update README benchmarks
Some checks reported errors
Tests / Clang total: 2500, passed: 2500
Clang |Total|New|Outstanding|Fixed|Trend |:-:|:-:|:-:|:-:|:-: |0|0|0|0|:clap:
Tests / Debug total: 2498, passed: 2498
Tests / SIMD fallback total: 2500, passed: 2500
Tests / Release [gcc] total: 2500, passed: 2500
GNU C Compiler (gcc) |Total|New|Outstanding|Fixed|Trend |:-:|:-:|:-:|:-:|:-: |0|0|0|0|:clap:
Tests / Release [gcc,aarch64] total: 1867, passed: 1867
Tests / Coverage total: 1877, passed: 1877
Code Coverage #### Project Overview No changes detected, that affect the code coverage. * Line Coverage: 98.64% (1813/1838) * Branch Coverage: 67.15% (1478/2201) * Complexity Density: 0.00 * Lines of Code: 1838 #### Quality Gates Summary Output truncated.
weaselab/conflict-set/pipeline/head Something is wrong with the build of this commit
2024-08-15 18:53:42 -07:00
aa8504ddba Add to corpus 2024-08-15 18:47:47 -07:00
fb7cf18f9b Add some comments about safety of calling maxVersion 2024-08-15 17:35:55 -07:00
b808b97940 Remove a bunch of impl plumbing
By making it a precondition that nodes aren't the root for certain
functions
2024-08-15 17:15:37 -07:00
e480f66846 Have caller ensure root max version for insert 2024-08-15 17:05:10 -07:00
d5bc9221a0 Simplify addWriteRange slightly 2024-08-15 16:59:28 -07:00
9d23b81d6f Bring in some of the changes from erase-between branch 2024-08-15 16:55:26 -07:00
0740dcad43 Strengthen checkMemoryBoundInvariants check for Node0
I think this is probably checked elsewhere, but let's check here too for
consistency
2024-08-14 14:28:28 -07:00
176df61321 Simplify handling of beginNode invalidation if begin is prefix of end 2024-08-14 14:26:13 -07:00
63 changed files with 328 additions and 247 deletions

View File

@@ -524,6 +524,8 @@ std::string getSearchPath(Node *n);
// Induction hypothesis is that each node's surplus is >= kMinNodeSurplus
constexpr int kBytesPerKey = 112;
constexpr int kMinNodeSurplus = 80;
// Cound the entry itself as a child
constexpr int kMinChildrenNode0 = 1;
constexpr int kMinChildrenNode3 = 2;
constexpr int kMinChildrenNode16 = 4;
constexpr int kMinChildrenNode48 = 17;
@@ -903,10 +905,115 @@ Node *&getChildExists(Node *self, uint8_t index) {
}
}
InternalVersionT maxVersion(Node *n, ConflictSet::Impl *);
InternalVersionT exchangeMaxVersion(Node *n, InternalVersionT newMax);
// Precondition `n` is not the root
InternalVersionT maxVersion(Node *n) {
int index = n->parentsIndex;
n = n->parent;
assert(n != nullptr);
switch (n->getType()) {
case Type_Node0: // GCOVR_EXCL_LINE
__builtin_unreachable(); // GCOVR_EXCL_LINE
case Type_Node3: {
auto *n3 = static_cast<Node3 *>(n);
int i = getNodeIndex(n3, index);
return n3->childMaxVersion[i];
}
case Type_Node16: {
auto *n16 = static_cast<Node16 *>(n);
int i = getNodeIndex(n16, index);
return n16->childMaxVersion[i];
}
case Type_Node48: {
auto *n48 = static_cast<Node48 *>(n);
assert(n48->bitSet.test(index));
return n48->childMaxVersion[n48->index[index]];
}
case Type_Node256: {
auto *n256 = static_cast<Node256 *>(n);
assert(n256->bitSet.test(index));
return n256->childMaxVersion[index];
}
default: // GCOVR_EXCL_LINE
__builtin_unreachable(); // GCOVR_EXCL_LINE
}
}
void setMaxVersion(Node *n, ConflictSet::Impl *, InternalVersionT maxVersion);
// Precondition `n` is not the root
InternalVersionT exchangeMaxVersion(Node *n, InternalVersionT newMax) {
int index = n->parentsIndex;
n = n->parent;
assert(n != nullptr);
switch (n->getType()) {
case Type_Node0: // GCOVR_EXCL_LINE
__builtin_unreachable(); // GCOVR_EXCL_LINE
case Type_Node3: {
auto *n3 = static_cast<Node3 *>(n);
int i = getNodeIndex(n3, index);
return std::exchange(n3->childMaxVersion[i], newMax);
}
case Type_Node16: {
auto *n16 = static_cast<Node16 *>(n);
int i = getNodeIndex(n16, index);
return std::exchange(n16->childMaxVersion[i], newMax);
}
case Type_Node48: {
auto *n48 = static_cast<Node48 *>(n);
assert(n48->bitSet.test(index));
return std::exchange(n48->childMaxVersion[n48->index[index]], newMax);
}
case Type_Node256: {
auto *n256 = static_cast<Node256 *>(n);
assert(n256->bitSet.test(index));
return std::exchange(n256->childMaxVersion[index], newMax);
}
default: // GCOVR_EXCL_LINE
__builtin_unreachable(); // GCOVR_EXCL_LINE
}
}
// Precondition `n` is not the root
void setMaxVersion(Node *n, InternalVersionT newMax) {
assert(newMax >= InternalVersionT::zero);
int index = n->parentsIndex;
n = n->parent;
assert(n != nullptr);
switch (n->getType()) {
case Type_Node0: // GCOVR_EXCL_LINE
__builtin_unreachable(); // GCOVR_EXCL_LINE
case Type_Node3: {
auto *n3 = static_cast<Node3 *>(n);
int i = getNodeIndex(n3, index);
n3->childMaxVersion[i] = newMax;
return;
}
case Type_Node16: {
auto *n16 = static_cast<Node16 *>(n);
int i = getNodeIndex(n16, index);
n16->childMaxVersion[i] = newMax;
return;
}
case Type_Node48: {
auto *n48 = static_cast<Node48 *>(n);
assert(n48->bitSet.test(index));
int i = n48->index[index];
n48->childMaxVersion[i] = newMax;
n48->maxOfMax[i >> Node48::kMaxOfMaxShift] = std::max<InternalVersionT>(
n48->maxOfMax[i >> Node48::kMaxOfMaxShift], newMax);
return;
}
case Type_Node256: {
auto *n256 = static_cast<Node256 *>(n);
assert(n256->bitSet.test(index));
n256->childMaxVersion[index] = newMax;
n256->maxOfMax[index >> Node256::kMaxOfMaxShift] =
std::max<InternalVersionT>(
n256->maxOfMax[index >> Node256::kMaxOfMaxShift], newMax);
return;
}
default: // GCOVR_EXCL_LINE
__builtin_unreachable(); // GCOVR_EXCL_LINE
}
}
Node *&getInTree(Node *n, ConflictSet::Impl *);
@@ -1542,6 +1649,47 @@ void rezero(Node *n, InternalVersionT z) {
}
}
void mergeWithChild(Node *&self, WriteContext *tls, ConflictSet::Impl *impl,
Node *&dontInvalidate, Node3 *self3) {
assert(!self3->entryPresent);
auto *child = self3->children[0];
int minCapacity = self3->partialKeyLen + 1 + child->partialKeyLen;
if (minCapacity > child->getCapacity()) {
const bool update = child == dontInvalidate;
freeAndMakeCapacityAtLeast(child, minCapacity, tls, impl, true);
if (update) {
dontInvalidate = child;
}
}
// Merge partial key with child
#if DEBUG_VERBOSE && !defined(NDEBUG)
fprintf(stderr, "Merge %s into %s\n", getSearchPathPrintable(self).c_str(),
getSearchPathPrintable(child).c_str());
#endif
InternalVersionT childMaxVersion = self3->childMaxVersion[0];
// Construct new partial key for child
memmove(child->partialKey() + self3->partialKeyLen + 1, child->partialKey(),
child->partialKeyLen);
memcpy(child->partialKey(), self3->partialKey(), self->partialKeyLen);
child->partialKey()[self3->partialKeyLen] = self3->index[0];
child->partialKeyLen += 1 + self3->partialKeyLen;
child->parent = self->parent;
child->parentsIndex = self->parentsIndex;
// Max versions are stored in the parent, so we need to update it now
// that we have a new parent. Safe we call since the root never has a partial
// key.
setMaxVersion(child, std::max(childMaxVersion, tls->zero));
self = child;
tls->release(self3);
}
void maybeDownsize(Node *self, WriteContext *tls, ConflictSet::Impl *impl,
Node *&dontInvalidate) {
@@ -1560,45 +1708,7 @@ void maybeDownsize(Node *self, WriteContext *tls, ConflictSet::Impl *impl,
getInTree(self, impl) = newSelf;
tls->release(self3);
} else if (self->numChildren == 1 && !self->entryPresent) {
auto *child = self3->children[0];
int minCapacity = self3->partialKeyLen + 1 + child->partialKeyLen;
if (minCapacity > child->getCapacity()) {
const bool update = child == dontInvalidate;
freeAndMakeCapacityAtLeast(child, minCapacity, tls, impl, true);
if (update) {
dontInvalidate = child;
}
}
// Merge partial key with child
#if DEBUG_VERBOSE && !defined(NDEBUG)
fprintf(stderr, "Merge %s into %s\n",
getSearchPathPrintable(self).c_str(),
getSearchPathPrintable(child).c_str());
#endif
InternalVersionT childMaxVersion = maxVersion(child, impl);
// Construct new partial key for child
memmove(child->partialKey() + self3->partialKeyLen + 1,
child->partialKey(), child->partialKeyLen);
memcpy(child->partialKey(), self3->partialKey(), self->partialKeyLen);
child->partialKey()[self3->partialKeyLen] = self3->index[0];
child->partialKeyLen += 1 + self3->partialKeyLen;
child->parent = self->parent;
child->parentsIndex = self->parentsIndex;
// Max versions are stored in the parent, so we need to update it now
// that we have a new parent.
setMaxVersion(child, impl, childMaxVersion);
if (child->parent) {
rezero(child->parent, tls->zero);
}
getInTree(self, impl) = child;
tls->release(self3);
mergeWithChild(getInTree(self, impl), tls, impl, dontInvalidate, self3);
}
} break;
case Type_Node16:
@@ -1846,10 +1956,10 @@ bool checkPrefixRead(Node *n, const std::span<const uint8_t> key,
fprintf(stderr, "Check prefix read: %s\n", printable(key).c_str());
#endif
auto remaining = key;
auto *impl = tls->impl;
for (;; ++tls->prefix_read_iterations_accum) {
if (remaining.size() == 0) {
return maxVersion(n, impl) <= readVersion;
// There's no way to encode a prefix read of "", so n is not the root
return maxVersion(n) <= readVersion;
}
auto [child, maxV] = getChildAndMaxVersion(n, remaining[0]);
@@ -1892,7 +2002,7 @@ bool checkPrefixRead(Node *n, const std::span<const uint8_t> key,
// n is the first physical node greater than remaining, and there's no
// eq node. All physical nodes that start with prefix are reachable from
// n.
if (maxVersion(n, impl) > readVersion) {
if (maxVersion(n) > readVersion) {
return false;
}
goto downLeftSpine;
@@ -2371,7 +2481,6 @@ bool checkRangeStartsWith(Node *n, std::span<const uint8_t> key, int begin,
fprintf(stderr, "%s(%02x,%02x)*\n", printable(key).c_str(), begin, end);
#endif
auto remaining = key;
auto *impl = tls->impl;
if (remaining.size() == 0) {
return checkMaxBetweenExclusive(n, begin, end, readVersion, tls);
}
@@ -2416,7 +2525,7 @@ bool checkRangeStartsWith(Node *n, std::span<const uint8_t> key, int begin,
if (n->entryPresent && n->entry.rangeVersion > readVersion) {
return false;
}
return maxVersion(n, impl) <= readVersion;
return maxVersion(n) <= readVersion;
}
return true;
}
@@ -2456,7 +2565,7 @@ struct CheckRangeLeftSide {
bool step() {
if (remaining.size() == 0) {
assert(searchPathLen >= prefixLen);
ok = maxVersion(n, impl) <= readVersion;
ok = maxVersion(n) <= readVersion;
return true;
}
@@ -2476,7 +2585,7 @@ struct CheckRangeLeftSide {
return downLeftSpine();
}
n = c;
ok = maxVersion(n, impl) <= readVersion;
ok = maxVersion(n) <= readVersion;
return true;
} else {
n = nextSibling(n);
@@ -2506,7 +2615,7 @@ struct CheckRangeLeftSide {
ok = false;
return true;
}
ok = maxVersion(n, impl) <= readVersion;
ok = maxVersion(n) <= readVersion;
return true;
} else {
n = nextSibling(n);
@@ -2526,7 +2635,7 @@ struct CheckRangeLeftSide {
ok = false;
return true;
}
ok = maxVersion(n, impl) <= readVersion;
ok = maxVersion(n) <= readVersion;
return true;
}
}
@@ -2647,7 +2756,8 @@ struct CheckRangeRightSide {
bool backtrack() {
for (;;) {
if (searchPathLen > prefixLen && maxVersion(n, impl) > readVersion) {
// searchPathLen > prefixLen implies n is not the root
if (searchPathLen > prefixLen && maxVersion(n) > readVersion) {
ok = false;
return true;
}
@@ -2748,6 +2858,9 @@ bool checkRangeRead(Node *n, std::span<const uint8_t> begin,
return false;
}
// This makes it safe to check maxVersion within CheckRangeLeftSide. If this
// were false, then we would have returned above since lcp == begin.size().
assert(!(n->parent == nullptr && begin.size() == 0));
CheckRangeLeftSide checkRangeLeftSide{n, begin, lcp + 1, readVersion, tls};
CheckRangeRightSide checkRangeRightSide{n, end, lcp + 1, readVersion, tls};
@@ -2795,14 +2908,11 @@ checkMaxBetweenExclusiveImpl<true>(Node *n, int begin, int end,
// Returns a pointer the pointer to the newly inserted node in the tree. Caller
// must set `entryPresent`, and `entry` fields. All nodes along the search path
// of the result will have `maxVersion` set to `writeVersion` as a
// postcondition. Nodes along the search path may be invalidated.
// postcondition. Nodes along the search path may be invalidated. Callers must
// ensure that the max version of the self argument is updated.
[[nodiscard]]
Node **insert(Node **self, std::span<const uint8_t> key,
InternalVersionT writeVersion, WriteContext *tls,
ConflictSet::Impl *impl) {
assert(maxVersion(*self, impl) <= writeVersion);
setMaxVersion(*self, impl, writeVersion);
InternalVersionT writeVersion, WriteContext *tls) {
for (; key.size() != 0; ++tls->accum.insert_iterations) {
self = &getOrCreateChild(*self, key, writeVersion, tls);
@@ -2810,36 +2920,57 @@ Node **insert(Node **self, std::span<const uint8_t> key,
return self;
}
void destroyTree(Node *root) {
void eraseTree(Node *root, WriteContext *tls) {
Arena arena;
auto toFree = vector<Node *>(arena);
toFree.push_back(root);
#if SHOW_MEMORY
for (auto *iter = root; iter != nullptr; iter = nextPhysical(iter)) {
removeNode(iter);
removeKey(iter);
}
#endif
while (toFree.size() > 0) {
auto *n = toFree.back();
toFree.pop_back();
// Add all children to toFree
for (auto c = getChildGeq(n, 0); c != nullptr;
c = getChildGeq(n, c->parentsIndex + 1)) {
assert(c != nullptr);
toFree.push_back(c);
tls->accum.entries_erased += n->entryPresent;
++tls->accum.nodes_released;
removeNode(n);
removeKey(n);
switch (n->getType()) {
case Type_Node0: {
auto *n0 = static_cast<Node0 *>(n);
tls->release(n0);
} break;
case Type_Node3: {
auto *n3 = static_cast<Node3 *>(n);
toFree.append(std::span<Node *>(n3->children, n3->numChildren));
tls->release(n3);
} break;
case Type_Node16: {
auto *n16 = static_cast<Node16 *>(n);
toFree.append(std::span<Node *>(n16->children, n16->numChildren));
tls->release(n16);
} break;
case Type_Node48: {
auto *n48 = static_cast<Node48 *>(n);
toFree.append(std::span<Node *>(n48->children, n48->numChildren));
tls->release(n48);
} break;
case Type_Node256: {
auto *n256 = static_cast<Node256 *>(n);
auto *out = toFree.unsafePrepareAppend(n256->numChildren).data();
n256->bitSet.forEachSet([&](int i) { *out++ = n256->children[i]; });
assert(out == toFree.end());
tls->release(n256);
} break;
default: // GCOVR_EXCL_LINE
__builtin_unreachable(); // GCOVR_EXCL_LINE
}
safe_free(n, n->size());
}
}
void addPointWrite(Node *&root, std::span<const uint8_t> key,
InternalVersionT writeVersion, WriteContext *tls,
ConflictSet::Impl *impl) {
InternalVersionT writeVersion, WriteContext *tls) {
++tls->accum.point_writes;
auto *n = *insert(&root, key, writeVersion, tls, impl);
auto *n = *insert(&root, key, writeVersion, tls);
if (!n->entryPresent) {
++tls->accum.entries_inserted;
auto *p = nextLogical(n);
@@ -2856,8 +2987,9 @@ void addPointWrite(Node *&root, std::span<const uint8_t> key,
}
}
// Precondition: `node->entryPresent`
void fixupMaxVersion(Node *node, ConflictSet::Impl *impl, WriteContext *tls) {
// Precondition: `node->entryPresent`, and node is not the root
void fixupMaxVersion(Node *node, WriteContext *tls) {
assert(node->parent);
InternalVersionT max;
assert(node->entryPresent);
max = std::max(node->entry.pointVersion, tls->zero);
@@ -2891,7 +3023,7 @@ void fixupMaxVersion(Node *node, ConflictSet::Impl *impl, WriteContext *tls) {
default: // GCOVR_EXCL_LINE
__builtin_unreachable(); // GCOVR_EXCL_LINE
}
setMaxVersion(node, impl, max);
setMaxVersion(node, max);
}
void addWriteRange(Node *&root, std::span<const uint8_t> begin,
@@ -2902,64 +3034,50 @@ void addWriteRange(Node *&root, std::span<const uint8_t> begin,
std::min(begin.size(), end.size()));
if (lcp == int(begin.size()) && end.size() == begin.size() + 1 &&
end.back() == 0) {
return addPointWrite(root, begin, writeVersion, tls, impl);
return addPointWrite(root, begin, writeVersion, tls);
}
++tls->accum.range_writes;
const bool beginIsPrefix = lcp == int(begin.size());
Node **useAsRoot =
insert(&root, begin.subspan(0, lcp), writeVersion, tls, impl);
Node **useAsRoot = insert(&root, begin.subspan(0, lcp), writeVersion, tls);
int consumed = lcp;
begin = begin.subspan(consumed, begin.size() - consumed);
end = end.subspan(consumed, end.size() - consumed);
auto *beginNode = *insert(useAsRoot, begin, writeVersion, tls, impl);
const bool insertedBegin = !beginNode->entryPresent;
begin = begin.subspan(lcp, begin.size() - lcp);
end = end.subspan(lcp, end.size() - lcp);
auto *beginNode = *insert(useAsRoot, begin, writeVersion, tls);
addKey(beginNode);
beginNode->entryPresent = true;
if (insertedBegin) {
if (!beginNode->entryPresent) {
++tls->accum.entries_inserted;
auto *p = nextLogical(beginNode);
beginNode->entry.rangeVersion =
p == nullptr ? tls->zero : std::max(p->entry.rangeVersion, tls->zero);
beginNode->entry.pointVersion = writeVersion;
beginNode->entryPresent = true;
}
assert(writeVersion >= beginNode->entry.pointVersion);
beginNode->entry.pointVersion = writeVersion;
auto *endNode = *insert(useAsRoot, end, writeVersion, tls, impl);
const bool insertedEnd = !endNode->entryPresent;
auto *endNode = *insert(useAsRoot, end, writeVersion, tls);
addKey(endNode);
endNode->entryPresent = true;
if (insertedEnd) {
if (!endNode->entryPresent) {
++tls->accum.entries_inserted;
auto *p = nextLogical(endNode);
endNode->entry.pointVersion =
p == nullptr ? tls->zero : std::max(p->entry.rangeVersion, tls->zero);
if (beginIsPrefix) {
// beginNode may have been invalidated when inserting end
beginNode = *useAsRoot;
assert(beginNode->entryPresent);
}
endNode->entryPresent = true;
}
endNode->entry.rangeVersion = writeVersion;
if (beginIsPrefix && insertedEnd) {
// beginNode may have been invalidated when inserting end. TODO can we do
// better?
beginNode = *insert(useAsRoot, begin, writeVersion, tls, impl);
assert(beginNode->entryPresent);
}
for (beginNode = nextLogical(beginNode); beginNode != endNode;
beginNode = erase(beginNode, tls, impl, /*logical*/ true, endNode)) {
}
// Inserting end trashed endNode's maxVersion. Fix that
fixupMaxVersion(endNode, impl, tls);
// Inserting end trashed endNode's maxVersion. Fix that. Safe to call since
// the end key always has non-zero size.
fixupMaxVersion(endNode, tls);
}
Node *firstGeqPhysical(Node *n, const std::span<const uint8_t> key) {
@@ -3070,7 +3188,7 @@ struct __attribute__((visibility("hidden"))) ConflictSet::Impl {
if (oldestExtantVersion < writeVersion - kMaxCorrectVersionWindow)
[[unlikely]] {
if (writeVersion > newestVersionFullPrecision + kNominalVersionWindow) {
destroyTree(root);
eraseTree(root, &tls);
init(writeVersion - kNominalVersionWindow);
}
@@ -3096,7 +3214,7 @@ struct __attribute__((visibility("hidden"))) ConflictSet::Impl {
addWriteRange(root, begin, end, InternalVersionT(writeVersion), &tls,
this);
} else {
addPointWrite(root, begin, InternalVersionT(writeVersion), &tls, this);
addPointWrite(root, begin, InternalVersionT(writeVersion), &tls);
}
}
@@ -3127,7 +3245,6 @@ struct __attribute__((visibility("hidden"))) ConflictSet::Impl {
// Don't erase the root
if (n == root) {
rezero(n, oldestVersion);
rootMaxVersion = std::max(rootMaxVersion, oldestVersion);
n = nextPhysical(n);
}
int64_t set_oldest_iterations_accum = 0;
@@ -3218,7 +3335,6 @@ struct __attribute__((visibility("hidden"))) ConflictSet::Impl {
root = tls.allocate<Node0>(0);
root->numChildren = 0;
root->parent = nullptr;
rootMaxVersion = this->oldestVersion;
root->entryPresent = false;
root->partialKeyLen = 0;
@@ -3238,7 +3354,7 @@ struct __attribute__((visibility("hidden"))) ConflictSet::Impl {
initMetrics();
}
~Impl() {
destroyTree(root);
eraseTree(root, &tls);
safe_free(metrics, metricsCount * sizeof(metrics[0]));
}
@@ -3249,7 +3365,6 @@ struct __attribute__((visibility("hidden"))) ConflictSet::Impl {
int64_t keyUpdates;
Node *root;
InternalVersionT rootMaxVersion;
InternalVersionT oldestVersion;
int64_t oldestVersionFullPrecision;
int64_t oldestExtantVersion;
@@ -3348,118 +3463,6 @@ Metric::Metric(ConflictSet::Impl *impl, const char *name, const char *help,
++impl->metricsCount;
}
InternalVersionT maxVersion(Node *n, ConflictSet::Impl *impl) {
int index = n->parentsIndex;
n = n->parent;
if (n == nullptr) {
return impl->rootMaxVersion;
}
switch (n->getType()) {
case Type_Node0: // GCOVR_EXCL_LINE
__builtin_unreachable(); // GCOVR_EXCL_LINE
case Type_Node3: {
auto *n3 = static_cast<Node3 *>(n);
int i = getNodeIndex(n3, index);
return n3->childMaxVersion[i];
}
case Type_Node16: {
auto *n16 = static_cast<Node16 *>(n);
int i = getNodeIndex(n16, index);
return n16->childMaxVersion[i];
}
case Type_Node48: {
auto *n48 = static_cast<Node48 *>(n);
assert(n48->bitSet.test(index));
return n48->childMaxVersion[n48->index[index]];
}
case Type_Node256: {
auto *n256 = static_cast<Node256 *>(n);
assert(n256->bitSet.test(index));
return n256->childMaxVersion[index];
}
default: // GCOVR_EXCL_LINE
__builtin_unreachable(); // GCOVR_EXCL_LINE
}
}
// Precondition `n` is not the root
InternalVersionT exchangeMaxVersion(Node *n, InternalVersionT newMax) {
int index = n->parentsIndex;
n = n->parent;
assert(n != nullptr);
switch (n->getType()) {
case Type_Node0: // GCOVR_EXCL_LINE
__builtin_unreachable(); // GCOVR_EXCL_LINE
case Type_Node3: {
auto *n3 = static_cast<Node3 *>(n);
int i = getNodeIndex(n3, index);
return std::exchange(n3->childMaxVersion[i], newMax);
}
case Type_Node16: {
auto *n16 = static_cast<Node16 *>(n);
int i = getNodeIndex(n16, index);
return std::exchange(n16->childMaxVersion[i], newMax);
}
case Type_Node48: {
auto *n48 = static_cast<Node48 *>(n);
assert(n48->bitSet.test(index));
return std::exchange(n48->childMaxVersion[n48->index[index]], newMax);
}
case Type_Node256: {
auto *n256 = static_cast<Node256 *>(n);
assert(n256->bitSet.test(index));
return std::exchange(n256->childMaxVersion[index], newMax);
}
default: // GCOVR_EXCL_LINE
__builtin_unreachable(); // GCOVR_EXCL_LINE
}
}
void setMaxVersion(Node *n, ConflictSet::Impl *impl, InternalVersionT newMax) {
int index = n->parentsIndex;
n = n->parent;
if (n == nullptr) {
impl->rootMaxVersion = newMax;
return;
}
switch (n->getType()) {
case Type_Node0: // GCOVR_EXCL_LINE
__builtin_unreachable(); // GCOVR_EXCL_LINE
case Type_Node3: {
auto *n3 = static_cast<Node3 *>(n);
int i = getNodeIndex(n3, index);
n3->childMaxVersion[i] = newMax;
return;
}
case Type_Node16: {
auto *n16 = static_cast<Node16 *>(n);
int i = getNodeIndex(n16, index);
n16->childMaxVersion[i] = newMax;
return;
}
case Type_Node48: {
auto *n48 = static_cast<Node48 *>(n);
assert(n48->bitSet.test(index));
int i = n48->index[index];
n48->childMaxVersion[i] = newMax;
n48->maxOfMax[i >> Node48::kMaxOfMaxShift] = std::max<InternalVersionT>(
n48->maxOfMax[i >> Node48::kMaxOfMaxShift], newMax);
return;
}
case Type_Node256: {
auto *n256 = static_cast<Node256 *>(n);
assert(n256->bitSet.test(index));
n256->childMaxVersion[index] = newMax;
n256->maxOfMax[index >> Node256::kMaxOfMaxShift] =
std::max<InternalVersionT>(
n256->maxOfMax[index >> Node256::kMaxOfMaxShift], newMax);
return;
}
default: // GCOVR_EXCL_LINE
__builtin_unreachable(); // GCOVR_EXCL_LINE
}
}
Node *&getInTree(Node *n, ConflictSet::Impl *impl) {
return n->parent == nullptr ? impl->root
: getChildExists(n->parent, n->parentsIndex);
@@ -3741,13 +3744,13 @@ std::string getSearchPath(Node *n) {
fprintf(file,
" k_%p [label=\"m=%" PRId64 " p=%" PRId64 " r=%" PRId64
"\n%s\", pos=\"%d,%d!\"];\n",
(void *)n, maxVersion(n, impl).toInt64(),
(void *)n, maxVersion(n).toInt64(),
n->entry.pointVersion.toInt64(),
n->entry.rangeVersion.toInt64(),
getPartialKeyPrintable(n).c_str(), x, y);
} else {
fprintf(file, " k_%p [label=\"m=%" PRId64 "\n%s\", pos=\"%d,%d!\"];\n",
(void *)n, maxVersion(n, impl).toInt64(),
(void *)n, maxVersion(n).toInt64(),
getPartialKeyPrintable(n).c_str(), x, y);
}
x += kSeparation;
@@ -3858,11 +3861,11 @@ checkMaxVersion(Node *root, Node *node, InternalVersionT oldestVersion,
expected = std::max(expected, borrowed->entry.rangeVersion);
}
}
if (maxVersion(node, impl) > oldestVersion &&
maxVersion(node, impl) != expected) {
if (node->parent && maxVersion(node) > oldestVersion &&
maxVersion(node) != expected) {
fprintf(stderr, "%s has max version %" PRId64 " . Expected %" PRId64 "\n",
getSearchPathPrintable(node).c_str(),
maxVersion(node, impl).toInt64(), expected.toInt64());
getSearchPathPrintable(node).c_str(), maxVersion(node).toInt64(),
expected.toInt64());
success = false;
}
return expected;
@@ -3888,7 +3891,7 @@ checkMaxVersion(Node *root, Node *node, InternalVersionT oldestVersion,
int minNumChildren;
switch (node->getType()) {
case Type_Node0:
minNumChildren = 0;
minNumChildren = kMinChildrenNode0;
break;
case Type_Node3:
minNumChildren = kMinChildrenNode3;
@@ -3926,7 +3929,7 @@ checkMaxVersion(Node *root, Node *node, InternalVersionT oldestVersion,
bool success = true;
if (node->partialKeyLen > 0) {
fprintf(stderr, "Root cannot have a partial key");
fprintf(stderr, "Root cannot have a partial key\n");
success = false;
}
checkParentPointers(node, success);

View File

@@ -273,6 +273,16 @@ template <class T> struct Vector {
size_ += slice.size();
}
// Caller must write to the returned slice
std::span<T> unsafePrepareAppend(int appendSize) {
if (size_ + appendSize > capacity) {
grow(std::max<int>(size_ + appendSize, capacity * 2));
}
auto result = std::span<T>(t + size_, appendSize);
size_ += appendSize;
return result;
}
void push_back(const T &t) { append(std::span<const T>(&t, 1)); }
T *begin() { return t; }

View File

@@ -24,16 +24,15 @@ Hardware for all benchmarks is an AMD Ryzen 9 7900 with (2x32GB) 5600MT/s CL28-3
| ns/op | op/s | err% | ins/op | cyc/op | IPC | bra/op | miss% | total | benchmark
|--------------------:|--------------------:|--------:|----------------:|----------------:|-------:|---------------:|--------:|----------:|:----------
| 10.80 | 92,600,541.52 | 0.6% | 180.38 | 54.49 | 3.310 | 41.51 | 0.4% | 0.01 | `point reads`
| 15.00 | 66,687,691.68 | 0.4% | 278.44 | 76.44 | 3.642 | 55.56 | 0.3% | 0.01 | `prefix reads`
| 36.81 | 27,163,394.61 | 0.4% | 795.06 | 187.91 | 4.231 | 142.67 | 0.2% | 0.01 | `range reads`
| 18.14 | 55,137,674.01 | 1.2% | 338.19 | 92.86 | 3.642 | 42.81 | 0.4% | 0.01 | `point writes`
| 33.19 | 30,127,119.71 | 0.1% | 681.03 | 170.05 | 4.005 | 98.68 | 0.2% | 0.01 | `prefix writes`
| 37.37 | 26,759,432.70 | 1.9% | 779.70 | 195.45 | 3.989 | 114.21 | 0.0% | 0.01 | `range writes`
| 74.36 | 13,448,582.47 | 1.9% | 1,425.68 | 389.08 | 3.664 | 258.88 | 0.1% | 0.01 | `monotonic increasing point writes`
| 316,928.00 | 3,155.29 | 1.5% | 3,992,986.00 | 1,699,813.00 | 2.349 | 806,226.50 | 0.0% | 0.01 | `worst case for radix tree`
| 75.26 | 13,286,517.16 | 0.5% | 1,590.01 | 386.67 | 4.112 | 258.00 | 0.0% | 0.01 | `create and destroy`
| 11.04 | 90,614,308.12 | 0.8% | 180.38 | 55.13 | 3.272 | 41.51 | 0.4% | 0.01 | `point reads`
| 14.96 | 66,843,629.12 | 0.4% | 274.41 | 74.73 | 3.672 | 55.05 | 0.3% | 0.01 | `prefix reads`
| 37.06 | 26,982,847.61 | 0.2% | 791.04 | 185.28 | 4.269 | 142.67 | 0.2% | 0.01 | `range reads`
| 17.89 | 55,887,365.73 | 0.6% | 335.54 | 89.79 | 3.737 | 43.84 | 0.4% | 0.01 | `point writes`
| 31.85 | 31,394,336.65 | 0.3% | 615.32 | 159.63 | 3.855 | 87.69 | 0.2% | 0.01 | `prefix writes`
| 36.17 | 27,647,221.45 | 0.6% | 705.11 | 182.80 | 3.857 | 100.62 | 0.1% | 0.01 | `range writes`
| 79.01 | 12,656,457.78 | 0.7% | 1,498.35 | 402.46 | 3.723 | 270.50 | 0.1% | 0.01 | `monotonic increasing point writes`
| 303,667.50 | 3,293.08 | 1.1% | 3,931,273.00 | 1,612,702.50 | 2.438 | 806,223.33 | 0.0% | 0.01 | `worst case for radix tree`
| 83.70 | 11,947,443.83 | 0.7% | 1,738.03 | 429.06 | 4.051 | 270.01 | 0.0% | 0.01 | `create and destroy`
# "Real data" test
@@ -48,7 +47,7 @@ Check: 4.47891 seconds, 364.05 MB/s, Add: 4.55599 seconds, 123.058 MB/s, Gc rati
## radix tree
```
Check: 0.910234 seconds, 1791.35 MB/s, Add: 1.25908 seconds, 445.287 MB/s, Gc ratio: 44.0415%
Check: 0.958985 seconds, 1700.28 MB/s, Add: 1.35083 seconds, 415.044 MB/s, Gc ratio: 44.4768%, Peak idle memory: 2.33588e+06
```
## hash table

View File

@@ -164,6 +164,63 @@ double toSeconds(timeval t) {
return double(t.tv_sec) + double(t.tv_usec) * 1e-6;
}
#include <linux/perf_event.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/syscall.h>
#include <sys/types.h>
#include <unistd.h>
#ifdef __linux__
struct PerfCounter {
explicit PerfCounter(int event) {
struct perf_event_attr pe;
memset(&pe, 0, sizeof(pe));
pe.type = PERF_TYPE_HARDWARE;
pe.size = sizeof(pe);
pe.config = event;
pe.inherit = 1;
pe.exclude_kernel = 1;
pe.exclude_hv = 1;
fd = perf_event_open(&pe, 0, -1, -1, 0);
if (fd == -1) {
fprintf(stderr, "Error opening leader %llx\n", pe.config);
exit(EXIT_FAILURE);
}
}
int64_t total() {
int64_t count;
if (read(fd, &count, sizeof(count)) != sizeof(count)) {
perror("read instructions from perf");
abort();
}
return count;
}
~PerfCounter() { close(fd); }
private:
int fd;
static long perf_event_open(struct perf_event_attr *hw_event, pid_t pid,
int cpu, int group_fd, unsigned long flags) {
int ret;
ret = syscall(SYS_perf_event_open, hw_event, pid, cpu, group_fd, flags);
return ret;
}
};
#else
struct PerfCounter {
explicit PerPerfCounter(int) {}
int64_t total() { return 0; }
};
#endif
int main(int argc, char **argv) {
if (argc != 3) {
goto fail;
@@ -176,6 +233,8 @@ int main(int argc, char **argv) {
int metricsCount;
cs.getMetricsV1(&metrics, &metricsCount);
PerfCounter instructions{PERF_COUNT_HW_INSTRUCTIONS};
PerfCounter cycles{PERF_COUNT_HW_CPU_CYCLES};
auto w = std::thread{workload, &cs};
for (;;) {
@@ -203,6 +262,16 @@ int main(int argc, char **argv) {
"transactions_total ";
body += std::to_string(transactions.load(std::memory_order_relaxed));
body += "\n";
body += "# HELP instructions_total Total number of instructions\n"
"# TYPE instructions_total counter\n"
"instructions_total ";
body += std::to_string(instructions.total());
body += "\n";
body += "# HELP cycles_total Total number of cycles\n"
"# TYPE cycles_total counter\n"
"cycles_total ";
body += std::to_string(cycles.total());
body += "\n";
for (int i = 0; i < metricsCount; ++i) {
body += "# HELP ";

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.

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.