22 Commits

Author SHA1 Message Date
f11720f5ae Add to corpus
Some checks failed
Tests / Clang total: 2620, passed: 2620
Clang |Total|New|Outstanding|Fixed|Trend |:-:|:-:|:-:|:-:|:-: |0|0|0|0|:clap:
Tests / Debug total: 2618, passed: 2618
Tests / SIMD fallback total: 2620, passed: 2620
Tests / Release [gcc] total: 2620, passed: 2620
GNU C Compiler (gcc) |Total|New|Outstanding|Fixed|Trend |:-:|:-:|:-:|:-:|:-: |0|0|0|0|:clap:
Tests / Release [gcc,aarch64] total: 1957, passed: 1957
Tests / Coverage total: 1967, failed: 1, passed: 1966
weaselab/conflict-set/pipeline/head Something is wrong with the build of this commit
2024-08-19 21:52:47 -07:00
e2b7298af5 Instrument setOldestVersion for callgrind too 2024-08-19 21:32:04 -07:00
8e1e344f4b Fix clang-18 warning about std::basic_string<uint8_t>
All checks were successful
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: 99.29% (1824/1837) * Branch Coverage: 67.44% (1485/2202) * Complexity Density: 0.00 * Lines of Code: 1837 #### Quality Gates Summary Output truncated.
weaselab/conflict-set/pipeline/head This commit looks good
2024-08-17 16:40:32 -07:00
3634b6a59b Simplify slightly in checkMaxBetweenExclusive
Some checks failed
Tests / Clang total: 2500, passed: 2500
Clang |Total|New|Outstanding|Fixed|Trend |:-:|:-:|:-:|:-:|:-: |0|0|0|0|:clap:
weaselab/conflict-set/pipeline/head There was a failure building this commit
2024-08-17 14:34:13 -07:00
a3cc14c807 Fix double counting of remove node in showMemory mode 2024-08-17 13:43:47 -07:00
55b3275434 ifdef linux specific stuff
All checks were successful
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: 99.29% (1829/1842) * Branch Coverage: 67.35% (1479/2196) * Complexity Density: 0.00 * Lines of Code: 1842 #### Quality Gates Summary Output truncated.
weaselab/conflict-set/pipeline/head This commit looks good
2024-08-16 16:23:46 -07:00
3a5b86ed9e Ignore itlb in grafana instead
All checks were successful
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: 99.29% (1829/1842) * Branch Coverage: 67.35% (1479/2196) * Complexity Density: 0.00 * Lines of Code: 1842 #### Quality Gates Summary Output truncated.
weaselab/conflict-set/pipeline/head This commit looks good
2024-08-16 16:02:01 -07:00
159f2eef74 Use group leader fd
Events in the same group should be associated with the same set of
instructions
2024-08-16 15:35:35 -07:00
2952abe811 Reorg headers and only print unexpected errno's 2024-08-16 14:25:41 -07:00
ce54746a4a Add several new cache events to metrics
All checks were successful
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: 99.29% (1829/1842) * Branch Coverage: 67.35% (1479/2196) * Complexity Density: 0.00 * Lines of Code: 1842 #### Quality Gates Summary Output truncated.
weaselab/conflict-set/pipeline/head This commit looks good
2024-08-16 12:48:22 -07:00
b15959d62c Add more perf counters 2024-08-16 10:57:00 -07:00
b009de1c2b Avoid branching on type twice in erase
All checks were successful
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: 99.29% (1829/1842) * Branch Coverage: 67.35% (1479/2196) * Complexity Density: 0.00 * Lines of Code: 1842 #### Quality Gates Summary Output truncated.
weaselab/conflict-set/pipeline/head This commit looks good
2024-08-16 09:29:22 -07:00
55a230c75e Remove dontInvalidate arg from erase
Use a new node member "endOfRange" instead
2024-08-16 09:08:56 -07:00
0711ec3831 Remove dead code
All checks were successful
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: 99.29% (1815/1828) * Branch Coverage: 67.41% (1479/2194) * Complexity Density: 0.00 * Lines of Code: 1828 #### Quality Gates Summary Output truncated.
weaselab/conflict-set/pipeline/head This commit looks good
2024-08-15 20:19:00 -07:00
0280bd77e5 Skip "dontInvalidate" check in erase from gc
Some checks failed
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% (1815/1840) * Branch Coverage: 67.14% (1479/2203) * Complexity Density: 0.00 * Lines of Code: 1840 #### Quality Gates Summary Output truncated.
weaselab/conflict-set/pipeline/head There was a failure building this commit
2024-08-15 19:07:53 -07:00
359f6f0042 Update README benchmarks
Some checks failed
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
93 changed files with 475 additions and 336 deletions

View File

