9 Commits

Author SHA1 Message Date
andrew e59fee39c7 Start versions at zero in fuzz test for now
Tests / Clang total: 1776, passed: 1776
Clang |Total|New|Outstanding|Fixed|Trend |:-:|:-:|:-:|:-:|:-: |0|0|0|0|:clap:
Tests / SIMD fallback total: 1776, passed: 1776
Tests / 32-bit versions total: 1776, passed: 1776
Tests / Release [gcc] total: 1776, passed: 1776
GNU C Compiler (gcc) |Total|New|Outstanding|Fixed|Trend |:-:|:-:|:-:|:-:|:-: |0|0|0|0|:clap:
weaselab/conflict-set/pipeline/head There was a failure building this commit
2024-07-03 07:55:07 -07:00
andrew 3e2c8310bb Fix several bugs related to extant versions too old 2024-07-03 07:53:03 -07:00
andrew 8264f1342d Fix some precision issues 2024-07-02 13:55:05 -07:00
andrew 5d7e9c6f85 Compare against oldestVersionFullPrecision at full precision 2024-07-02 13:53:56 -07:00
andrew cdf42fcb34 Add gcScanStep
Also remove oldestVersion arg in write call tree
2024-07-02 13:06:53 -07:00
andrew cbe40b5dba Fix missing debug verbose guard 2024-07-02 12:48:00 -07:00
andrew a04e81b3ff Add version window constants 2024-07-02 12:45:35 -07:00
andrew 0be97a34b6 Add new version window requirement to reference impl 2024-07-02 12:42:03 -07:00
andrew 68ab9a9f08 Commit to 32-bit versions 2024-07-02 10:30:22 -07:00
3 changed files with 282 additions and 129 deletions
-8
View File
@@ -59,10 +59,6 @@ cmake_pop_check_state()
option(USE_SIMD_FALLBACK option(USE_SIMD_FALLBACK
"Use fallback implementations of functions that use SIMD" OFF) "Use fallback implementations of functions that use SIMD" OFF)
option(
USE_32_BIT_VERSIONS
"Store 32 bit versions internally, and rely on versions never being different by more than 2e9"
OFF)
# This is encouraged according to # This is encouraged according to
# https://valgrind.org/docs/manual/manual-core-adv.html#manual-core-adv.clientreq # https://valgrind.org/docs/manual/manual-core-adv.html#manual-core-adv.clientreq
@@ -107,10 +103,6 @@ if(NOT USE_SIMD_FALLBACK)
endif() endif()
endif() endif()
if(USE_32_BIT_VERSIONS)
add_compile_definitions(INTERNAL_VERSION_32_BIT=1)
endif()
set(CMAKE_CXX_IMPLICIT_LINK_LIBRARIES "") set(CMAKE_CXX_IMPLICIT_LINK_LIBRARIES "")
add_library(${PROJECT_NAME}-object OBJECT ConflictSet.cpp) add_library(${PROJECT_NAME}-object OBJECT ConflictSet.cpp)
+261 -108
View File
@@ -78,18 +78,18 @@ constexpr void removeKey(struct Node *) {}
// ==================== BEGIN IMPLEMENTATION ==================== // ==================== BEGIN IMPLEMENTATION ====================
#ifndef INTERNAL_VERSION_32_BIT constexpr int64_t kNominalVersionWindow = 2e9;
#define INTERNAL_VERSION_32_BIT 0 constexpr int64_t kMaxCorrectVersionWindow =
#endif std::numeric_limits<int32_t>::max();
static_assert(kNominalVersionWindow <= kMaxCorrectVersionWindow);
#if INTERNAL_VERSION_32_BIT
struct InternalVersionT { struct InternalVersionT {
constexpr InternalVersionT() = default; constexpr InternalVersionT() = default;
constexpr explicit InternalVersionT(int64_t value) : value(value) {} constexpr explicit InternalVersionT(int64_t value) : value(value) {}
constexpr int64_t toInt64() const { return value; } // GCOVR_EXCL_LINE constexpr int64_t toInt64() const { return value; } // GCOVR_EXCL_LINE
constexpr auto operator<=>(const InternalVersionT &rhs) const { constexpr auto operator<=>(const InternalVersionT &rhs) const {
// Maintains ordering after overflow, as long as the full-precision versions // Maintains ordering after overflow, as long as the full-precision versions
// are within ~2e9 of eachother. // are within `kMaxCorrectVersionWindow` of eachother.
return int32_t(value - rhs.value) <=> 0; return int32_t(value - rhs.value) <=> 0;
} }
constexpr bool operator==(const InternalVersionT &) const = default; constexpr bool operator==(const InternalVersionT &) const = default;
@@ -99,20 +99,6 @@ private:
uint32_t value; uint32_t value;
}; };
thread_local InternalVersionT InternalVersionT::zero; thread_local InternalVersionT InternalVersionT::zero;
#else
struct InternalVersionT {
constexpr InternalVersionT() = default;
constexpr explicit InternalVersionT(int64_t value) : value(value) {}
constexpr int64_t toInt64() const { return value; } // GCOVR_EXCL_LINE
constexpr auto operator<=>(const InternalVersionT &rhs) const = default;
constexpr bool operator==(const InternalVersionT &) const = default;
static const InternalVersionT zero;
private:
int64_t value;
};
const InternalVersionT InternalVersionT::zero{0};
#endif
struct Entry { struct Entry {
InternalVersionT pointVersion; InternalVersionT pointVersion;
@@ -527,13 +513,8 @@ std::string getSearchPath(Node *n);
// Each node with an entry present gets a budget of kBytesPerKey. Node0 always // Each node with an entry present gets a budget of kBytesPerKey. Node0 always
// has an entry present. // has an entry present.
// Induction hypothesis is that each node's surplus is >= kMinNodeSurplus // Induction hypothesis is that each node's surplus is >= kMinNodeSurplus
#if INTERNAL_VERSION_32_BIT
constexpr int kBytesPerKey = 112; constexpr int kBytesPerKey = 112;
constexpr int kMinNodeSurplus = 80; constexpr int kMinNodeSurplus = 80;
#else
constexpr int kBytesPerKey = 144;
constexpr int kMinNodeSurplus = 104;
#endif
constexpr int kMinChildrenNode3 = 2; constexpr int kMinChildrenNode3 = 2;
constexpr int kMinChildrenNode16 = 4; constexpr int kMinChildrenNode16 = 4;
constexpr int kMinChildrenNode48 = 17; constexpr int kMinChildrenNode48 = 17;
@@ -1230,6 +1211,53 @@ void maybeDecreaseCapacity(Node *&self, NodeAllocators *allocators,
freeAndMakeCapacityAtLeast(self, maxCapacity, allocators, impl, false); freeAndMakeCapacityAtLeast(self, maxCapacity, allocators, impl, false);
} }
void rezero(Node *n, InternalVersionT z) {
#if DEBUG_VERBOSE && !defined(NDEBUG)
fprintf(stderr, "rezero to %" PRId64 ": %s\n", z.toInt64(),
getSearchPathPrintable(n).c_str());
#endif
if (n->entryPresent) {
n->entry.pointVersion = std::max(n->entry.pointVersion, z);
n->entry.rangeVersion = std::max(n->entry.rangeVersion, z);
}
switch (n->getType()) {
case Type_Node0: {
} break;
case Type_Node3: {
auto *self = static_cast<Node3 *>(n);
for (int i = 0; i < 3; ++i) {
self->childMaxVersion[i] = std::max(self->childMaxVersion[i], z);
}
} break;
case Type_Node16: {
auto *self = static_cast<Node16 *>(n);
for (int i = 0; i < 16; ++i) {
self->childMaxVersion[i] = std::max(self->childMaxVersion[i], z);
}
} break;
case Type_Node48: {
auto *self = static_cast<Node48 *>(n);
for (int i = 0; i < 48; ++i) {
self->childMaxVersion[i] = std::max(self->childMaxVersion[i], z);
}
for (auto &m : self->maxOfMax) {
m = std::max(m, z);
}
} break;
case Type_Node256: {
auto *self = static_cast<Node256 *>(n);
for (int i = 0; i < 256; ++i) {
self->childMaxVersion[i] = std::max(self->childMaxVersion[i], z);
}
for (auto &m : self->maxOfMax) {
m = std::max(m, z);
}
} break;
default: // GCOVR_EXCL_LINE
__builtin_unreachable(); // GCOVR_EXCL_LINE
}
}
void maybeDownsize(Node *self, NodeAllocators *allocators, void maybeDownsize(Node *self, NodeAllocators *allocators,
ConflictSet::Impl *impl, Node *&dontInvalidate) { ConflictSet::Impl *impl, Node *&dontInvalidate) {
@@ -1281,6 +1309,9 @@ void maybeDownsize(Node *self, NodeAllocators *allocators,
// 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.
setMaxVersion(child, impl, childMaxVersion); setMaxVersion(child, impl, childMaxVersion);
if (child->parent) {
rezero(child->parent, InternalVersionT::zero);
}
getInTree(self, impl) = child; getInTree(self, impl) = child;
allocators->node3.release(self3); allocators->node3.release(self3);
@@ -1323,7 +1354,7 @@ void maybeDownsize(Node *self, NodeAllocators *allocators,
// the node after self. If erase invalidates the pointee of `dontInvalidate`, it // the node after self. If erase invalidates the pointee of `dontInvalidate`, it
// will update it to its new pointee as well. // will update it to its new pointee as well.
Node *erase(Node *self, NodeAllocators *allocators, ConflictSet::Impl *impl, Node *erase(Node *self, NodeAllocators *allocators, ConflictSet::Impl *impl,
Node *&dontInvalidate) { bool logical, Node *&dontInvalidate) {
assert(self->parent != nullptr); assert(self->parent != nullptr);
#if DEBUG_VERBOSE && !defined(NDEBUG) #if DEBUG_VERBOSE && !defined(NDEBUG)
@@ -1333,7 +1364,7 @@ Node *erase(Node *self, NodeAllocators *allocators, ConflictSet::Impl *impl,
Node *parent = self->parent; Node *parent = self->parent;
uint8_t parentsIndex = self->parentsIndex; uint8_t parentsIndex = self->parentsIndex;
auto *result = nextLogical(self); auto *result = logical ? nextLogical(self) : nextPhysical(self);
removeKey(self); removeKey(self);
self->entryPresent = false; self->entryPresent = false;
@@ -1833,7 +1864,6 @@ bool scan16(const InternalVersionT *vs, const uint8_t *is, int begin, int end,
uint64_t mask = vget_lane_u64( uint64_t mask = vget_lane_u64(
vreinterpret_u64_u8(vshrn_n_u16(vreinterpretq_u16_u8(results), 4)), 0); vreinterpret_u64_u8(vshrn_n_u16(vreinterpretq_u16_u8(results), 4)), 0);
#if INTERNAL_VERSION_32_BIT
uint32x4_t w4[4]; uint32x4_t w4[4];
memcpy(w4, vs, sizeof(w4)); memcpy(w4, vs, sizeof(w4));
uint32_t rv; uint32_t rv;
@@ -1853,12 +1883,6 @@ bool scan16(const InternalVersionT *vs, const uint8_t *is, int begin, int end,
uint64_t compared = vget_lane_u64( uint64_t compared = vget_lane_u64(
vreinterpret_u64_u8(vshrn_n_u16(vreinterpretq_u16_u8(combined), 4)), 0); vreinterpret_u64_u8(vshrn_n_u16(vreinterpretq_u16_u8(combined), 4)), 0);
#else
uint64_t compared = 0;
for (int i = 0; i < 16; ++i) {
compared |= uint64_t(vs[i] > readVersion) << (i << 2);
}
#endif
return !(compared & mask); return !(compared & mask);
@@ -1871,17 +1895,11 @@ bool scan16(const InternalVersionT *vs, const uint8_t *is, int begin, int end,
indices, _mm_max_epu8(indices, _mm_set1_epi8(end - begin)))); indices, _mm_max_epu8(indices, _mm_set1_epi8(end - begin))));
uint32_t compared = 0; uint32_t compared = 0;
#if INTERNAL_VERSION_32_BIT
if constexpr (kAVX512) { if constexpr (kAVX512) {
compared = compare16_32bit_avx512(vs, readVersion); compared = compare16_32bit_avx512(vs, readVersion);
} else { } else {
compared = compare16_32bit(vs, readVersion); compared = compare16_32bit(vs, readVersion);
} }
#else
for (int i = 0; i < 16; ++i) {
compared |= (vs[i] > readVersion) << i;
}
#endif
return !(compared & mask); return !(compared & mask);
#else #else
@@ -1914,7 +1932,7 @@ scan16(const InternalVersionT *vs, int begin, int end,
assert(0 <= end && end <= 16); assert(0 <= end && end <= 16);
assert(begin <= end); assert(begin <= end);
#if INTERNAL_VERSION_32_BIT && defined(HAS_ARM_NEON) #if defined(HAS_ARM_NEON)
uint32x4_t w4[4]; uint32x4_t w4[4];
memcpy(w4, vs, sizeof(w4)); memcpy(w4, vs, sizeof(w4));
uint32_t rv; uint32_t rv;
@@ -1938,7 +1956,7 @@ scan16(const InternalVersionT *vs, int begin, int end,
conflict &= end == 16 ? -1 : (uint64_t(1) << (end << 2)) - 1; conflict &= end == 16 ? -1 : (uint64_t(1) << (end << 2)) - 1;
conflict >>= begin << 2; conflict >>= begin << 2;
return !conflict; return !conflict;
#elif INTERNAL_VERSION_32_BIT && defined(HAS_AVX) #elif defined(HAS_AVX)
uint32_t conflict; uint32_t conflict;
if constexpr (kAVX512) { if constexpr (kAVX512) {
conflict = compare16_32bit_avx512(vs, readVersion); conflict = compare16_32bit_avx512(vs, readVersion);
@@ -2678,9 +2696,9 @@ void destroyTree(Node *root) {
} }
} }
void addPointWrite(Node *&root, InternalVersionT oldestVersion, void addPointWrite(Node *&root, std::span<const uint8_t> key,
std::span<const uint8_t> key, InternalVersionT writeVersion, InternalVersionT writeVersion, NodeAllocators *allocators,
NodeAllocators *allocators, ConflictSet::Impl *impl) { ConflictSet::Impl *impl) {
auto *n = insert<true>(&root, key, writeVersion, allocators, impl); auto *n = insert<true>(&root, key, writeVersion, allocators, impl);
if (!n->entryPresent) { if (!n->entryPresent) {
auto *p = nextLogical(n); auto *p = nextLogical(n);
@@ -2691,24 +2709,23 @@ void addPointWrite(Node *&root, InternalVersionT oldestVersion,
n->entry.pointVersion = writeVersion; n->entry.pointVersion = writeVersion;
setMaxVersion(n, impl, writeVersion); setMaxVersion(n, impl, writeVersion);
n->entry.rangeVersion = n->entry.rangeVersion =
p != nullptr ? p->entry.rangeVersion : oldestVersion; p == nullptr ? InternalVersionT::zero
: std::max(p->entry.rangeVersion, InternalVersionT::zero);
} else { } else {
assert(writeVersion >= n->entry.pointVersion); assert(writeVersion >= n->entry.pointVersion);
n->entry.pointVersion = writeVersion; n->entry.pointVersion = writeVersion;
} }
} }
void addWriteRange(Node *&root, InternalVersionT oldestVersion, void addWriteRange(Node *&root, std::span<const uint8_t> begin,
std::span<const uint8_t> begin, std::span<const uint8_t> end, std::span<const uint8_t> end, InternalVersionT writeVersion,
InternalVersionT writeVersion, NodeAllocators *allocators, NodeAllocators *allocators, ConflictSet::Impl *impl) {
ConflictSet::Impl *impl) {
int lcp = longestCommonPrefix(begin.data(), end.data(), int lcp = longestCommonPrefix(begin.data(), end.data(),
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, oldestVersion, begin, writeVersion, allocators, return addPointWrite(root, begin, writeVersion, allocators, impl);
impl);
} }
const bool beginIsPrefix = lcp == int(begin.size()); const bool beginIsPrefix = lcp == int(begin.size());
auto remaining = begin.subspan(0, lcp); auto remaining = begin.subspan(0, lcp);
@@ -2756,7 +2773,8 @@ void addWriteRange(Node *&root, InternalVersionT oldestVersion,
if (insertedBegin) { if (insertedBegin) {
auto *p = nextLogical(beginNode); auto *p = nextLogical(beginNode);
beginNode->entry.rangeVersion = beginNode->entry.rangeVersion =
p != nullptr ? p->entry.rangeVersion : oldestVersion; p == nullptr ? InternalVersionT::zero
: std::max(p->entry.rangeVersion, InternalVersionT::zero);
beginNode->entry.pointVersion = writeVersion; beginNode->entry.pointVersion = writeVersion;
assert(maxVersion(beginNode, impl) <= writeVersion); assert(maxVersion(beginNode, impl) <= writeVersion);
setMaxVersion(beginNode, impl, writeVersion); setMaxVersion(beginNode, impl, writeVersion);
@@ -2775,7 +2793,8 @@ void addWriteRange(Node *&root, InternalVersionT oldestVersion,
if (insertedEnd) { if (insertedEnd) {
auto *p = nextLogical(endNode); auto *p = nextLogical(endNode);
endNode->entry.pointVersion = endNode->entry.pointVersion =
p != nullptr ? p->entry.rangeVersion : oldestVersion; p == nullptr ? InternalVersionT::zero
: std::max(p->entry.rangeVersion, InternalVersionT::zero);
auto m = maxVersion(endNode, impl); auto m = maxVersion(endNode, impl);
setMaxVersion(endNode, impl, setMaxVersion(endNode, impl,
std::max<InternalVersionT>(m, endNode->entry.pointVersion)); std::max<InternalVersionT>(m, endNode->entry.pointVersion));
@@ -2790,21 +2809,16 @@ void addWriteRange(Node *&root, InternalVersionT oldestVersion,
} }
for (beginNode = nextLogical(beginNode); beginNode != endNode; for (beginNode = nextLogical(beginNode); beginNode != endNode;
beginNode = erase(beginNode, allocators, impl, endNode)) { beginNode =
erase(beginNode, allocators, impl, /*logical*/ true, endNode)) {
} }
} }
Iterator firstGeq(Node *n, const std::span<const uint8_t> key) { Node *firstGeqPhysical(Node *n, const std::span<const uint8_t> key) {
auto remaining = key; auto remaining = key;
for (;;) { for (;;) {
if (remaining.size() == 0) { if (remaining.size() == 0) {
if (n->entryPresent) { return n;
return {n, 0};
}
int c = getChildGeq(n, 0);
assert(c >= 0);
n = getChildExists(n, c);
goto downLeftSpine;
} }
auto *child = getChild(n, remaining[0]); auto *child = getChild(n, remaining[0]);
@@ -2812,7 +2826,7 @@ Iterator firstGeq(Node *n, const std::span<const uint8_t> key) {
int c = getChildGeq(n, remaining[0]); int c = getChildGeq(n, remaining[0]);
if (c >= 0) { if (c >= 0) {
n = getChildExists(n, c); n = getChildExists(n, c);
goto downLeftSpine; return n;
} else { } else {
n = nextSibling(n); n = nextSibling(n);
if (n == nullptr) { if (n == nullptr) {
@@ -2820,9 +2834,9 @@ Iterator firstGeq(Node *n, const std::span<const uint8_t> key) {
// final library, since we can't remove a key without introducing a // final library, since we can't remove a key without introducing a
// key after it, and the only production caller of firstGeq is for // key after it, and the only production caller of firstGeq is for
// resuming the setOldestVersion scan. // resuming the setOldestVersion scan.
return {nullptr, 1}; // GCOVR_EXCL_LINE return nullptr; // GCOVR_EXCL_LINE
} }
goto downLeftSpine; return n;
} }
} }
@@ -2835,10 +2849,10 @@ Iterator firstGeq(Node *n, const std::span<const uint8_t> key) {
if (i < commonLen) { if (i < commonLen) {
auto c = n->partialKey()[i] <=> remaining[i]; auto c = n->partialKey()[i] <=> remaining[i];
if (c > 0) { if (c > 0) {
goto downLeftSpine; return n;
} else { } else {
n = nextSibling(n); n = nextSibling(n);
goto downLeftSpine; return n;
} }
} }
if (commonLen == n->partialKeyLen) { if (commonLen == n->partialKeyLen) {
@@ -2847,19 +2861,10 @@ Iterator firstGeq(Node *n, const std::span<const uint8_t> key) {
} else if (n->partialKeyLen > int(remaining.size())) { } else if (n->partialKeyLen > int(remaining.size())) {
// 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 // eq node
goto downLeftSpine; return n;
} }
} }
} }
downLeftSpine:
for (;;) {
if (n->entryPresent) {
return {n, 1};
}
int c = getChildGeq(n, 0);
assert(c >= 0);
n = getChildExists(n, c);
}
} }
struct __attribute__((visibility("hidden"))) ConflictSet::Impl { struct __attribute__((visibility("hidden"))) ConflictSet::Impl {
@@ -2870,8 +2875,9 @@ struct __attribute__((visibility("hidden"))) ConflictSet::Impl {
auto begin = std::span<const uint8_t>(r.begin.p, r.begin.len); auto begin = std::span<const uint8_t>(r.begin.p, r.begin.len);
auto end = std::span<const uint8_t>(r.end.p, r.end.len); auto end = std::span<const uint8_t>(r.end.p, r.end.len);
result[i] = result[i] =
InternalVersionT(reads[i].readVersion) < oldestVersion || reads[i].readVersion < oldestVersionFullPrecision ||
reads[i].readVersion < newestVersionFullPrecision - 2e9 reads[i].readVersion <
newestVersionFullPrecision - kNominalVersionWindow
? TooOld ? TooOld
: (end.size() > 0 : (end.size() > 0
? checkRangeRead(root, begin, end, ? checkRangeRead(root, begin, end,
@@ -2885,50 +2891,49 @@ struct __attribute__((visibility("hidden"))) ConflictSet::Impl {
void addWrites(const WriteRange *writes, int count, int64_t writeVersion) { void addWrites(const WriteRange *writes, int count, int64_t writeVersion) {
assert(writeVersion >= newestVersionFullPrecision); assert(writeVersion >= newestVersionFullPrecision);
// TODO allow this condition
assert(writeVersion < newestVersionFullPrecision + kNominalVersionWindow);
newestVersionFullPrecision = writeVersion; newestVersionFullPrecision = writeVersion;
#if INTERNAL_VERSION_32_BIT setOldestVersion(
InternalVersionT::zero = oldestVersion; std::max(oldestVersionFullPrecision,
#endif newestVersionFullPrecision - kNominalVersionWindow));
while (oldestExtantVersion <
newestVersionFullPrecision - kMaxCorrectVersionWindow) {
gcScanStep(1000);
}
for (int i = 0; i < count; ++i) { for (int i = 0; i < count; ++i) {
const auto &w = writes[i]; const auto &w = writes[i];
auto begin = std::span<const uint8_t>(w.begin.p, w.begin.len); auto begin = std::span<const uint8_t>(w.begin.p, w.begin.len);
auto end = std::span<const uint8_t>(w.end.p, w.end.len); auto end = std::span<const uint8_t>(w.end.p, w.end.len);
if (w.end.len > 0) { if (w.end.len > 0) {
keyUpdates += 3; keyUpdates += 3;
addWriteRange(root, oldestVersion, begin, end, addWriteRange(root, begin, end, InternalVersionT(writeVersion),
InternalVersionT(writeVersion), &allocators, this); &allocators, this);
} else { } else {
keyUpdates += 2; keyUpdates += 2;
addPointWrite(root, oldestVersion, begin, addPointWrite(root, begin, InternalVersionT(writeVersion), &allocators,
InternalVersionT(writeVersion), &allocators, this); this);
} }
} }
} }
void setOldestVersion(int64_t o) { // Spends up to `fuel` gc'ing, and returns its unused fuel. Reclaims memory
InternalVersionT oldestVersion{o}; // and updates oldestExtantVersion after spending enough fuel.
assert(o >= oldestVersionFullPrecision); int64_t gcScanStep(int64_t fuel) {
this->oldestVersionFullPrecision = o; Node *n = firstGeqPhysical(root, removalKey);
this->oldestVersion = oldestVersion;
#if INTERNAL_VERSION_32_BIT
InternalVersionT::zero = oldestVersion;
#endif
#ifdef NDEBUG
// This is here for performance reasons, since we want to amortize the cost
// of storing the search path as a string. In tests, we want to exercise the
// rest of the code often.
if (keyUpdates < 100) {
return;
}
#endif
Node *n = firstGeq(root, removalKey).n;
// There's no way to erase removalKey without introducing a key after it // There's no way to erase removalKey without introducing a key after it
assert(n != nullptr); assert(n != nullptr);
// Don't erase the root // Don't erase the root
if (n == root) { if (n == root) {
rezero(n, oldestVersion);
rootMaxVersion = std::max(rootMaxVersion, oldestVersion);
n = nextPhysical(n); n = nextPhysical(n);
} }
for (; keyUpdates > 0 && n != nullptr; --keyUpdates) { for (; fuel > 0 && n != nullptr; --fuel) {
rezero(n, oldestVersion);
if (n->entryPresent && std::max(n->entry.pointVersion, if (n->entryPresent && std::max(n->entry.pointVersion,
n->entry.rangeVersion) <= oldestVersion) { n->entry.rangeVersion) <= oldestVersion) {
// Any transaction n would have prevented from committing is // Any transaction n would have prevented from committing is
@@ -2938,7 +2943,7 @@ struct __attribute__((visibility("hidden"))) ConflictSet::Impl {
// 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; Node *dummy = nullptr;
n = erase(n, &allocators, this, dummy); n = erase(n, &allocators, this, /*logical*/ false, dummy);
} else { } else {
maybeDecreaseCapacity(n, &allocators, this); maybeDecreaseCapacity(n, &allocators, this);
n = nextPhysical(n); n = nextPhysical(n);
@@ -2946,14 +2951,44 @@ struct __attribute__((visibility("hidden"))) ConflictSet::Impl {
} }
if (n == nullptr) { if (n == nullptr) {
removalKey = {}; removalKey = {};
oldestExtantVersion = oldestVersionAtGcBegin;
oldestVersionAtGcBegin = oldestVersionFullPrecision;
#if DEBUG_VERBOSE && !defined(NDEBUG)
fprintf(stderr,
"new oldestExtantVersion: %" PRId64
", new oldestVersionAtGcBegin: %" PRId64 "\n",
oldestExtantVersion, oldestVersionAtGcBegin);
#endif
} else { } else {
removalKeyArena = Arena(); removalKeyArena = Arena();
removalKey = getSearchPath(removalKeyArena, n); removalKey = getSearchPath(removalKeyArena, n);
} }
return fuel;
}
void setOldestVersion(int64_t o) {
if (o <= oldestVersionFullPrecision) {
return;
}
InternalVersionT oldestVersion{o};
this->oldestVersionFullPrecision = o;
this->oldestVersion = oldestVersion;
InternalVersionT::zero = oldestVersion;
#ifdef NDEBUG
// This is here for performance reasons, since we want to amortize the cost
// of storing the search path as a string. In tests, we want to exercise the
// rest of the code often.
if (keyUpdates < 100) {
return;
}
#endif
keyUpdates = gcScanStep(keyUpdates);
} }
explicit Impl(int64_t oldestVersion) explicit Impl(int64_t oldestVersion)
: oldestVersion(oldestVersion), oldestVersionFullPrecision(oldestVersion), : oldestVersion(oldestVersion), oldestVersionFullPrecision(oldestVersion),
oldestExtantVersion(oldestVersion),
oldestVersionAtGcBegin(oldestVersion),
newestVersionFullPrecision(oldestVersion) { newestVersionFullPrecision(oldestVersion) {
#if DEBUG_VERBOSE #if DEBUG_VERBOSE
fprintf(stderr, "radix_tree: create\n"); fprintf(stderr, "radix_tree: create\n");
@@ -2972,6 +3007,8 @@ struct __attribute__((visibility("hidden"))) ConflictSet::Impl {
root->entryPresent = true; root->entryPresent = true;
root->entry.pointVersion = this->oldestVersion; root->entry.pointVersion = this->oldestVersion;
root->entry.rangeVersion = this->oldestVersion; root->entry.rangeVersion = this->oldestVersion;
InternalVersionT::zero = this->oldestVersion;
} }
~Impl() { ~Impl() {
#if DEBUG_VERBOSE #if DEBUG_VERBOSE
@@ -2991,8 +3028,10 @@ struct __attribute__((visibility("hidden"))) ConflictSet::Impl {
InternalVersionT oldestVersion; InternalVersionT oldestVersion;
// TODO this doesn't fully mitigate the 32-bit precision issue, since we still // TODO this doesn't fully mitigate the 32-bit precision issue, since we still
// need to make sure we clean up versions in the tree before they fall out of // need to make sure we clean up versions in the tree before they fall out of
// the 2e9 window. // the `kMaxCorrectVersionWindow` window.
int64_t oldestVersionFullPrecision; int64_t oldestVersionFullPrecision;
int64_t oldestExtantVersion;
int64_t oldestVersionAtGcBegin;
int64_t newestVersionFullPrecision; int64_t newestVersionFullPrecision;
int64_t totalBytes = 0; int64_t totalBytes = 0;
}; };
@@ -3130,6 +3169,74 @@ int64_t internal_getBytes(ConflictSet::Impl *impl) { return impl->totalBytes; }
// GCOVR_EXCL_START // GCOVR_EXCL_START
Iterator firstGeqLogical(Node *n, const std::span<const uint8_t> key) {
auto remaining = key;
for (;;) {
if (remaining.size() == 0) {
if (n->entryPresent) {
return {n, 0};
}
int c = getChildGeq(n, 0);
assert(c >= 0);
n = getChildExists(n, c);
goto downLeftSpine;
}
auto *child = getChild(n, remaining[0]);
if (child == nullptr) {
int c = getChildGeq(n, remaining[0]);
if (c >= 0) {
n = getChildExists(n, c);
goto downLeftSpine;
} else {
n = nextSibling(n);
if (n == nullptr) {
// This line is genuinely unreachable from any entry point of the
// final library, since we can't remove a key without introducing a
// key after it, and the only production caller of firstGeq is for
// resuming the setOldestVersion scan.
return {nullptr, 1}; // GCOVR_EXCL_LINE
}
goto downLeftSpine;
}
}
n = child;
remaining = remaining.subspan(1, remaining.size() - 1);
if (n->partialKeyLen > 0) {
int commonLen = std::min<int>(n->partialKeyLen, remaining.size());
int i = longestCommonPrefix(n->partialKey(), remaining.data(), commonLen);
if (i < commonLen) {
auto c = n->partialKey()[i] <=> remaining[i];
if (c > 0) {
goto downLeftSpine;
} else {
n = nextSibling(n);
goto downLeftSpine;
}
}
if (commonLen == n->partialKeyLen) {
// partial key matches
remaining = remaining.subspan(commonLen, remaining.size() - commonLen);
} else if (n->partialKeyLen > int(remaining.size())) {
// n is the first physical node greater than remaining, and there's no
// eq node
goto downLeftSpine;
}
}
}
downLeftSpine:
for (;;) {
if (n->entryPresent) {
return {n, 1};
}
int c = getChildGeq(n, 0);
assert(c >= 0);
n = getChildExists(n, c);
}
}
void ConflictSet::check(const ReadRange *reads, Result *results, void ConflictSet::check(const ReadRange *reads, Result *results,
int count) const { int count) const {
internal_check(impl, reads, results, count); internal_check(impl, reads, results, count);
@@ -3326,14 +3433,60 @@ void checkParentPointers(Node *node, bool &success) {
} }
Iterator firstGeq(Node *n, std::string_view key) { Iterator firstGeq(Node *n, std::string_view key) {
return firstGeq( return firstGeqLogical(
n, std::span<const uint8_t>((const uint8_t *)key.data(), key.size())); n, std::span<const uint8_t>((const uint8_t *)key.data(), key.size()));
} }
void checkVersionsGeqOldestExtant(Node *n,
InternalVersionT oldestExtantVersion) {
if (n->entryPresent) {
assert(n->entry.pointVersion >= oldestExtantVersion);
assert(n->entry.rangeVersion >= oldestExtantVersion);
}
switch (n->getType()) {
case Type_Node0: {
} break;
case Type_Node3: {
auto *self = static_cast<Node3 *>(n);
for (int i = 0; i < 3; ++i) {
assert(self->childMaxVersion[i] >= oldestExtantVersion);
}
} break;
case Type_Node16: {
auto *self = static_cast<Node16 *>(n);
for (int i = 0; i < 16; ++i) {
assert(self->childMaxVersion[i] >= oldestExtantVersion);
}
} break;
case Type_Node48: {
auto *self = static_cast<Node48 *>(n);
for (int i = 0; i < 48; ++i) {
assert(self->childMaxVersion[i] >= oldestExtantVersion);
}
for (auto m : self->maxOfMax) {
assert(m >= oldestExtantVersion);
}
} break;
case Type_Node256: {
auto *self = static_cast<Node256 *>(n);
for (int i = 0; i < 256; ++i) {
assert(self->childMaxVersion[i] >= oldestExtantVersion);
}
for (auto m : self->maxOfMax) {
assert(m >= oldestExtantVersion);
}
} break;
default: // GCOVR_EXCL_LINE
__builtin_unreachable(); // GCOVR_EXCL_LINE
}
}
[[maybe_unused]] InternalVersionT [[maybe_unused]] InternalVersionT
checkMaxVersion(Node *root, Node *node, InternalVersionT oldestVersion, checkMaxVersion(Node *root, Node *node, InternalVersionT oldestVersion,
bool &success, ConflictSet::Impl *impl) { bool &success, ConflictSet::Impl *impl) {
InternalVersionT expected{0}; checkVersionsGeqOldestExtant(node,
InternalVersionT(impl->oldestExtantVersion));
auto expected = InternalVersionT::zero;
if (node->entryPresent) { if (node->entryPresent) {
expected = std::max(expected, node->entry.pointVersion); expected = std::max(expected, node->entry.pointVersion);
} }
+21 -13
View File
@@ -467,13 +467,15 @@ inline uint32_t Arbitrary::bounded(uint32_t s) {
// ==================== END ARBITRARY IMPL ==================== // ==================== END ARBITRARY IMPL ====================
struct ReferenceImpl { struct ReferenceImpl {
explicit ReferenceImpl(int64_t oldestVersion) : oldestVersion(oldestVersion) { explicit ReferenceImpl(int64_t oldestVersion)
: oldestVersion(oldestVersion), newestVersion(oldestVersion) {
writeVersionMap[""] = oldestVersion; writeVersionMap[""] = oldestVersion;
} }
void check(const ConflictSet::ReadRange *reads, ConflictSet::Result *results, void check(const ConflictSet::ReadRange *reads, ConflictSet::Result *results,
int count) const { int count) const {
for (int i = 0; i < count; ++i) { for (int i = 0; i < count; ++i) {
if (reads[i].readVersion < oldestVersion) { if (reads[i].readVersion < oldestVersion ||
reads[i].readVersion < newestVersion - 2e9) {
results[i] = ConflictSet::TooOld; results[i] = ConflictSet::TooOld;
continue; continue;
} }
@@ -495,6 +497,8 @@ struct ReferenceImpl {
} }
void addWrites(const ConflictSet::WriteRange *writes, int count, void addWrites(const ConflictSet::WriteRange *writes, int count,
int64_t writeVersion) { int64_t writeVersion) {
assert(writeVersion >= newestVersion);
newestVersion = writeVersion;
for (int i = 0; i < count; ++i) { for (int i = 0; i < count; ++i) {
auto begin = auto begin =
std::string((const char *)writes[i].begin.p, writes[i].begin.len); std::string((const char *)writes[i].begin.p, writes[i].begin.len);
@@ -519,6 +523,7 @@ struct ReferenceImpl {
} }
int64_t oldestVersion; int64_t oldestVersion;
int64_t newestVersion;
std::map<std::string, int64_t> writeVersionMap; std::map<std::string, int64_t> writeVersionMap;
}; };
@@ -578,8 +583,8 @@ template <class ConflictSetImpl> struct TestDriver {
explicit TestDriver(const uint8_t *data, size_t size) explicit TestDriver(const uint8_t *data, size_t size)
: arbitrary({data, size}) {} : arbitrary({data, size}) {}
int64_t oldestVersion = arbitrary.bounded(2) ? 0 : 0xfffffff0; int64_t oldestVersion = 0;
int64_t writeVersion = oldestVersion + 100; int64_t writeVersion = 0;
ConflictSetImpl cs{oldestVersion}; ConflictSetImpl cs{oldestVersion};
ReferenceImpl refImpl{oldestVersion}; ReferenceImpl refImpl{oldestVersion};
@@ -600,7 +605,7 @@ template <class ConflictSetImpl> struct TestDriver {
{ {
int numPointWrites = arbitrary.bounded(100); int numPointWrites = arbitrary.bounded(100);
int numRangeWrites = arbitrary.bounded(100); int numRangeWrites = arbitrary.bounded(100);
int64_t v = (writeVersion += arbitrary.bounded(10)); int64_t v = (writeVersion += arbitrary.bounded(2e9));
auto *writes = auto *writes =
new (arena) ConflictSet::WriteRange[numPointWrites + numRangeWrites]; new (arena) ConflictSet::WriteRange[numPointWrites + numRangeWrites];
auto keys = set<std::string_view>(arena); auto keys = set<std::string_view>(arena);
@@ -642,18 +647,21 @@ template <class ConflictSetImpl> struct TestDriver {
} }
#if DEBUG_VERBOSE && !defined(NDEBUG) #if DEBUG_VERBOSE && !defined(NDEBUG)
if (writes[i].end.len == 0) { if (writes[i].end.len == 0) {
fprintf(stderr, "Write: {%s} -> %" PRId64 "\n", fprintf(stderr, "Write: {%s}\n", printable(writes[i].begin).c_str());
printable(writes[i].begin).c_str(), writeVersion);
} else { } else {
fprintf(stderr, "Write: [%s, %s) -> %" PRId64 "\n", fprintf(stderr, "Write: [%s, %s)\n",
printable(writes[i].begin).c_str(), printable(writes[i].begin).c_str(),
printable(writes[i].end).c_str(), writeVersion); printable(writes[i].end).c_str());
} }
#endif #endif
} }
assert(iter == keys.end()); assert(iter == keys.end());
assert(i == numPointWrites + numRangeWrites); assert(i == numPointWrites + numRangeWrites);
#if DEBUG_VERBOSE && !defined(NDEBUG)
fprintf(stderr, "Write @ %" PRId64 "\n", v);
#endif
CALLGRIND_START_INSTRUMENTATION; CALLGRIND_START_INSTRUMENTATION;
cs.addWrites(writes, numPointWrites + numRangeWrites, v); cs.addWrites(writes, numPointWrites + numRangeWrites, v);
CALLGRIND_STOP_INSTRUMENTATION; CALLGRIND_STOP_INSTRUMENTATION;
@@ -710,12 +718,12 @@ template <class ConflictSetImpl> struct TestDriver {
reads[i].readVersion = v; reads[i].readVersion = v;
#if DEBUG_VERBOSE && !defined(NDEBUG) #if DEBUG_VERBOSE && !defined(NDEBUG)
if (reads[i].end.len == 0) { if (reads[i].end.len == 0) {
fprintf(stderr, "Read: {%s} @ %d\n", fprintf(stderr, "Read: {%s} @ %" PRId64 "\n",
printable(reads[i].begin).c_str(), int(reads[i].readVersion)); printable(reads[i].begin).c_str(), reads[i].readVersion);
} else { } else {
fprintf(stderr, "Read: [%s, %s) @ %d\n", fprintf(stderr, "Read: [%s, %s) @ %" PRId64 "\n",
printable(reads[i].begin).c_str(), printable(reads[i].begin).c_str(),
printable(reads[i].end).c_str(), int(reads[i].readVersion)); printable(reads[i].end).c_str(), reads[i].readVersion);
} }
#endif #endif
} }