@@ -173,7 +173,7 @@ int BitSet::firstSetGeq(int i) const {
return -1; return -1;
} }
enum Type { enum Type : int8_t {
Type_Node0, Type_Node0,
Type_Node3, Type_Node3,
Type_Node16, Type_Node16,
@@ -191,11 +191,12 @@ struct Node {
int32_t partialKeyLen; int32_t partialKeyLen;
int16_t numChildren; int16_t numChildren;
bool entryPresent; bool entryPresent;
// Temp variable used to signal the end of the range during addWriteRange
bool endOfRange;
uint8_t parentsIndex; uint8_t parentsIndex;
/* end section that's copied to the next node */ /* end section that's copied to the next node */
uint8_t *partialKey(); uint8_t *partialKey();
size_t size() const;
Type getType() const { return type; } Type getType() const { return type; }
int32_t getCapacity() const { return partialKeyCapacity; } int32_t getCapacity() const { return partialKeyCapacity; }
@@ -626,6 +627,7 @@ template <class T> struct BoundedFreeListAllocator {
T *allocate(int partialKeyCapacity) { T *allocate(int partialKeyCapacity) {
T *result = allocate_helper(partialKeyCapacity); T *result = allocate_helper(partialKeyCapacity);
result->endOfRange = false;
if constexpr (!std::is_same_v<T, Node0>) { if constexpr (!std::is_same_v<T, Node0>) {
memset(result->children, 0, sizeof(result->children)); memset(result->children, 0, sizeof(result->children));
const auto z = InternalVersionT::zero; const auto z = InternalVersionT::zero;
@@ -693,23 +695,6 @@ uint8_t *Node::partialKey() {
} }
} }
size_t Node::size() const {
switch (type) {
case Type_Node0:
return ((Node0 *)this)->size();
case Type_Node3:
return ((Node3 *)this)->size();
case Type_Node16:
return ((Node16 *)this)->size();
case Type_Node48:
return ((Node48 *)this)->size();
case Type_Node256:
return ((Node256 *)this)->size();
default: // GCOVR_EXCL_LINE
__builtin_unreachable(); // GCOVR_EXCL_LINE
}
}
// A type that's plumbed along the check call tree. Lifetime ends after each // A type that's plumbed along the check call tree. Lifetime ends after each
// check call. // check call.
struct ReadContext { struct ReadContext {
@@ -905,10 +890,115 @@ Node *&getChildExists(Node *self, uint8_t index) {
} }
} }
InternalVersionT maxVersion(Node *n, ConflictSet::Impl *); // Precondition `n` is not the root
InternalVersionT exchangeMaxVersion(Node *n, InternalVersionT newMax); 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 *); Node *&getInTree(Node *n, ConflictSet::Impl *);
@@ -1544,24 +1634,9 @@ void rezero(Node *n, InternalVersionT z) {
} }
} }
void maybeDownsize(Node *self, WriteContext *tls, ConflictSet::Impl *impl, void mergeWithChild(Node *&self, WriteContext *tls, ConflictSet::Impl *impl,
Node *&dontInvalidate) { Node *&dontInvalidate, Node3 *self3) {
assert(!self3->entryPresent);
#if DEBUG_VERBOSE && !defined(NDEBUG)
fprintf(stderr, "maybeDownsize: %s\n", getSearchPathPrintable(self).c_str());
#endif
switch (self->getType()) {
case Type_Node0: // GCOVR_EXCL_LINE
__builtin_unreachable(); // GCOVR_EXCL_LINE
case Type_Node3: {
auto *self3 = (Node3 *)self;
if (self->numChildren == 0) {
auto *newSelf = tls->allocate<Node0>(self->partialKeyLen);
newSelf->copyChildrenAndKeyFrom(*self3);
getInTree(self, impl) = newSelf;
tls->release(self3);
} else if (self->numChildren == 1 && !self->entryPresent) {
auto *child = self3->children[0]; auto *child = self3->children[0];
int minCapacity = self3->partialKeyLen + 1 + child->partialKeyLen; int minCapacity = self3->partialKeyLen + 1 + child->partialKeyLen;
@@ -1575,16 +1650,15 @@ void maybeDownsize(Node *self, WriteContext *tls, ConflictSet::Impl *impl,
// Merge partial key with child // Merge partial key with child
#if DEBUG_VERBOSE && !defined(NDEBUG) #if DEBUG_VERBOSE && !defined(NDEBUG)
fprintf(stderr, "Merge %s into %s\n", fprintf(stderr, "Merge %s into %s\n", getSearchPathPrintable(self).c_str(),
getSearchPathPrintable(self).c_str(),
getSearchPathPrintable(child).c_str()); getSearchPathPrintable(child).c_str());
#endif #endif
InternalVersionT childMaxVersion = maxVersion(child, impl); InternalVersionT childMaxVersion = self3->childMaxVersion[0];
// Construct new partial key for child // Construct new partial key for child
memmove(child->partialKey() + self3->partialKeyLen + 1, memmove(child->partialKey() + self3->partialKeyLen + 1, child->partialKey(),
child->partialKey(), child->partialKeyLen); child->partialKeyLen);
memcpy(child->partialKey(), self3->partialKey(), self->partialKeyLen); memcpy(child->partialKey(), self3->partialKey(), self->partialKeyLen);
child->partialKey()[self3->partialKeyLen] = self3->index[0]; child->partialKey()[self3->partialKeyLen] = self3->index[0];
child->partialKeyLen += 1 + self3->partialKeyLen; child->partialKeyLen += 1 + self3->partialKeyLen;
@@ -1593,42 +1667,75 @@ void maybeDownsize(Node *self, WriteContext *tls, ConflictSet::Impl *impl,
child->parentsIndex = self->parentsIndex; child->parentsIndex = self->parentsIndex;
// Max versions are stored in the parent, so we need to update it now // Max versions are stored in the parent, so we need to update it now
// that we have a new parent. // that we have a new parent. Safe we call since the root never has a partial
setMaxVersion(child, impl, childMaxVersion); // key.
if (child->parent) { setMaxVersion(child, std::max(childMaxVersion, tls->zero));
rezero(child->parent, tls->zero);
}
getInTree(self, impl) = child; self = child;
tls->release(self3); tls->release(self3);
}
bool needsDownsize(Node *n) {
static int minTable[] = {0, kMinChildrenNode3, kMinChildrenNode16,
kMinChildrenNode48, kMinChildrenNode256};
return n->numChildren + n->entryPresent < minTable[n->getType()];
}
void downsize(Node3 *self, WriteContext *tls, ConflictSet::Impl *impl,
Node *&dontInvalidate) {
if (self->numChildren == 0) {
auto *newSelf = tls->allocate<Node0>(self->partialKeyLen);
newSelf->copyChildrenAndKeyFrom(*self);
getInTree(self, impl) = newSelf;
tls->release(self);
} else {
assert(self->numChildren == 1 && !self->entryPresent);
mergeWithChild(getInTree(self, impl), tls, impl, dontInvalidate, self);
} }
} break; }
case Type_Node16:
if (self->numChildren + int(self->entryPresent) < kMinChildrenNode16) { void downsize(Node16 *self, WriteContext *tls, ConflictSet::Impl *impl) {
auto *self16 = (Node16 *)self; assert(self->numChildren + int(self->entryPresent) < kMinChildrenNode16);
auto *newSelf = tls->allocate<Node3>(self->partialKeyLen); auto *newSelf = tls->allocate<Node3>(self->partialKeyLen);
newSelf->copyChildrenAndKeyFrom(*self16); newSelf->copyChildrenAndKeyFrom(*self);
getInTree(self, impl) = newSelf; getInTree(self, impl) = newSelf;
tls->release(self16); tls->release(self);
} }
break;
case Type_Node48: void downsize(Node48 *self, WriteContext *tls, ConflictSet::Impl *impl) {
if (self->numChildren + int(self->entryPresent) < kMinChildrenNode48) { assert(self->numChildren + int(self->entryPresent) < kMinChildrenNode48);
auto *self48 = (Node48 *)self;
auto *newSelf = tls->allocate<Node16>(self->partialKeyLen); auto *newSelf = tls->allocate<Node16>(self->partialKeyLen);
newSelf->copyChildrenAndKeyFrom(*self48); newSelf->copyChildrenAndKeyFrom(*self);
getInTree(self, impl) = newSelf; getInTree(self, impl) = newSelf;
tls->release(self48); tls->release(self);
} }
break;
case Type_Node256: void downsize(Node256 *self, WriteContext *tls, ConflictSet::Impl *impl) {
if (self->numChildren + int(self->entryPresent) < kMinChildrenNode256) { assert(self->numChildren + int(self->entryPresent) < kMinChildrenNode256);
auto *self256 = (Node256 *)self; auto *self256 = (Node256 *)self;
auto *newSelf = tls->allocate<Node48>(self->partialKeyLen); auto *newSelf = tls->allocate<Node48>(self->partialKeyLen);
newSelf->copyChildrenAndKeyFrom(*self256); newSelf->copyChildrenAndKeyFrom(*self256);
getInTree(self, impl) = newSelf; getInTree(self, impl) = newSelf;
tls->release(self256); tls->release(self256);
} }
void downsize(Node *self, WriteContext *tls, ConflictSet::Impl *impl,
Node *&dontInvalidate) {
switch (self->getType()) {
case Type_Node0: // GCOVR_EXCL_LINE
__builtin_unreachable(); // GCOVR_EXCL_LINE
case Type_Node3:
downsize(static_cast<Node3 *>(self), tls, impl, dontInvalidate);
break;
case Type_Node16:
downsize(static_cast<Node16 *>(self), tls, impl);
break;
case Type_Node48:
downsize(static_cast<Node48 *>(self), tls, impl);
break;
case Type_Node256:
downsize(static_cast<Node256 *>(self), tls, impl);
break; break;
default: // GCOVR_EXCL_LINE default: // GCOVR_EXCL_LINE
__builtin_unreachable(); // GCOVR_EXCL_LINE __builtin_unreachable(); // GCOVR_EXCL_LINE
@@ -1637,10 +1744,10 @@ void maybeDownsize(Node *self, WriteContext *tls, ConflictSet::Impl *impl,
// 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. May invalidate children of self->parent. Returns a pointer to // path to self. May invalidate children of self->parent. Returns a pointer to
// the node after self. If erase invalidates the pointee of `dontInvalidate`, it // the node after self. Precondition: `self->entryPresent`
// will update it to its new pointee as well. Precondition: `self->entryPresent`
Node *erase(Node *self, WriteContext *tls, ConflictSet::Impl *impl, Node *erase(Node *self, WriteContext *tls, ConflictSet::Impl *impl,
bool logical, Node *&dontInvalidate) { bool logical) {
++tls->accum.entries_erased; ++tls->accum.entries_erased;
assert(self->parent != nullptr); assert(self->parent != nullptr);
@@ -1658,10 +1765,8 @@ Node *erase(Node *self, WriteContext *tls, ConflictSet::Impl *impl,
self->entryPresent = false; self->entryPresent = false;
if (self->numChildren != 0) { if (self->numChildren != 0) {
const bool update = result == dontInvalidate; if (needsDownsize(self)) {
maybeDownsize(self, tls, impl, result); downsize(self, tls, impl, result);
if (update) {
dontInvalidate = result;
} }
return result; return result;
} }
@@ -1682,7 +1787,10 @@ Node *erase(Node *self, WriteContext *tls, ConflictSet::Impl *impl,
parent3->children[i] = parent3->children[i + 1]; parent3->children[i] = parent3->children[i + 1];
parent3->childMaxVersion[i] = parent3->childMaxVersion[i + 1]; parent3->childMaxVersion[i] = parent3->childMaxVersion[i + 1];
} }
assert(parent->numChildren > 0 || parent->entryPresent);
if (needsDownsize(parent3)) {
downsize(parent3, tls, impl, result);
}
} break; } break;
case Type_Node16: { case Type_Node16: {
auto *parent16 = static_cast<Node16 *>(parent); auto *parent16 = static_cast<Node16 *>(parent);
@@ -1695,8 +1803,9 @@ Node *erase(Node *self, WriteContext *tls, ConflictSet::Impl *impl,
parent16->childMaxVersion[i] = parent16->childMaxVersion[i + 1]; parent16->childMaxVersion[i] = parent16->childMaxVersion[i + 1];
} }
// By kMinChildrenNode16 if (needsDownsize(parent16)) {
assert(parent->numChildren > 0); downsize(parent16, tls, impl, result);
}
} break; } break;
case Type_Node48: { case Type_Node48: {
auto *parent48 = static_cast<Node48 *>(parent); auto *parent48 = static_cast<Node48 *>(parent);
@@ -1724,8 +1833,9 @@ Node *erase(Node *self, WriteContext *tls, ConflictSet::Impl *impl,
--parent->numChildren; --parent->numChildren;
// By kMinChildrenNode48 if (needsDownsize(parent48)) {
assert(parent->numChildren > 0); downsize(parent48, tls, impl, result);
}
} break; } break;
case Type_Node256: { case Type_Node256: {
auto *parent256 = static_cast<Node256 *>(parent); auto *parent256 = static_cast<Node256 *>(parent);
@@ -1734,20 +1844,14 @@ Node *erase(Node *self, WriteContext *tls, ConflictSet::Impl *impl,
--parent->numChildren; --parent->numChildren;
// By kMinChildrenNode256 if (needsDownsize(parent256)) {
assert(parent->numChildren > 0); downsize(parent256, tls, impl, result);
}
} break; } break;
default: // GCOVR_EXCL_LINE default: // GCOVR_EXCL_LINE
__builtin_unreachable(); // GCOVR_EXCL_LINE __builtin_unreachable(); // GCOVR_EXCL_LINE
} }
const bool update = result == dontInvalidate;
maybeDownsize(parent, tls, impl, result);
if (update) {
dontInvalidate = result;
}
return result; return result;
} }
@@ -1848,10 +1952,10 @@ bool checkPrefixRead(Node *n, const std::span<const uint8_t> key,
fprintf(stderr, "Check prefix read: %s\n", printable(key).c_str()); fprintf(stderr, "Check prefix read: %s\n", printable(key).c_str());
#endif #endif
auto remaining = key; auto remaining = key;
auto *impl = tls->impl;
for (;; ++tls->prefix_read_iterations_accum) { for (;; ++tls->prefix_read_iterations_accum) {
if (remaining.size() == 0) { 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]); auto [child, maxV] = getChildAndMaxVersion(n, remaining[0]);
@@ -1894,7 +1998,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 // 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 // eq node. All physical nodes that start with prefix are reachable from
// n. // n.
if (maxVersion(n, impl) > readVersion) { if (maxVersion(n) > readVersion) {
return false; return false;
} }
goto downLeftSpine; goto downLeftSpine;
@@ -2236,10 +2340,8 @@ bool checkMaxBetweenExclusiveImpl(Node *n, int begin, int end,
int c = self->bitSet.firstSetGeq(begin + 1); int c = self->bitSet.firstSetGeq(begin + 1);
if (c >= 0 && c < end) { if (c >= 0 && c < end) {
auto *child = self->children[self->index[c]]; auto *child = self->children[self->index[c]];
if (child->entryPresent) { if (child->entryPresent && child->entry.rangeVersion > readVersion) {
if (!(child->entry.rangeVersion <= readVersion)) {
return false; return false;
};
} }
begin = c; begin = c;
} else { } else {
@@ -2272,10 +2374,8 @@ bool checkMaxBetweenExclusiveImpl(Node *n, int begin, int end,
int c = self->bitSet.firstSetGeq(begin + 1); int c = self->bitSet.firstSetGeq(begin + 1);
if (c >= 0 && c < end) { if (c >= 0 && c < end) {
auto *child = self->children[c]; auto *child = self->children[c];
if (child->entryPresent) { if (child->entryPresent && child->entry.rangeVersion > readVersion) {
if (!(child->entry.rangeVersion <= readVersion)) {
return false; return false;
};
} }
begin = c; begin = c;
} else { } else {
@@ -2318,9 +2418,7 @@ bool checkMaxBetweenExclusiveImpl(Node *n, int begin, int end,
} }
} }
// Check inner pages // Check inner pages
const int innerPageBegin = (begin >> Node256::kMaxOfMaxShift) + 1; return scan16<kAVX512>(self->maxOfMax, firstPage + 1, lastPage,
const int innerPageEnd = (end - 1) >> Node256::kMaxOfMaxShift;
return scan16<kAVX512>(self->maxOfMax, innerPageBegin, innerPageEnd,
readVersion); readVersion);
} }
default: // GCOVR_EXCL_LINE default: // GCOVR_EXCL_LINE
@@ -2373,7 +2471,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); fprintf(stderr, "%s(%02x,%02x)*\n", printable(key).c_str(), begin, end);
#endif #endif
auto remaining = key; auto remaining = key;
auto *impl = tls->impl;
if (remaining.size() == 0) { if (remaining.size() == 0) {
return checkMaxBetweenExclusive(n, begin, end, readVersion, tls); return checkMaxBetweenExclusive(n, begin, end, readVersion, tls);
} }
@@ -2418,7 +2515,7 @@ bool checkRangeStartsWith(Node *n, std::span<const uint8_t> key, int begin,
if (n->entryPresent && n->entry.rangeVersion > readVersion) { if (n->entryPresent && n->entry.rangeVersion > readVersion) {
return false; return false;
} }
return maxVersion(n, impl) <= readVersion; return maxVersion(n) <= readVersion;
} }
return true; return true;
} }
@@ -2458,7 +2555,7 @@ struct CheckRangeLeftSide {
bool step() { bool step() {
if (remaining.size() == 0) { if (remaining.size() == 0) {
assert(searchPathLen >= prefixLen); assert(searchPathLen >= prefixLen);
ok = maxVersion(n, impl) <= readVersion; ok = maxVersion(n) <= readVersion;
return true; return true;
} }
@@ -2478,7 +2575,7 @@ struct CheckRangeLeftSide {
return downLeftSpine(); return downLeftSpine();
} }
n = c; n = c;
ok = maxVersion(n, impl) <= readVersion; ok = maxVersion(n) <= readVersion;
return true; return true;
} else { } else {
n = nextSibling(n); n = nextSibling(n);
@@ -2508,7 +2605,7 @@ struct CheckRangeLeftSide {
ok = false; ok = false;
return true; return true;
} }
ok = maxVersion(n, impl) <= readVersion; ok = maxVersion(n) <= readVersion;
return true; return true;
} else { } else {
n = nextSibling(n); n = nextSibling(n);
@@ -2528,7 +2625,7 @@ struct CheckRangeLeftSide {
ok = false; ok = false;
return true; return true;
} }
ok = maxVersion(n, impl) <= readVersion; ok = maxVersion(n) <= readVersion;
return true; return true;
} }
} }
@@ -2649,7 +2746,8 @@ struct CheckRangeRightSide {
bool backtrack() { bool backtrack() {
for (;;) { for (;;) {
if (searchPathLen > prefixLen && maxVersion(n, impl) > readVersion) { // searchPathLen > prefixLen implies n is not the root
if (searchPathLen > prefixLen && maxVersion(n) > readVersion) {
ok = false; ok = false;
return true; return true;
} }
@@ -2750,6 +2848,9 @@ bool checkRangeRead(Node *n, std::span<const uint8_t> begin,
return false; 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}; CheckRangeLeftSide checkRangeLeftSide{n, begin, lcp + 1, readVersion, tls};
CheckRangeRightSide checkRangeRightSide{n, end, lcp + 1, readVersion, tls}; CheckRangeRightSide checkRangeRightSide{n, end, lcp + 1, readVersion, tls};
@@ -2797,14 +2898,11 @@ checkMaxBetweenExclusiveImpl<true>(Node *n, int begin, int end,
// Returns a pointer the pointer to the newly inserted node in the tree. Caller // 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 // must set `entryPresent`, and `entry` fields. All nodes along the search path
// of the result will have `maxVersion` set to `writeVersion` as a // 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]] [[nodiscard]]
Node **insert(Node **self, std::span<const uint8_t> key, Node **insert(Node **self, std::span<const uint8_t> key,
InternalVersionT writeVersion, WriteContext *tls, InternalVersionT writeVersion, WriteContext *tls) {
ConflictSet::Impl *impl) {
assert(maxVersion(*self, impl) <= writeVersion);
setMaxVersion(*self, impl, writeVersion);
for (; key.size() != 0; ++tls->accum.insert_iterations) { for (; key.size() != 0; ++tls->accum.insert_iterations) {
self = &getOrCreateChild(*self, key, writeVersion, tls); self = &getOrCreateChild(*self, key, writeVersion, tls);
@@ -2812,36 +2910,56 @@ Node **insert(Node **self, std::span<const uint8_t> key,
return self; return self;
} }
void destroyTree(Node *root) { void eraseTree(Node *root, WriteContext *tls) {
Arena arena; Arena arena;
auto toFree = vector<Node *>(arena); auto toFree = vector<Node *>(arena);
toFree.push_back(root); 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) { while (toFree.size() > 0) {
auto *n = toFree.back(); auto *n = toFree.back();
toFree.pop_back(); toFree.pop_back();
// Add all children to toFree tls->accum.entries_erased += n->entryPresent;
for (auto c = getChildGeq(n, 0); c != nullptr; ++tls->accum.nodes_released;
c = getChildGeq(n, c->parentsIndex + 1)) {
assert(c != nullptr); removeKey(n);
toFree.push_back(c);
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, void addPointWrite(Node *&root, std::span<const uint8_t> key,
InternalVersionT writeVersion, WriteContext *tls, InternalVersionT writeVersion, WriteContext *tls) {
ConflictSet::Impl *impl) {
++tls->accum.point_writes; ++tls->accum.point_writes;
auto *n = *insert(&root, key, writeVersion, tls, impl); auto *n = *insert(&root, key, writeVersion, tls);
if (!n->entryPresent) { if (!n->entryPresent) {
++tls->accum.entries_inserted; ++tls->accum.entries_inserted;
auto *p = nextLogical(n); auto *p = nextLogical(n);
@@ -2858,8 +2976,9 @@ void addPointWrite(Node *&root, std::span<const uint8_t> key,
} }
} }
// Precondition: `node->entryPresent` // Precondition: `node->entryPresent`, and node is not the root
void fixupMaxVersion(Node *node, ConflictSet::Impl *impl, WriteContext *tls) { void fixupMaxVersion(Node *node, WriteContext *tls) {
assert(node->parent);
InternalVersionT max; InternalVersionT max;
assert(node->entryPresent); assert(node->entryPresent);
max = std::max(node->entry.pointVersion, tls->zero); max = std::max(node->entry.pointVersion, tls->zero);
@@ -2893,7 +3012,7 @@ void fixupMaxVersion(Node *node, ConflictSet::Impl *impl, WriteContext *tls) {
default: // GCOVR_EXCL_LINE default: // GCOVR_EXCL_LINE
__builtin_unreachable(); // 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, void addWriteRange(Node *&root, std::span<const uint8_t> begin,
@@ -2904,44 +3023,30 @@ void addWriteRange(Node *&root, std::span<const uint8_t> begin,
std::min(begin.size(), end.size())); std::min(begin.size(), end.size()));
if (lcp == int(begin.size()) && end.size() == begin.size() + 1 && if (lcp == int(begin.size()) && end.size() == begin.size() + 1 &&
end.back() == 0) { end.back() == 0) {
return addPointWrite(root, begin, writeVersion, tls, impl); return addPointWrite(root, begin, writeVersion, tls);
} }
++tls->accum.range_writes; ++tls->accum.range_writes;
const bool beginIsPrefix = lcp == int(begin.size()); const bool beginIsPrefix = lcp == int(begin.size());
Node **useAsRoot = Node **useAsRoot = insert(&root, begin.subspan(0, lcp), writeVersion, tls);
insert(&root, begin.subspan(0, lcp), writeVersion, tls, impl);
int consumed = lcp; begin = begin.subspan(lcp, begin.size() - lcp);
end = end.subspan(lcp, end.size() - 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;
auto *beginNode = *insert(useAsRoot, begin, writeVersion, tls);
addKey(beginNode); addKey(beginNode);
beginNode->entryPresent = true; if (!beginNode->entryPresent) {
if (insertedBegin) {
++tls->accum.entries_inserted; ++tls->accum.entries_inserted;
auto *p = nextLogical(beginNode); auto *p = nextLogical(beginNode);
beginNode->entry.rangeVersion = beginNode->entry.rangeVersion =
p == nullptr ? tls->zero : std::max(p->entry.rangeVersion, tls->zero); 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; beginNode->entry.pointVersion = writeVersion;
auto *endNode = *insert(useAsRoot, end, writeVersion, tls, impl); auto *endNode = *insert(useAsRoot, end, writeVersion, tls);
const bool insertedEnd = !endNode->entryPresent;
addKey(endNode); addKey(endNode);
endNode->entryPresent = true; if (!endNode->entryPresent) {
if (insertedEnd) {
++tls->accum.entries_inserted; ++tls->accum.entries_inserted;
auto *p = nextLogical(endNode); auto *p = nextLogical(endNode);
endNode->entry.pointVersion = endNode->entry.pointVersion =
@@ -2951,15 +3056,25 @@ void addWriteRange(Node *&root, std::span<const uint8_t> begin,
beginNode = *useAsRoot; beginNode = *useAsRoot;
assert(beginNode->entryPresent); assert(beginNode->entryPresent);
} }
endNode->entryPresent = true;
} }
endNode->entry.rangeVersion = writeVersion; endNode->entry.rangeVersion = writeVersion;
for (beginNode = nextLogical(beginNode); beginNode != endNode; // Erase nodes in range
beginNode = erase(beginNode, tls, impl, /*logical*/ true, endNode)) { assert(!beginNode->endOfRange);
assert(!endNode->endOfRange);
endNode->endOfRange = true;
auto *iter = beginNode;
for (iter = nextLogical(iter); !iter->endOfRange;
iter = erase(iter, tls, impl, /*logical*/ true)) {
assert(!iter->endOfRange);
} }
assert(iter->endOfRange);
iter->endOfRange = false;
// Inserting end trashed endNode's maxVersion. Fix that // Inserting end trashed the last node's maxVersion. Fix that. Safe to call
fixupMaxVersion(endNode, impl, tls); // since the end key always has non-zero size.
fixupMaxVersion(iter, tls);
} }
Node *firstGeqPhysical(Node *n, const std::span<const uint8_t> key) { Node *firstGeqPhysical(Node *n, const std::span<const uint8_t> key) {
@@ -3070,7 +3185,7 @@ struct __attribute__((visibility("hidden"))) ConflictSet::Impl {
if (oldestExtantVersion < writeVersion - kMaxCorrectVersionWindow) if (oldestExtantVersion < writeVersion - kMaxCorrectVersionWindow)
[[unlikely]] { [[unlikely]] {
if (writeVersion > newestVersionFullPrecision + kNominalVersionWindow) { if (writeVersion > newestVersionFullPrecision + kNominalVersionWindow) {
destroyTree(root); eraseTree(root, &tls);
init(writeVersion - kNominalVersionWindow); init(writeVersion - kNominalVersionWindow);
} }
@@ -3096,7 +3211,7 @@ struct __attribute__((visibility("hidden"))) ConflictSet::Impl {
addWriteRange(root, begin, end, InternalVersionT(writeVersion), &tls, addWriteRange(root, begin, end, InternalVersionT(writeVersion), &tls,
this); this);
} else { } else {
addPointWrite(root, begin, InternalVersionT(writeVersion), &tls, this); addPointWrite(root, begin, InternalVersionT(writeVersion), &tls);
} }
} }
@@ -3127,7 +3242,6 @@ struct __attribute__((visibility("hidden"))) ConflictSet::Impl {
// Don't erase the root // Don't erase the root
if (n == root) { if (n == root) {
rezero(n, oldestVersion); rezero(n, oldestVersion);
rootMaxVersion = std::max(rootMaxVersion, oldestVersion);
n = nextPhysical(n); n = nextPhysical(n);
} }
int64_t set_oldest_iterations_accum = 0; int64_t set_oldest_iterations_accum = 0;
@@ -3145,8 +3259,7 @@ 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);
Node *dummy = nullptr; n = erase(n, &tls, this, /*logical*/ false);
n = erase(n, &tls, this, /*logical*/ false, dummy);
} else { } else {
maybeDecreaseCapacity(n, &tls, this); maybeDecreaseCapacity(n, &tls, this);
n = nextPhysical(n); n = nextPhysical(n);
@@ -3218,7 +3331,6 @@ struct __attribute__((visibility("hidden"))) ConflictSet::Impl {
root = tls.allocate<Node0>(0); root = tls.allocate<Node0>(0);
root->numChildren = 0; root->numChildren = 0;
root->parent = nullptr; root->parent = nullptr;
rootMaxVersion = this->oldestVersion;
root->entryPresent = false; root->entryPresent = false;
root->partialKeyLen = 0; root->partialKeyLen = 0;
@@ -3238,7 +3350,7 @@ struct __attribute__((visibility("hidden"))) ConflictSet::Impl {
initMetrics(); initMetrics();
} }
~Impl() { ~Impl() {
destroyTree(root); eraseTree(root, &tls);
safe_free(metrics, metricsCount * sizeof(metrics[0])); safe_free(metrics, metricsCount * sizeof(metrics[0]));
} }
@@ -3249,7 +3361,6 @@ struct __attribute__((visibility("hidden"))) ConflictSet::Impl {
int64_t keyUpdates; int64_t keyUpdates;
Node *root; Node *root;
InternalVersionT rootMaxVersion;
InternalVersionT oldestVersion; InternalVersionT oldestVersion;
int64_t oldestVersionFullPrecision; int64_t oldestVersionFullPrecision;
int64_t oldestExtantVersion; int64_t oldestExtantVersion;
@@ -3348,118 +3459,6 @@ Metric::Metric(ConflictSet::Impl *impl, const char *name, const char *help,
++impl->metricsCount; ++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) { Node *&getInTree(Node *n, ConflictSet::Impl *impl) {
return n->parent == nullptr ? impl->root return n->parent == nullptr ? impl->root
: getChildExists(n->parent, n->parentsIndex); : getChildExists(n->parent, n->parentsIndex);
@@ -3741,13 +3740,13 @@ std::string getSearchPath(Node *n) {
fprintf(file, fprintf(file,
" k_%p [label=\"m=%" PRId64 " p=%" PRId64 " r=%" PRId64 " k_%p [label=\"m=%" PRId64 " p=%" PRId64 " r=%" PRId64
"\n%s\", pos=\"%d,%d!\"];\n", "\n%s\", pos=\"%d,%d!\"];\n",
(void *)n, maxVersion(n, impl).toInt64(), (void *)n, maxVersion(n).toInt64(),
n->entry.pointVersion.toInt64(), n->entry.pointVersion.toInt64(),
n->entry.rangeVersion.toInt64(), n->entry.rangeVersion.toInt64(),
getPartialKeyPrintable(n).c_str(), x, y); getPartialKeyPrintable(n).c_str(), x, y);
} else { } else {
fprintf(file, " k_%p [label=\"m=%" PRId64 "\n%s\", pos=\"%d,%d!\"];\n", 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); getPartialKeyPrintable(n).c_str(), x, y);
} }
x += kSeparation; x += kSeparation;
@@ -3858,11 +3857,11 @@ checkMaxVersion(Node *root, Node *node, InternalVersionT oldestVersion,
expected = std::max(expected, borrowed->entry.rangeVersion); expected = std::max(expected, borrowed->entry.rangeVersion);
} }
} }
if (maxVersion(node, impl) > oldestVersion && if (node->parent && maxVersion(node) > oldestVersion &&
maxVersion(node, impl) != expected) { maxVersion(node) != expected) {
fprintf(stderr, "%s has max version %" PRId64 " . Expected %" PRId64 "\n", fprintf(stderr, "%s has max version %" PRId64 " . Expected %" PRId64 "\n",
getSearchPathPrintable(node).c_str(), getSearchPathPrintable(node).c_str(), maxVersion(node).toInt64(),
maxVersion(node, impl).toInt64(), expected.toInt64()); expected.toInt64());
success = false; success = false;
} }
return expected; return expected;
@@ -3926,7 +3925,7 @@ checkMaxVersion(Node *root, Node *node, InternalVersionT oldestVersion,
bool success = true; bool success = true;
if (node->partialKeyLen > 0) { if (node->partialKeyLen > 0) {
fprintf(stderr, "Root cannot have a partial key"); fprintf(stderr, "Root cannot have a partial key\n");
success = false; success = false;
} }
checkParentPointers(node, success); checkParentPointers(node, success);

View File

@@ -273,6 +273,16 @@ template <class T> struct Vector {
size_ += slice.size(); 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)); } void push_back(const T &t) { append(std::span<const T>(&t, 1)); }
T *begin() { return t; } T *begin() { return t; }
@@ -738,7 +748,10 @@ struct TestDriver {
fprintf(stderr, "%p Set oldest version: %" PRId64 "\n", this, fprintf(stderr, "%p Set oldest version: %" PRId64 "\n", this,
oldestVersion); oldestVersion);
#endif #endif
CALLGRIND_START_INSTRUMENTATION;
cs.setOldestVersion(oldestVersion); cs.setOldestVersion(oldestVersion);
CALLGRIND_STOP_INSTRUMENTATION;
if constexpr (kEnableAssertions) { if constexpr (kEnableAssertions) {
refImpl.setOldestVersion(oldestVersion); refImpl.setOldestVersion(oldestVersion);
} }

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 | 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` | 11.04 | 90,614,308.12 | 0.8% | 180.38 | 55.13 | 3.272 | 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` | 14.96 | 66,843,629.12 | 0.4% | 274.41 | 74.73 | 3.672 | 55.05 | 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` | 37.06 | 26,982,847.61 | 0.2% | 791.04 | 185.28 | 4.269 | 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` | 17.89 | 55,887,365.73 | 0.6% | 335.54 | 89.79 | 3.737 | 43.84 | 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` | 31.85 | 31,394,336.65 | 0.3% | 615.32 | 159.63 | 3.855 | 87.69 | 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` | 36.17 | 27,647,221.45 | 0.6% | 705.11 | 182.80 | 3.857 | 100.62 | 0.1% | 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` | 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`
| 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` | 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`
| 75.26 | 13,286,517.16 | 0.5% | 1,590.01 | 386.67 | 4.112 | 258.00 | 0.0% | 0.01 | `create and destroy` | 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 # "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 ## 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 ## hash table

View File

@@ -6,11 +6,15 @@
#include <string.h> #include <string.h>
#include <string> #include <string>
#include <string_view> #include <string_view>
#include <sys/ioctl.h>
#include <sys/resource.h> #include <sys/resource.h>
#include <sys/socket.h> #include <sys/socket.h>
#include <sys/types.h>
#include <sys/uio.h> #include <sys/uio.h>
#include <thread> #include <thread>
#include <unistd.h> #include <unistd.h>
#include <utility>
#include <vector>
#include "ConflictSet.h" #include "ConflictSet.h"
#include "third_party/nadeau.h" #include "third_party/nadeau.h"
@@ -20,8 +24,8 @@ std::atomic<int64_t> transactions;
constexpr int kBaseSearchDepth = 32; constexpr int kBaseSearchDepth = 32;
constexpr int kWindowSize = 10000000; constexpr int kWindowSize = 10000000;
std::basic_string<uint8_t> numToKey(int64_t num) { std::string numToKey(int64_t num) {
std::basic_string<uint8_t> result; std::string result;
result.resize(kBaseSearchDepth + sizeof(int64_t)); result.resize(kBaseSearchDepth + sizeof(int64_t));
memset(result.data(), 0, kBaseSearchDepth); memset(result.data(), 0, kBaseSearchDepth);
int64_t be = __builtin_bswap64(num); int64_t be = __builtin_bswap64(num);
@@ -41,13 +45,13 @@ void workload(weaselab::ConflictSet *cs) {
auto pointK = numToKey(pointRv); auto pointK = numToKey(pointRv);
weaselab::ConflictSet::ReadRange reads[] = { weaselab::ConflictSet::ReadRange reads[] = {
{ {
{pointK.data(), int(pointK.size())}, {(const uint8_t *)pointK.data(), int(pointK.size())},
{nullptr, 0}, {nullptr, 0},
pointRv, pointRv,
}, },
{ {
{beginK.data(), int(beginK.size())}, {(const uint8_t *)beginK.data(), int(beginK.size())},
{endK.data(), int(endK.size())}, {(const uint8_t *)endK.data(), int(endK.size())},
version - 2, version - 2,
}, },
}; };
@@ -66,7 +70,7 @@ void workload(weaselab::ConflictSet *cs) {
{ {
weaselab::ConflictSet::WriteRange w; weaselab::ConflictSet::WriteRange w;
auto k = numToKey(version); auto k = numToKey(version);
w.begin.p = k.data(); w.begin.p = (const uint8_t *)k.data();
w.end.len = 0; w.end.len = 0;
if (version % (kWindowSize / 2) == 0) { if (version % (kWindowSize / 2) == 0) {
for (int l = 0; l <= k.size(); ++l) { for (int l = 0; l <= k.size(); ++l) {
@@ -79,9 +83,9 @@ void workload(weaselab::ConflictSet *cs) {
int64_t beginN = version - kWindowSize + rand() % kWindowSize; int64_t beginN = version - kWindowSize + rand() % kWindowSize;
auto b = numToKey(beginN); auto b = numToKey(beginN);
auto e = numToKey(beginN + 1000); auto e = numToKey(beginN + 1000);
w.begin.p = b.data(); w.begin.p = (const uint8_t *)b.data();
w.begin.len = b.size(); w.begin.len = b.size();
w.end.p = e.data(); w.end.p = (const uint8_t *)e.data();
w.end.len = e.size(); w.end.len = e.size();
cs->addWrites(&w, 1, version); cs->addWrites(&w, 1, version);
} }
@@ -164,6 +168,68 @@ double toSeconds(timeval t) {
return double(t.tv_sec) + double(t.tv_usec) * 1e-6; return double(t.tv_sec) + double(t.tv_usec) * 1e-6;
} }
#ifdef __linux__
#include <linux/perf_event.h>
struct PerfCounter {
PerfCounter(int type, int config, const std::string &labels = {},
int groupLeaderFd = -1)
: labels(labels) {
struct perf_event_attr pe;
memset(&pe, 0, sizeof(pe));
pe.type = type;
pe.size = sizeof(pe);
pe.config = config;
pe.inherit = 1;
pe.exclude_kernel = 1;
pe.exclude_hv = 1;
fd = perf_event_open(&pe, 0, -1, groupLeaderFd, 0);
if (fd < 0 && errno != ENOENT && errno != EINVAL) {
perror(labels.c_str());
}
}
int64_t total() const {
int64_t count;
if (read(fd, &count, sizeof(count)) != sizeof(count)) {
perror("read instructions from perf");
abort();
}
return count;
}
PerfCounter(PerfCounter &&other)
: fd(std::exchange(other.fd, -1)), labels(std::move(other.labels)) {}
PerfCounter &operator=(PerfCounter &&other) {
fd = std::exchange(other.fd, -1);
labels = std::move(other.labels);
return *this;
}
~PerfCounter() {
if (fd >= 0) {
close(fd);
}
}
bool ok() const { return fd >= 0; }
const std::string &getLabels() const { return labels; }
int getFd() const { return fd; }
private:
int fd;
std::string labels;
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;
}
};
#endif
int main(int argc, char **argv) { int main(int argc, char **argv) {
if (argc != 3) { if (argc != 3) {
goto fail; goto fail;
@@ -176,6 +242,50 @@ int main(int argc, char **argv) {
int metricsCount; int metricsCount;
cs.getMetricsV1(&metrics, &metricsCount); cs.getMetricsV1(&metrics, &metricsCount);
#ifdef __linux__
PerfCounter instructions{PERF_TYPE_HARDWARE, PERF_COUNT_HW_INSTRUCTIONS};
PerfCounter cycles{PERF_TYPE_HARDWARE, PERF_COUNT_HW_CPU_CYCLES, "",
instructions.getFd()};
std::vector<PerfCounter> cacheCounters;
for (auto [id, idStr] : std::initializer_list<std::pair<int, std::string>>{
{PERF_COUNT_HW_CACHE_L1D, "l1d"},
{PERF_COUNT_HW_CACHE_L1I, "l1i"},
{PERF_COUNT_HW_CACHE_LL, "ll"},
{PERF_COUNT_HW_CACHE_DTLB, "dtlb"},
{PERF_COUNT_HW_CACHE_ITLB, "itlb"},
{PERF_COUNT_HW_CACHE_BPU, "bpu"},
{PERF_COUNT_HW_CACHE_NODE, "node"},
}) {
for (auto [op, opStr] :
std::initializer_list<std::pair<int, std::string>>{
{PERF_COUNT_HW_CACHE_OP_READ, "read"},
{PERF_COUNT_HW_CACHE_OP_WRITE, "write"},
{PERF_COUNT_HW_CACHE_OP_PREFETCH, "prefetch"},
}) {
int groupLeaderFd = -1;
for (auto [result, resultStr] :
std::initializer_list<std::pair<int, std::string>>{
{PERF_COUNT_HW_CACHE_RESULT_MISS, "miss"},
{PERF_COUNT_HW_CACHE_RESULT_ACCESS, "access"},
}) {
auto labels = "{id=\"" + idStr + "\", op=\"" + opStr +
"\", result=\"" + resultStr + "\"}";
cacheCounters.emplace_back(PERF_TYPE_HW_CACHE,
id | (op << 8) | (result << 16), labels,
groupLeaderFd);
if (!cacheCounters.back().ok()) {
cacheCounters.pop_back();
} else {
if (groupLeaderFd == -1) {
groupLeaderFd = cacheCounters.back().getFd();
}
}
}
}
}
#endif
auto w = std::thread{workload, &cs}; auto w = std::thread{workload, &cs};
for (;;) { for (;;) {
@@ -203,6 +313,24 @@ int main(int argc, char **argv) {
"transactions_total "; "transactions_total ";
body += std::to_string(transactions.load(std::memory_order_relaxed)); body += std::to_string(transactions.load(std::memory_order_relaxed));
body += "\n"; body += "\n";
#ifdef __linux__
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";
body += "# HELP cache_event_total Total number of cache events\n"
"# TYPE cache_event_total counter\n";
for (const auto &counter : cacheCounters) {
body += "cache_event_total" + counter.getLabels() + " " +
std::to_string(counter.total()) + "\n";
}
#endif
for (int i = 0; i < metricsCount; ++i) { for (int i = 0; i < metricsCount; ++i) {
body += "# HELP "; 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.

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.