Compare commits
31 Commits
493a6572ad
...
v0.0.11
Author | SHA1 | Date | |
---|---|---|---|
84c6a2bfc2 | |||
b5772a6aa0 | |||
e6c39981b9 | |||
c20c08f112 | |||
ac98d4a443 | |||
1d9e8ab68b | |||
7d86beb14c | |||
2fa954ed36 | |||
ded6e7fc2c | |||
781ba15cae | |||
9b56a74b2f | |||
6da9cbdec9 | |||
29c05187fb | |||
d89028dd2f | |||
09cf807747 | |||
051eb5919d | |||
ed5589e4ed | |||
a7b3d8fe4c | |||
c3a047fdf8 | |||
b4b469a175 | |||
0201e27498 | |||
2010920a2c | |||
19af8da65c | |||
80785e3c3b | |||
4580ee44b4 | |||
2d3985ca40 | |||
c8be68db40 | |||
f5d021d6b6 | |||
1c41605b53 | |||
8f03a105bb | |||
0e574856be |
@@ -1,7 +1,7 @@
|
||||
cmake_minimum_required(VERSION 3.18)
|
||||
project(
|
||||
conflict-set
|
||||
VERSION 0.0.10
|
||||
VERSION 0.0.11
|
||||
DESCRIPTION
|
||||
"A data structure for optimistic concurrency control on ranges of bitwise-lexicographically-ordered keys."
|
||||
HOMEPAGE_URL "https://git.weaselab.dev/weaselab/conflict-set"
|
||||
@@ -31,14 +31,12 @@ if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
|
||||
"MinSizeRel" "RelWithDebInfo")
|
||||
endif()
|
||||
|
||||
add_compile_options(
|
||||
-fdata-sections
|
||||
-ffunction-sections
|
||||
-Wswitch-enum
|
||||
-Werror=switch-enum
|
||||
-fPIC
|
||||
-g
|
||||
-fno-omit-frame-pointer)
|
||||
add_compile_options(-fdata-sections -ffunction-sections -Wswitch-enum
|
||||
-Werror=switch-enum -fPIC)
|
||||
if(NOT APPLE)
|
||||
# This causes some versions of clang to crash on macos
|
||||
add_compile_options(-g -fno-omit-frame-pointer)
|
||||
endif()
|
||||
|
||||
set(full_relro_flags "-pie;LINKER:-z,relro,-z,now,-z,noexecstack")
|
||||
cmake_push_check_state()
|
||||
@@ -342,6 +340,11 @@ if(CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR AND BUILD_TESTING)
|
||||
add_executable(driver_perf TestDriver.cpp)
|
||||
target_compile_definitions(driver_perf PRIVATE PERF_TEST=1)
|
||||
target_link_libraries(driver_perf PRIVATE ${PROJECT_NAME})
|
||||
|
||||
# server bench
|
||||
add_executable(server_bench ServerBench.cpp)
|
||||
target_link_libraries(server_bench PRIVATE ${PROJECT_NAME})
|
||||
set_target_properties(server_bench PROPERTIES SKIP_BUILD_RPATH ON)
|
||||
endif()
|
||||
|
||||
# packaging
|
||||
|
533
ConflictSet.cpp
533
ConflictSet.cpp
@@ -902,6 +902,7 @@ Node *&getChildExists(Node *self, uint8_t index) {
|
||||
}
|
||||
|
||||
InternalVersionT maxVersion(Node *n, ConflictSet::Impl *);
|
||||
InternalVersionT exchangeMaxVersion(Node *n, InternalVersionT newMax);
|
||||
|
||||
void setMaxVersion(Node *n, ConflictSet::Impl *, InternalVersionT maxVersion);
|
||||
|
||||
@@ -939,6 +940,54 @@ Node *getChild(Node *self, uint8_t index) {
|
||||
}
|
||||
}
|
||||
|
||||
struct ChildAndMaxVersion {
|
||||
Node *child;
|
||||
InternalVersionT maxVersion;
|
||||
};
|
||||
|
||||
ChildAndMaxVersion getChildAndMaxVersion(Node0 *, uint8_t) { return {}; }
|
||||
ChildAndMaxVersion getChildAndMaxVersion(Node3 *self, uint8_t index) {
|
||||
int i = getNodeIndex(self, index);
|
||||
if (i < 0) {
|
||||
return {};
|
||||
}
|
||||
return {self->children[i], self->childMaxVersion[i]};
|
||||
}
|
||||
ChildAndMaxVersion getChildAndMaxVersion(Node16 *self, uint8_t index) {
|
||||
int i = getNodeIndex(self, index);
|
||||
if (i < 0) {
|
||||
return {};
|
||||
}
|
||||
return {self->children[i], self->childMaxVersion[i]};
|
||||
}
|
||||
ChildAndMaxVersion getChildAndMaxVersion(Node48 *self, uint8_t index) {
|
||||
int i = self->index[index];
|
||||
if (i < 0) {
|
||||
return {};
|
||||
}
|
||||
return {self->children[i], self->childMaxVersion[i]};
|
||||
}
|
||||
ChildAndMaxVersion getChildAndMaxVersion(Node256 *self, uint8_t index) {
|
||||
return {self->children[index], self->childMaxVersion[index]};
|
||||
}
|
||||
|
||||
ChildAndMaxVersion getChildAndMaxVersion(Node *self, uint8_t index) {
|
||||
switch (self->getType()) {
|
||||
case Type_Node0:
|
||||
return getChildAndMaxVersion(static_cast<Node0 *>(self), index);
|
||||
case Type_Node3:
|
||||
return getChildAndMaxVersion(static_cast<Node3 *>(self), index);
|
||||
case Type_Node16:
|
||||
return getChildAndMaxVersion(static_cast<Node16 *>(self), index);
|
||||
case Type_Node48:
|
||||
return getChildAndMaxVersion(static_cast<Node48 *>(self), index);
|
||||
case Type_Node256:
|
||||
return getChildAndMaxVersion(static_cast<Node256 *>(self), index);
|
||||
default: // GCOVR_EXCL_LINE
|
||||
__builtin_unreachable(); // GCOVR_EXCL_LINE
|
||||
}
|
||||
}
|
||||
|
||||
template <class NodeT> Node *getChildGeqSimd(NodeT *self, int child) {
|
||||
static_assert(std::is_same_v<NodeT, Node3> || std::is_same_v<NodeT, Node16>);
|
||||
|
||||
@@ -1075,8 +1124,10 @@ Node *getFirstChildExists(Node *self) {
|
||||
}
|
||||
|
||||
// Caller is responsible for assigning a non-null pointer to the returned
|
||||
// reference if null
|
||||
Node *&getOrCreateChild(Node *&self, uint8_t index, WriteContext *tls) {
|
||||
// reference if null. Updates child's max version to `newMaxVersion` if child
|
||||
// exists but does not have a partial key.
|
||||
Node *&getOrCreateChild(Node *&self, uint8_t index,
|
||||
InternalVersionT newMaxVersion, WriteContext *tls) {
|
||||
|
||||
// Fast path for if it exists already
|
||||
switch (self->getType()) {
|
||||
@@ -1086,6 +1137,9 @@ Node *&getOrCreateChild(Node *&self, uint8_t index, WriteContext *tls) {
|
||||
auto *self3 = static_cast<Node3 *>(self);
|
||||
int i = getNodeIndex(self3, index);
|
||||
if (i >= 0) {
|
||||
if (self3->children[i]->partialKeyLen == 0) {
|
||||
self3->childMaxVersion[i] = newMaxVersion;
|
||||
}
|
||||
return self3->children[i];
|
||||
}
|
||||
} break;
|
||||
@@ -1093,6 +1147,9 @@ Node *&getOrCreateChild(Node *&self, uint8_t index, WriteContext *tls) {
|
||||
auto *self16 = static_cast<Node16 *>(self);
|
||||
int i = getNodeIndex(self16, index);
|
||||
if (i >= 0) {
|
||||
if (self16->children[i]->partialKeyLen == 0) {
|
||||
self16->childMaxVersion[i] = newMaxVersion;
|
||||
}
|
||||
return self16->children[i];
|
||||
}
|
||||
} break;
|
||||
@@ -1100,12 +1157,23 @@ Node *&getOrCreateChild(Node *&self, uint8_t index, WriteContext *tls) {
|
||||
auto *self48 = static_cast<Node48 *>(self);
|
||||
int secondIndex = self48->index[index];
|
||||
if (secondIndex >= 0) {
|
||||
if (self48->children[secondIndex]->partialKeyLen == 0) {
|
||||
self48->childMaxVersion[secondIndex] = newMaxVersion;
|
||||
self48->maxOfMax[secondIndex >> Node48::kMaxOfMaxShift] =
|
||||
std::max(self48->maxOfMax[secondIndex >> Node48::kMaxOfMaxShift],
|
||||
newMaxVersion);
|
||||
}
|
||||
return self48->children[secondIndex];
|
||||
}
|
||||
} break;
|
||||
case Type_Node256: {
|
||||
auto *self256 = static_cast<Node256 *>(self);
|
||||
if (auto &result = self256->children[index]; result != nullptr) {
|
||||
if (self256->children[index]->partialKeyLen == 0) {
|
||||
self256->childMaxVersion[index] = newMaxVersion;
|
||||
self256->maxOfMax[index >> Node256::kMaxOfMaxShift] = std::max(
|
||||
self256->maxOfMax[index >> Node256::kMaxOfMaxShift], newMaxVersion);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
} break;
|
||||
@@ -1332,6 +1400,29 @@ void maybeDecreaseCapacity(Node *&self, WriteContext *tls,
|
||||
freeAndMakeCapacityAtLeast(self, maxCapacity, tls, impl, false);
|
||||
}
|
||||
|
||||
#if defined(HAS_AVX) && !defined(__SANITIZE_THREAD__)
|
||||
// This gets covered in local development
|
||||
// GCOVR_EXCL_START
|
||||
__attribute__((target("avx512f"))) void rezero16(InternalVersionT *vs,
|
||||
InternalVersionT zero) {
|
||||
uint32_t z;
|
||||
memcpy(&z, &zero, sizeof(z));
|
||||
const auto zvec = _mm512_set1_epi32(z);
|
||||
auto m = _mm512_cmplt_epi32_mask(
|
||||
_mm512_sub_epi32(_mm512_loadu_epi32(vs), zvec), _mm512_setzero_epi32());
|
||||
_mm512_mask_storeu_epi32(vs, m, zvec);
|
||||
}
|
||||
// GCOVR_EXCL_STOP
|
||||
|
||||
__attribute__((target("default")))
|
||||
#endif
|
||||
|
||||
void rezero16(InternalVersionT *vs, InternalVersionT zero) {
|
||||
for (int i = 0; i < 16; ++i) {
|
||||
vs[i] = std::max(vs[i], zero);
|
||||
}
|
||||
}
|
||||
|
||||
void rezero(Node *n, InternalVersionT z) {
|
||||
#if DEBUG_VERBOSE && !defined(NDEBUG)
|
||||
fprintf(stderr, "rezero to %" PRId64 ": %s\n", z.toInt64(),
|
||||
@@ -1352,14 +1443,12 @@ void rezero(Node *n, InternalVersionT 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);
|
||||
}
|
||||
rezero16(self->childMaxVersion, 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 (int i = 0; i < 48; i += 16) {
|
||||
rezero16(self->childMaxVersion + i, z);
|
||||
}
|
||||
for (auto &m : self->maxOfMax) {
|
||||
m = std::max(m, z);
|
||||
@@ -1367,12 +1456,10 @@ void rezero(Node *n, InternalVersionT 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);
|
||||
for (int i = 0; i < 256; i += 16) {
|
||||
rezero16(self->childMaxVersion + i, z);
|
||||
}
|
||||
rezero16(self->maxOfMax, z);
|
||||
} break;
|
||||
default: // GCOVR_EXCL_LINE
|
||||
__builtin_unreachable(); // GCOVR_EXCL_LINE
|
||||
@@ -1676,7 +1763,7 @@ int firstNeqStride(const uint8_t *ap, const uint8_t *bp) {
|
||||
// This gets covered in local development
|
||||
// GCOVR_EXCL_START
|
||||
#if defined(HAS_AVX) && !defined(__SANITIZE_THREAD__)
|
||||
__attribute__((target("avx512bw"))) int
|
||||
__attribute__((target("avx512f,avx512bw"))) int
|
||||
longestCommonPrefix(const uint8_t *ap, const uint8_t *bp, int cl) {
|
||||
int i = 0;
|
||||
int end = cl & ~63;
|
||||
@@ -1761,41 +1848,6 @@ int longestCommonPrefix(const uint8_t *ap, const uint8_t *bp, int cl) {
|
||||
return i;
|
||||
}
|
||||
|
||||
// Performs a physical search for remaining
|
||||
struct SearchStepWise {
|
||||
Node *n;
|
||||
std::span<const uint8_t> remaining;
|
||||
|
||||
SearchStepWise() {}
|
||||
SearchStepWise(Node *n, std::span<const uint8_t> remaining)
|
||||
: n(n), remaining(remaining) {
|
||||
assert(n->partialKeyLen == 0);
|
||||
}
|
||||
|
||||
bool step() {
|
||||
if (remaining.size() == 0) {
|
||||
return true;
|
||||
}
|
||||
auto *child = getChild(n, remaining[0]);
|
||||
if (child == nullptr) {
|
||||
return true;
|
||||
}
|
||||
if (child->partialKeyLen > 0) {
|
||||
int cl = std::min<int>(child->partialKeyLen, remaining.size() - 1);
|
||||
int i =
|
||||
longestCommonPrefix(child->partialKey(), remaining.data() + 1, cl);
|
||||
if (i != child->partialKeyLen) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
n = child;
|
||||
remaining =
|
||||
remaining.subspan(1 + child->partialKeyLen,
|
||||
remaining.size() - (1 + child->partialKeyLen));
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
// Logically this is the same as performing firstGeq and then checking against
|
||||
// point or range version according to cmp, but this version short circuits as
|
||||
// soon as it can prove that there's no conflict.
|
||||
@@ -1806,12 +1858,7 @@ bool checkPointRead(Node *n, const std::span<const uint8_t> key,
|
||||
fprintf(stderr, "Check point read: %s\n", printable(key).c_str());
|
||||
#endif
|
||||
auto remaining = key;
|
||||
auto *impl = tls->impl;
|
||||
for (;; ++tls->point_read_iterations_accum) {
|
||||
if (maxVersion(n, impl) <= readVersion) {
|
||||
++tls->point_read_short_circuit_accum;
|
||||
return true;
|
||||
}
|
||||
if (remaining.size() == 0) {
|
||||
if (n->entryPresent) {
|
||||
return n->entry.pointVersion <= readVersion;
|
||||
@@ -1820,7 +1867,7 @@ bool checkPointRead(Node *n, const std::span<const uint8_t> key,
|
||||
goto downLeftSpine;
|
||||
}
|
||||
|
||||
auto *child = getChild(n, remaining[0]);
|
||||
auto [child, maxV] = getChildAndMaxVersion(n, remaining[0]);
|
||||
if (child == nullptr) {
|
||||
auto c = getChildGeq(n, remaining[0]);
|
||||
if (c != nullptr) {
|
||||
@@ -1862,6 +1909,11 @@ bool checkPointRead(Node *n, const std::span<const uint8_t> key,
|
||||
goto downLeftSpine;
|
||||
}
|
||||
}
|
||||
|
||||
if (maxV <= readVersion) {
|
||||
++tls->point_read_short_circuit_accum;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
downLeftSpine:
|
||||
for (; !n->entryPresent; n = getFirstChildExists(n)) {
|
||||
@@ -1881,17 +1933,11 @@ bool checkPrefixRead(Node *n, const std::span<const uint8_t> key,
|
||||
auto remaining = key;
|
||||
auto *impl = tls->impl;
|
||||
for (;; ++tls->prefix_read_iterations_accum) {
|
||||
auto m = maxVersion(n, impl);
|
||||
if (remaining.size() == 0) {
|
||||
return m <= readVersion;
|
||||
return maxVersion(n, impl) <= readVersion;
|
||||
}
|
||||
|
||||
if (m <= readVersion) {
|
||||
++tls->prefix_read_short_circuit_accum;
|
||||
return true;
|
||||
}
|
||||
|
||||
auto *child = getChild(n, remaining[0]);
|
||||
auto [child, maxV] = getChildAndMaxVersion(n, remaining[0]);
|
||||
if (child == nullptr) {
|
||||
auto c = getChildGeq(n, remaining[0]);
|
||||
if (c != nullptr) {
|
||||
@@ -1937,6 +1983,11 @@ bool checkPrefixRead(Node *n, const std::span<const uint8_t> key,
|
||||
goto downLeftSpine;
|
||||
}
|
||||
}
|
||||
|
||||
if (maxV <= readVersion) {
|
||||
++tls->prefix_read_short_circuit_accum;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
downLeftSpine:
|
||||
for (; !n->entryPresent; n = getFirstChildExists(n)) {
|
||||
@@ -2053,13 +2104,9 @@ bool scan16(const InternalVersionT *vs, const uint8_t *is, int begin, int end,
|
||||
}
|
||||
|
||||
// Returns true if v[i] <= readVersion for all i such that begin <= i < end
|
||||
//
|
||||
// always_inline So that we can optimize when begin or end is a constant.
|
||||
// gcovr exclude annotation necessary because of always_inline?
|
||||
template <bool kAVX512>
|
||||
inline __attribute__((always_inline)) bool
|
||||
scan16(const InternalVersionT *vs, int begin, int end, // GCOVR_EXCL_LINE
|
||||
InternalVersionT readVersion) { // GCOVR_EXCL_LINE
|
||||
bool scan16(const InternalVersionT *vs, int begin, int end,
|
||||
InternalVersionT readVersion) {
|
||||
assert(0 <= begin && begin < 16);
|
||||
assert(0 <= end && end <= 16);
|
||||
assert(begin <= end);
|
||||
@@ -2114,8 +2161,9 @@ scan16(const InternalVersionT *vs, int begin, int end, // GCOVR_EXCL_LINE
|
||||
// path of n + [child], where child in (begin, end) is <= readVersion. Does not
|
||||
// account for the range version of firstGt(searchpath(n) + [end - 1])
|
||||
template <bool kAVX512>
|
||||
bool checkMaxBetweenExclusive(Node *n, int begin, int end,
|
||||
InternalVersionT readVersion, ReadContext *tls) {
|
||||
bool checkMaxBetweenExclusiveImpl(Node *n, int begin, int end,
|
||||
InternalVersionT readVersion,
|
||||
ReadContext *tls) {
|
||||
++tls->range_read_node_scan_accum;
|
||||
assume(-1 <= begin);
|
||||
assume(begin <= 256);
|
||||
@@ -2147,7 +2195,7 @@ bool checkMaxBetweenExclusive(Node *n, int begin, int end,
|
||||
if (!mask) {
|
||||
return true;
|
||||
}
|
||||
auto *child = self->children[__builtin_ctz(mask)];
|
||||
auto *child = self->children[std::countr_zero(mask)];
|
||||
const bool firstRangeOk =
|
||||
!child->entryPresent || child->entry.rangeVersion <= readVersion;
|
||||
uint32_t compared = 0;
|
||||
@@ -2182,7 +2230,7 @@ bool checkMaxBetweenExclusive(Node *n, int begin, int end,
|
||||
if (!mask) {
|
||||
return true;
|
||||
}
|
||||
auto *child = self->children[__builtin_ctzll(mask) >> 2];
|
||||
auto *child = self->children[std::countr_zero(mask) >> 2];
|
||||
const bool firstRangeOk =
|
||||
!child->entryPresent || child->entry.rangeVersion <= readVersion;
|
||||
|
||||
@@ -2222,7 +2270,7 @@ bool checkMaxBetweenExclusive(Node *n, int begin, int end,
|
||||
if (!mask) {
|
||||
return true;
|
||||
}
|
||||
auto *child = self->children[__builtin_ctz(mask)];
|
||||
auto *child = self->children[std::countr_zero(mask)];
|
||||
const bool firstRangeOk =
|
||||
!child->entryPresent || child->entry.rangeVersion <= readVersion;
|
||||
|
||||
@@ -2253,7 +2301,7 @@ bool checkMaxBetweenExclusive(Node *n, int begin, int end,
|
||||
if (!mask) {
|
||||
return true;
|
||||
}
|
||||
auto *child = self->children[__builtin_ctz(mask)];
|
||||
auto *child = self->children[std::countr_zero(mask)];
|
||||
const bool firstRangeOk =
|
||||
!child->entryPresent || child->entry.rangeVersion <= readVersion;
|
||||
uint32_t compared = 0;
|
||||
@@ -2363,6 +2411,22 @@ bool checkMaxBetweenExclusive(Node *n, int begin, int end,
|
||||
}
|
||||
}
|
||||
|
||||
#if defined(HAS_AVX) && !defined(__SANITIZE_THREAD__)
|
||||
// This gets covered in local development
|
||||
// GCOVR_EXCL_START
|
||||
__attribute__((target("avx512f"))) bool
|
||||
checkMaxBetweenExclusive(Node *n, int begin, int end,
|
||||
InternalVersionT readVersion, ReadContext *tls) {
|
||||
return checkMaxBetweenExclusiveImpl<true>(n, begin, end, readVersion, tls);
|
||||
}
|
||||
// GCOVR_EXCL_STOP
|
||||
__attribute__((target("default")))
|
||||
#endif
|
||||
bool checkMaxBetweenExclusive(Node *n, int begin, int end,
|
||||
InternalVersionT readVersion, ReadContext *tls) {
|
||||
return checkMaxBetweenExclusiveImpl<false>(n, begin, end, readVersion, tls);
|
||||
}
|
||||
|
||||
Vector<uint8_t> getSearchPath(Arena &arena, Node *n) {
|
||||
assert(n != nullptr);
|
||||
auto result = vector<uint8_t>(arena);
|
||||
@@ -2385,7 +2449,6 @@ Vector<uint8_t> getSearchPath(Arena &arena, Node *n) {
|
||||
//
|
||||
// Precondition: transitively, no child of n has a search path that's a longer
|
||||
// prefix of key than n
|
||||
template <bool kAVX512>
|
||||
bool checkRangeStartsWith(Node *n, std::span<const uint8_t> key, int begin,
|
||||
int end, InternalVersionT readVersion,
|
||||
ReadContext *tls) {
|
||||
@@ -2395,7 +2458,7 @@ bool checkRangeStartsWith(Node *n, std::span<const uint8_t> key, int begin,
|
||||
auto remaining = key;
|
||||
auto *impl = tls->impl;
|
||||
if (remaining.size() == 0) {
|
||||
return checkMaxBetweenExclusive<kAVX512>(n, begin, end, readVersion, tls);
|
||||
return checkMaxBetweenExclusive(n, begin, end, readVersion, tls);
|
||||
}
|
||||
|
||||
auto *child = getChild(n, remaining[0]);
|
||||
@@ -2454,7 +2517,7 @@ downLeftSpine:
|
||||
namespace {
|
||||
// Return true if the max version among all keys that start with key[:prefixLen]
|
||||
// that are >= key is <= readVersion
|
||||
template <bool kAVX512> struct CheckRangeLeftSide {
|
||||
struct CheckRangeLeftSide {
|
||||
CheckRangeLeftSide(Node *n, std::span<const uint8_t> key, int prefixLen,
|
||||
InternalVersionT readVersion, ReadContext *tls)
|
||||
: n(n), remaining(key), prefixLen(prefixLen), readVersion(readVersion),
|
||||
@@ -2476,10 +2539,6 @@ template <bool kAVX512> struct CheckRangeLeftSide {
|
||||
bool ok;
|
||||
|
||||
bool step() {
|
||||
if (maxVersion(n, impl) <= readVersion) {
|
||||
ok = true;
|
||||
return true;
|
||||
}
|
||||
if (remaining.size() == 0) {
|
||||
assert(searchPathLen >= prefixLen);
|
||||
ok = maxVersion(n, impl) <= readVersion;
|
||||
@@ -2487,14 +2546,13 @@ template <bool kAVX512> struct CheckRangeLeftSide {
|
||||
}
|
||||
|
||||
if (searchPathLen >= prefixLen) {
|
||||
if (!checkMaxBetweenExclusive<kAVX512>(n, remaining[0], 256, readVersion,
|
||||
tls)) {
|
||||
if (!checkMaxBetweenExclusive(n, remaining[0], 256, readVersion, tls)) {
|
||||
ok = false;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
auto *child = getChild(n, remaining[0]);
|
||||
auto [child, maxV] = getChildAndMaxVersion(n, remaining[0]);
|
||||
if (child == nullptr) {
|
||||
auto c = getChildGeq(n, remaining[0]);
|
||||
if (c != nullptr) {
|
||||
@@ -2557,6 +2615,10 @@ template <bool kAVX512> struct CheckRangeLeftSide {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (maxV <= readVersion) {
|
||||
ok = true;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -2570,7 +2632,7 @@ template <bool kAVX512> struct CheckRangeLeftSide {
|
||||
|
||||
// Return true if the max version among all keys that start with key[:prefixLen]
|
||||
// that are < key is <= readVersion
|
||||
template <bool kAVX512> struct CheckRangeRightSide {
|
||||
struct CheckRangeRightSide {
|
||||
CheckRangeRightSide(Node *n, std::span<const uint8_t> key, int prefixLen,
|
||||
InternalVersionT readVersion, ReadContext *tls)
|
||||
: n(n), key(key), remaining(key), prefixLen(prefixLen),
|
||||
@@ -2613,8 +2675,7 @@ template <bool kAVX512> struct CheckRangeRightSide {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!checkMaxBetweenExclusive<kAVX512>(n, -1, remaining[0], readVersion,
|
||||
tls)) {
|
||||
if (!checkMaxBetweenExclusive(n, -1, remaining[0], readVersion, tls)) {
|
||||
ok = false;
|
||||
return true;
|
||||
}
|
||||
@@ -2701,10 +2762,9 @@ template <bool kAVX512> struct CheckRangeRightSide {
|
||||
};
|
||||
} // namespace
|
||||
|
||||
template <bool kAVX512>
|
||||
bool checkRangeReadImpl(Node *n, std::span<const uint8_t> begin,
|
||||
std::span<const uint8_t> end,
|
||||
InternalVersionT readVersion, ReadContext *tls) {
|
||||
bool checkRangeRead(Node *n, std::span<const uint8_t> begin,
|
||||
std::span<const uint8_t> end, InternalVersionT readVersion,
|
||||
ReadContext *tls) {
|
||||
int lcp = longestCommonPrefix(begin.data(), end.data(),
|
||||
std::min(begin.size(), end.size()));
|
||||
if (lcp == int(begin.size()) && end.size() == begin.size() + 1 &&
|
||||
@@ -2718,50 +2778,63 @@ bool checkRangeReadImpl(Node *n, std::span<const uint8_t> begin,
|
||||
|
||||
++tls->range_read_accum;
|
||||
|
||||
SearchStepWise search{n, begin.subspan(0, lcp)};
|
||||
auto remaining = begin.subspan(0, lcp);
|
||||
Arena arena;
|
||||
auto *impl = tls->impl;
|
||||
// If the common prefix isn't a prefix of any physical entry in the tree, we
|
||||
// can go to "downLeftSpine"
|
||||
for (;; ++tls->range_read_iterations_accum) {
|
||||
assert(getSearchPath(arena, search.n) <=>
|
||||
begin.subspan(0, lcp - search.remaining.size()) ==
|
||||
assert(getSearchPath(arena, n) <=>
|
||||
begin.subspan(0, lcp - remaining.size()) ==
|
||||
0);
|
||||
if (maxVersion(search.n, impl) <= readVersion) {
|
||||
++tls->range_read_short_circuit_accum;
|
||||
return true;
|
||||
if (remaining.size() == 0) {
|
||||
break;
|
||||
}
|
||||
if (search.step()) {
|
||||
auto [child, v] = getChildAndMaxVersion(n, remaining[0]);
|
||||
if (child == nullptr) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (child->partialKeyLen > 0) {
|
||||
int cl = std::min<int>(child->partialKeyLen, remaining.size() - 1);
|
||||
int i =
|
||||
longestCommonPrefix(child->partialKey(), remaining.data() + 1, cl);
|
||||
if (i != child->partialKeyLen) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
assert(getSearchPath(arena, search.n) <=>
|
||||
begin.subspan(0, lcp - search.remaining.size()) ==
|
||||
if (v <= readVersion) {
|
||||
++tls->range_read_short_circuit_accum;
|
||||
return true;
|
||||
}
|
||||
n = child;
|
||||
remaining =
|
||||
remaining.subspan(1 + child->partialKeyLen,
|
||||
remaining.size() - (1 + child->partialKeyLen));
|
||||
}
|
||||
assert(getSearchPath(arena, n) <=> begin.subspan(0, lcp - remaining.size()) ==
|
||||
0);
|
||||
|
||||
const int consumed = lcp - search.remaining.size();
|
||||
const int consumed = lcp - remaining.size();
|
||||
assume(consumed >= 0);
|
||||
|
||||
begin = begin.subspan(consumed, int(begin.size()) - consumed);
|
||||
end = end.subspan(consumed, int(end.size()) - consumed);
|
||||
n = search.n;
|
||||
lcp -= consumed;
|
||||
|
||||
if (lcp == int(begin.size())) {
|
||||
CheckRangeRightSide<kAVX512> checkRangeRightSide{n, end, lcp, readVersion,
|
||||
tls};
|
||||
CheckRangeRightSide checkRangeRightSide{n, end, lcp, readVersion, tls};
|
||||
while (!checkRangeRightSide.step())
|
||||
;
|
||||
return checkRangeRightSide.ok;
|
||||
}
|
||||
|
||||
if (!checkRangeStartsWith<kAVX512>(n, begin.subspan(0, lcp), begin[lcp],
|
||||
end[lcp], readVersion, tls)) {
|
||||
if (!checkRangeStartsWith(n, begin.subspan(0, lcp), begin[lcp], end[lcp],
|
||||
readVersion, tls)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
CheckRangeLeftSide<kAVX512> checkRangeLeftSide{n, begin, lcp + 1, readVersion,
|
||||
tls};
|
||||
CheckRangeRightSide<kAVX512> checkRangeRightSide{n, end, lcp + 1, readVersion,
|
||||
tls};
|
||||
CheckRangeLeftSide checkRangeLeftSide{n, begin, lcp + 1, readVersion, tls};
|
||||
CheckRangeRightSide checkRangeRightSide{n, end, lcp + 1, readVersion, tls};
|
||||
|
||||
for (;;) {
|
||||
bool leftDone = checkRangeLeftSide.step();
|
||||
@@ -2796,89 +2869,47 @@ bool checkRangeReadImpl(Node *n, std::span<const uint8_t> begin,
|
||||
template __attribute__((target("avx512f"))) bool
|
||||
scan16<true>(const InternalVersionT *vs, const uint8_t *is, int begin, int end,
|
||||
InternalVersionT readVersion);
|
||||
template __attribute__((always_inline, target("avx512f"))) bool
|
||||
template __attribute__((target("avx512f"))) bool
|
||||
scan16<true>(const InternalVersionT *vs, int begin, int end,
|
||||
InternalVersionT readVersion);
|
||||
template __attribute__((target("avx512f"))) bool
|
||||
checkMaxBetweenExclusive<true>(Node *n, int begin, int end,
|
||||
InternalVersionT readVersion, ReadContext *);
|
||||
template __attribute__((target("avx512f"))) bool
|
||||
checkRangeStartsWith<true>(Node *n, std::span<const uint8_t> key, int begin,
|
||||
int end, InternalVersionT readVersion,
|
||||
ReadContext *);
|
||||
template __attribute__((target("avx512f"))) bool
|
||||
CheckRangeLeftSide<true>::step();
|
||||
template __attribute__((target("avx512f"))) bool
|
||||
CheckRangeRightSide<true>::step();
|
||||
template __attribute__((target("avx512f"))) bool
|
||||
checkRangeReadImpl<true>(Node *n, std::span<const uint8_t> begin,
|
||||
std::span<const uint8_t> end,
|
||||
checkMaxBetweenExclusiveImpl<true>(Node *n, int begin, int end,
|
||||
InternalVersionT readVersion, ReadContext *);
|
||||
#endif
|
||||
|
||||
#if defined(__SANITIZE_THREAD__) || !defined(__x86_64__)
|
||||
bool checkRangeRead(Node *n, std::span<const uint8_t> begin,
|
||||
std::span<const uint8_t> end, InternalVersionT readVersion,
|
||||
ReadContext *tls) {
|
||||
return checkRangeReadImpl<false>(n, begin, end, readVersion, tls);
|
||||
}
|
||||
#else
|
||||
// Only one of these is ever exercised. I'm not worried about somehow not
|
||||
// calling one of these though.
|
||||
// GCOVR_EXCL_START
|
||||
__attribute__((target("default"))) bool
|
||||
checkRangeRead(Node *n, std::span<const uint8_t> begin,
|
||||
std::span<const uint8_t> end, InternalVersionT readVersion,
|
||||
ReadContext *tls) {
|
||||
return checkRangeReadImpl<false>(n, begin, end, readVersion, tls);
|
||||
}
|
||||
__attribute__((target("avx512f"))) bool
|
||||
checkRangeRead(Node *n, std::span<const uint8_t> begin,
|
||||
std::span<const uint8_t> end, InternalVersionT readVersion,
|
||||
ReadContext *tls) {
|
||||
return checkRangeReadImpl<true>(n, begin, end, readVersion, tls);
|
||||
}
|
||||
// GCOVR_EXCL_STOP
|
||||
#endif
|
||||
|
||||
// Returns a pointer to the newly inserted node. Caller must set
|
||||
// `entryPresent`, `entry` fields and `maxVersion` on the result. The search
|
||||
// path of the result's parent will have `maxVersion` at least `writeVersion` as
|
||||
// a postcondition. Nodes along the search path to `key` may be invalidated.
|
||||
template <bool kBegin>
|
||||
[[nodiscard]] Node *insert(Node **self, std::span<const uint8_t> key,
|
||||
InternalVersionT writeVersion, WriteContext *tls,
|
||||
ConflictSet::Impl *impl) {
|
||||
|
||||
for (;; ++tls->accum.insert_iterations) {
|
||||
|
||||
if ((*self)->partialKeyLen > 0) {
|
||||
// Consume the partial key of `self` (which must exist), and update `self` and
|
||||
// `key` such that `self` is along the search path of `key`
|
||||
void consumePartialKey(Node *&self, std::span<const uint8_t> &key,
|
||||
InternalVersionT writeVersion, WriteContext *tls) {
|
||||
assert(self->partialKeyLen > 0);
|
||||
// Handle an existing partial key
|
||||
int commonLen = std::min<int>((*self)->partialKeyLen, key.size());
|
||||
int commonLen = std::min<int>(self->partialKeyLen, key.size());
|
||||
int partialKeyIndex =
|
||||
longestCommonPrefix((*self)->partialKey(), key.data(), commonLen);
|
||||
if (partialKeyIndex < (*self)->partialKeyLen) {
|
||||
auto *old = *self;
|
||||
InternalVersionT oldMaxVersion = maxVersion(old, impl);
|
||||
longestCommonPrefix(self->partialKey(), key.data(), commonLen);
|
||||
if (partialKeyIndex < self->partialKeyLen) {
|
||||
auto *old = self;
|
||||
// Since root cannot have a partial key
|
||||
assert(old->parent != nullptr);
|
||||
InternalVersionT oldMaxVersion = exchangeMaxVersion(old, writeVersion);
|
||||
|
||||
// *self will have one child
|
||||
*self = tls->allocate<Node3>(partialKeyIndex);
|
||||
// *self will have one child (old)
|
||||
auto *newSelf = tls->allocate<Node3>(partialKeyIndex);
|
||||
|
||||
memcpy((char *)*self + kNodeCopyBegin, (char *)old + kNodeCopyBegin,
|
||||
kNodeCopySize);
|
||||
(*self)->partialKeyLen = partialKeyIndex;
|
||||
newSelf->parent = old->parent;
|
||||
newSelf->parentsIndex = old->parentsIndex;
|
||||
newSelf->partialKeyLen = partialKeyIndex;
|
||||
newSelf->entryPresent = false;
|
||||
newSelf->numChildren = 1;
|
||||
|
||||
// Not necessary to call removeKey here, since this node is "synthetic"
|
||||
(*self)->entryPresent = false;
|
||||
memcpy(newSelf->partialKey(), old->partialKey(), newSelf->partialKeyLen);
|
||||
|
||||
(*self)->numChildren = 0;
|
||||
memcpy((*self)->partialKey(), old->partialKey(),
|
||||
(*self)->partialKeyLen);
|
||||
|
||||
getOrCreateChild(*self, old->partialKey()[partialKeyIndex], tls) = old;
|
||||
old->parent = *self;
|
||||
old->parentsIndex = old->partialKey()[partialKeyIndex];
|
||||
setMaxVersion(old, impl, oldMaxVersion);
|
||||
uint8_t oldDistinguishingByte = old->partialKey()[partialKeyIndex];
|
||||
old->parent = newSelf;
|
||||
old->parentsIndex = oldDistinguishingByte;
|
||||
newSelf->index[0] = oldDistinguishingByte;
|
||||
newSelf->children[0] = old;
|
||||
newSelf->childMaxVersion[0] = oldMaxVersion;
|
||||
self = newSelf;
|
||||
|
||||
memmove(old->partialKey(), old->partialKey() + partialKeyIndex + 1,
|
||||
old->partialKeyLen - (partialKeyIndex + 1));
|
||||
@@ -2889,45 +2920,50 @@ template <bool kBegin>
|
||||
// up.
|
||||
}
|
||||
key = key.subspan(partialKeyIndex, key.size() - partialKeyIndex);
|
||||
|
||||
} else {
|
||||
// Consider adding a partial key
|
||||
if ((*self)->numChildren == 0 && !(*self)->entryPresent) {
|
||||
assert((*self)->getCapacity() >= int(key.size()));
|
||||
(*self)->partialKeyLen = key.size();
|
||||
memcpy((*self)->partialKey(), key.data(), (*self)->partialKeyLen);
|
||||
key = key.subspan((*self)->partialKeyLen,
|
||||
key.size() - (*self)->partialKeyLen);
|
||||
}
|
||||
}
|
||||
|
||||
if constexpr (kBegin) {
|
||||
// Returns a pointer to the newly inserted node. Caller must set
|
||||
// `entryPresent`, and `entry` fields. All nodes along the search path of the
|
||||
// result will have `maxVersion` set to `writeVersion` as a postcondition. Nodes
|
||||
// along the search path may be invalidated.
|
||||
[[nodiscard]]
|
||||
Node *insert(Node **self, std::span<const uint8_t> key,
|
||||
InternalVersionT writeVersion, WriteContext *tls,
|
||||
ConflictSet::Impl *impl) {
|
||||
|
||||
if ((*self)->partialKeyLen > 0) {
|
||||
consumePartialKey(*self, key, writeVersion, tls);
|
||||
}
|
||||
assert(maxVersion(*self, impl) <= writeVersion);
|
||||
setMaxVersion(*self, impl, writeVersion);
|
||||
}
|
||||
|
||||
for (;; ++tls->accum.insert_iterations) {
|
||||
|
||||
if (key.size() == 0) {
|
||||
return *self;
|
||||
}
|
||||
|
||||
if constexpr (!kBegin) {
|
||||
assert(maxVersion(*self, impl) <= writeVersion);
|
||||
setMaxVersion(*self, impl, writeVersion);
|
||||
}
|
||||
|
||||
auto &child = getOrCreateChild(*self, key.front(), tls);
|
||||
auto &child = getOrCreateChild(*self, key.front(), writeVersion, tls);
|
||||
if (!child) {
|
||||
child = tls->allocate<Node0>(key.size() - 1);
|
||||
child->numChildren = 0;
|
||||
child->entryPresent = false;
|
||||
child->partialKeyLen = 0;
|
||||
child->partialKeyLen = key.size() - 1;
|
||||
child->parent = *self;
|
||||
child->parentsIndex = key.front();
|
||||
setMaxVersion(child, impl, kBegin ? writeVersion : tls->zero);
|
||||
setMaxVersion(child, impl, writeVersion);
|
||||
memcpy(child->partialKey(), key.data() + 1, child->partialKeyLen);
|
||||
return child;
|
||||
}
|
||||
|
||||
self = &child;
|
||||
key = key.subspan(1, key.size() - 1);
|
||||
|
||||
if ((*self)->partialKeyLen > 0) {
|
||||
consumePartialKey(*self, key, writeVersion, tls);
|
||||
assert(maxVersion(*self, impl) <= writeVersion);
|
||||
setMaxVersion(*self, impl, writeVersion);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2960,7 +2996,7 @@ void addPointWrite(Node *&root, std::span<const uint8_t> key,
|
||||
InternalVersionT writeVersion, WriteContext *tls,
|
||||
ConflictSet::Impl *impl) {
|
||||
++tls->accum.point_writes;
|
||||
auto *n = insert<true>(&root, key, writeVersion, tls, impl);
|
||||
auto *n = insert(&root, key, writeVersion, tls, impl);
|
||||
if (!n->entryPresent) {
|
||||
++tls->accum.entries_inserted;
|
||||
auto *p = nextLogical(n);
|
||||
@@ -2969,7 +3005,6 @@ void addPointWrite(Node *&root, std::span<const uint8_t> key,
|
||||
n->entryPresent = true;
|
||||
|
||||
n->entry.pointVersion = writeVersion;
|
||||
setMaxVersion(n, impl, writeVersion);
|
||||
n->entry.rangeVersion =
|
||||
p == nullptr ? tls->zero : std::max(p->entry.rangeVersion, tls->zero);
|
||||
} else {
|
||||
@@ -2978,6 +3013,44 @@ void addPointWrite(Node *&root, std::span<const uint8_t> key,
|
||||
}
|
||||
}
|
||||
|
||||
// Precondition: `node->entryPresent`
|
||||
void fixupMaxVersion(Node *node, ConflictSet::Impl *impl, WriteContext *tls) {
|
||||
InternalVersionT max;
|
||||
assert(node->entryPresent);
|
||||
max = std::max(node->entry.pointVersion, tls->zero);
|
||||
switch (node->getType()) {
|
||||
case Type_Node0:
|
||||
break;
|
||||
case Type_Node3: {
|
||||
auto *self3 = static_cast<Node3 *>(node);
|
||||
for (int i = 0; i < self3->numChildren; ++i) {
|
||||
max = std::max(self3->childMaxVersion[i], max);
|
||||
}
|
||||
} break;
|
||||
case Type_Node16: {
|
||||
auto *self16 = static_cast<Node16 *>(node);
|
||||
for (int i = 0; i < self16->numChildren; ++i) {
|
||||
max = std::max(self16->childMaxVersion[i], max);
|
||||
}
|
||||
} break;
|
||||
case Type_Node48: {
|
||||
auto *self48 = static_cast<Node48 *>(node);
|
||||
for (auto v : self48->maxOfMax) {
|
||||
max = std::max(v, max);
|
||||
}
|
||||
} break;
|
||||
case Type_Node256: {
|
||||
auto *self256 = static_cast<Node256 *>(node);
|
||||
for (auto v : self256->maxOfMax) {
|
||||
max = std::max(v, max);
|
||||
}
|
||||
} break;
|
||||
default: // GCOVR_EXCL_LINE
|
||||
__builtin_unreachable(); // GCOVR_EXCL_LINE
|
||||
}
|
||||
setMaxVersion(node, impl, max);
|
||||
}
|
||||
|
||||
void addWriteRange(Node *&root, std::span<const uint8_t> begin,
|
||||
std::span<const uint8_t> end, InternalVersionT writeVersion,
|
||||
WriteContext *tls, ConflictSet::Impl *impl) {
|
||||
@@ -3024,7 +3097,7 @@ void addWriteRange(Node *&root, std::span<const uint8_t> begin,
|
||||
begin = begin.subspan(consumed, begin.size() - consumed);
|
||||
end = end.subspan(consumed, end.size() - consumed);
|
||||
|
||||
auto *beginNode = insert<true>(useAsRoot, begin, writeVersion, tls, impl);
|
||||
auto *beginNode = insert(useAsRoot, begin, writeVersion, tls, impl);
|
||||
|
||||
const bool insertedBegin = !beginNode->entryPresent;
|
||||
|
||||
@@ -3037,14 +3110,11 @@ void addWriteRange(Node *&root, std::span<const uint8_t> begin,
|
||||
beginNode->entry.rangeVersion =
|
||||
p == nullptr ? tls->zero : std::max(p->entry.rangeVersion, tls->zero);
|
||||
beginNode->entry.pointVersion = writeVersion;
|
||||
assert(maxVersion(beginNode, impl) <= writeVersion);
|
||||
setMaxVersion(beginNode, impl, writeVersion);
|
||||
}
|
||||
setMaxVersion(beginNode, impl, writeVersion);
|
||||
assert(writeVersion >= beginNode->entry.pointVersion);
|
||||
beginNode->entry.pointVersion = writeVersion;
|
||||
|
||||
auto *endNode = insert<false>(useAsRoot, end, writeVersion, tls, impl);
|
||||
auto *endNode = insert(useAsRoot, end, writeVersion, tls, impl);
|
||||
|
||||
const bool insertedEnd = !endNode->entryPresent;
|
||||
|
||||
@@ -3056,22 +3126,22 @@ void addWriteRange(Node *&root, std::span<const uint8_t> begin,
|
||||
auto *p = nextLogical(endNode);
|
||||
endNode->entry.pointVersion =
|
||||
p == nullptr ? tls->zero : std::max(p->entry.rangeVersion, tls->zero);
|
||||
auto m = maxVersion(endNode, impl);
|
||||
setMaxVersion(endNode, impl,
|
||||
std::max<InternalVersionT>(m, endNode->entry.pointVersion));
|
||||
}
|
||||
endNode->entry.rangeVersion = writeVersion;
|
||||
|
||||
if (beginIsPrefix && insertedEnd) {
|
||||
// beginNode may have been invalidated when inserting end. TODO can we do
|
||||
// better?
|
||||
beginNode = insert<true>(useAsRoot, begin, writeVersion, tls, impl);
|
||||
beginNode = insert(useAsRoot, begin, writeVersion, tls, impl);
|
||||
assert(beginNode->entryPresent);
|
||||
}
|
||||
|
||||
for (beginNode = nextLogical(beginNode); beginNode != endNode;
|
||||
beginNode = erase(beginNode, tls, impl, /*logical*/ true, endNode)) {
|
||||
}
|
||||
|
||||
// Inserting end trashed endNode's maxVersion. Fix that
|
||||
fixupMaxVersion(endNode, impl, tls);
|
||||
}
|
||||
|
||||
Node *firstGeqPhysical(Node *n, const std::span<const uint8_t> key) {
|
||||
@@ -3488,6 +3558,39 @@ InternalVersionT maxVersion(Node *n, ConflictSet::Impl *impl) {
|
||||
}
|
||||
}
|
||||
|
||||
// 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;
|
||||
@@ -3998,6 +4101,10 @@ checkMaxVersion(Node *root, Node *node, InternalVersionT oldestVersion,
|
||||
ConflictSet::Impl *impl) {
|
||||
bool success = true;
|
||||
|
||||
if (node->partialKeyLen > 0) {
|
||||
fprintf(stderr, "Root cannot have a partial key");
|
||||
success = false;
|
||||
}
|
||||
checkParentPointers(node, success);
|
||||
checkMaxVersion(node, node, oldestVersion, success, impl);
|
||||
checkEntriesExist(node, success);
|
||||
|
20
README.md
20
README.md
@@ -24,15 +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
|
||||
|--------------------:|--------------------:|--------:|----------------:|----------------:|-------:|---------------:|--------:|----------:|:----------
|
||||
| 17.03 | 58,732,967.93 | 0.6% | 276.28 | 87.96 | 3.141 | 52.15 | 0.4% | 0.01 | `point reads`
|
||||
| 19.52 | 51,239,158.04 | 0.3% | 367.16 | 101.50 | 3.617 | 61.92 | 0.3% | 0.01 | `prefix reads`
|
||||
| 47.74 | 20,947,676.63 | 0.5% | 998.16 | 247.43 | 4.034 | 161.64 | 0.2% | 0.01 | `range reads`
|
||||
| 23.14 | 43,207,824.89 | 0.4% | 408.18 | 121.64 | 3.356 | 70.20 | 0.3% | 0.01 | `point writes`
|
||||
| 38.02 | 26,302,115.66 | 0.1% | 709.72 | 199.70 | 3.554 | 134.26 | 0.3% | 0.01 | `prefix writes`
|
||||
| 44.28 | 22,583,559.17 | 0.9% | 825.19 | 233.10 | 3.540 | 141.48 | 0.2% | 0.01 | `range writes`
|
||||
| 85.50 | 11,695,990.63 | 0.5% | 1,488.16 | 455.68 | 3.266 | 289.22 | 0.1% | 0.01 | `monotonic increasing point writes`
|
||||
| 338,388.50 | 2,955.18 | 3.3% | 4,097,087.00 | 1,809,996.00 | 2.264 | 759,645.00 | 0.0% | 0.01 | `worst case for radix tree`
|
||||
| 84.84 | 11,787,313.59 | 1.4% | 1,716.02 | 440.50 | 3.896 | 271.00 | 0.0% | 0.01 | `create and destroy`
|
||||
| 12.42 | 80,500,398.66 | 0.8% | 180.38 | 61.57 | 2.930 | 41.51 | 0.4% | 0.01 | `point reads`
|
||||
| 15.17 | 65,917,580.99 | 0.2% | 279.47 | 74.95 | 3.729 | 55.54 | 0.3% | 0.01 | `prefix reads`
|
||||
| 38.16 | 26,202,393.91 | 0.1% | 803.07 | 189.13 | 4.246 | 141.68 | 0.2% | 0.01 | `range reads`
|
||||
| 20.20 | 49,504,615.44 | 0.4% | 363.00 | 100.35 | 3.617 | 49.81 | 0.3% | 0.01 | `point writes`
|
||||
| 41.99 | 23,816,559.99 | 0.3% | 799.27 | 209.63 | 3.813 | 154.32 | 0.1% | 0.01 | `prefix writes`
|
||||
| 46.28 | 21,607,605.88 | 1.5% | 953.79 | 231.47 | 4.121 | 168.34 | 0.0% | 0.01 | `range writes`
|
||||
| 80.99 | 12,347,449.98 | 0.9% | 1,501.97 | 406.50 | 3.695 | 281.89 | 0.1% | 0.01 | `monotonic increasing point writes`
|
||||
| 318,010.00 | 3,144.56 | 1.0% | 3,994,511.50 | 1,657,831.50 | 2.409 | 805,969.50 | 0.0% | 0.01 | `worst case for radix tree`
|
||||
| 75.85 | 13,183,612.56 | 0.5% | 1,590.01 | 385.64 | 4.123 | 258.00 | 0.0% | 0.01 | `create and destroy`
|
||||
|
||||
# "Real data" test
|
||||
|
||||
@@ -47,7 +47,7 @@ Check: 4.47891 seconds, 364.05 MB/s, Add: 4.55599 seconds, 123.058 MB/s, Gc rati
|
||||
## radix tree
|
||||
|
||||
```
|
||||
Check: 1.05813 seconds, 1540.97 MB/s, Add: 1.32071 seconds, 424.508 MB/s, Gc ratio: 42.2067%
|
||||
Check: 0.963721 seconds, 1691.93 MB/s, Add: 1.3288 seconds, 421.924 MB/s, Gc ratio: 42.8819%
|
||||
```
|
||||
|
||||
## hash table
|
||||
|
233
ServerBench.cpp
Normal file
233
ServerBench.cpp
Normal file
@@ -0,0 +1,233 @@
|
||||
#include <atomic>
|
||||
#include <errno.h>
|
||||
#include <netdb.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <sys/resource.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/uio.h>
|
||||
#include <thread>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "ConflictSet.h"
|
||||
#include "third_party/nadeau.h"
|
||||
|
||||
std::atomic<int64_t> transactions;
|
||||
|
||||
constexpr int kBaseSearchDepth = 32;
|
||||
constexpr int kWindowSize = 10000000;
|
||||
|
||||
std::basic_string<uint8_t> numToKey(int64_t num) {
|
||||
std::basic_string<uint8_t> result;
|
||||
result.resize(kBaseSearchDepth + sizeof(int64_t));
|
||||
memset(result.data(), 0, kBaseSearchDepth);
|
||||
int64_t be = __builtin_bswap64(num);
|
||||
memcpy(result.data() + kBaseSearchDepth, &be, sizeof(int64_t));
|
||||
return result;
|
||||
}
|
||||
|
||||
void workload(weaselab::ConflictSet *cs) {
|
||||
int64_t version = kWindowSize;
|
||||
cs->addWrites(nullptr, 0, version);
|
||||
for (;; transactions.fetch_add(1, std::memory_order_relaxed)) {
|
||||
// Reads
|
||||
{
|
||||
auto beginK = numToKey(version - kWindowSize);
|
||||
auto endK = numToKey(version - 1);
|
||||
auto pointRv = version - kWindowSize + rand() % kWindowSize + 1;
|
||||
auto pointK = numToKey(pointRv);
|
||||
weaselab::ConflictSet::ReadRange reads[] = {
|
||||
{
|
||||
{pointK.data(), int(pointK.size())},
|
||||
{nullptr, 0},
|
||||
pointRv,
|
||||
},
|
||||
{
|
||||
{beginK.data(), int(beginK.size())},
|
||||
{endK.data(), int(endK.size())},
|
||||
version - 2,
|
||||
},
|
||||
};
|
||||
weaselab::ConflictSet::Result result[sizeof(reads) / sizeof(reads[0])];
|
||||
cs->check(reads, result, sizeof(reads) / sizeof(reads[0]));
|
||||
// for (int i = 0; i < sizeof(reads) / sizeof(reads[0]); ++i) {
|
||||
// if (result[i] != weaselab::ConflictSet::Commit) {
|
||||
// fprintf(stderr, "Unexpected conflict: [%s, %s) @ %" PRId64 "\n",
|
||||
// printable(reads[i].begin).c_str(),
|
||||
// printable(reads[i].end).c_str(), reads[i].readVersion);
|
||||
// abort();
|
||||
// }
|
||||
// }
|
||||
}
|
||||
// Writes
|
||||
{
|
||||
weaselab::ConflictSet::WriteRange w;
|
||||
auto k = numToKey(version);
|
||||
w.begin.p = k.data();
|
||||
w.end.len = 0;
|
||||
if (version % (kWindowSize / 2) == 0) {
|
||||
for (int l = 0; l <= k.size(); ++l) {
|
||||
w.begin.len = l;
|
||||
cs->addWrites(&w, 1, version);
|
||||
}
|
||||
} else {
|
||||
w.begin.len = k.size();
|
||||
cs->addWrites(&w, 1, version);
|
||||
}
|
||||
}
|
||||
// GC
|
||||
cs->setOldestVersion(version - kWindowSize);
|
||||
++version;
|
||||
}
|
||||
}
|
||||
|
||||
// Adapted from getaddrinfo man page
|
||||
int getListenFd(const char *node, const char *service) {
|
||||
|
||||
struct addrinfo hints;
|
||||
struct addrinfo *result, *rp;
|
||||
int sfd, s;
|
||||
|
||||
memset(&hints, 0, sizeof(hints));
|
||||
hints.ai_family = AF_UNSPEC; /* Allow IPv4 or IPv6 */
|
||||
hints.ai_socktype = SOCK_STREAM; /* stream socket */
|
||||
hints.ai_flags = AI_PASSIVE; /* For wildcard IP address */
|
||||
hints.ai_protocol = 0; /* Any protocol */
|
||||
hints.ai_canonname = nullptr;
|
||||
hints.ai_addr = nullptr;
|
||||
hints.ai_next = nullptr;
|
||||
|
||||
s = getaddrinfo(node, service, &hints, &result);
|
||||
if (s != 0) {
|
||||
fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(s));
|
||||
abort();
|
||||
}
|
||||
|
||||
/* getaddrinfo() returns a list of address structures.
|
||||
Try each address until we successfully bind(2).
|
||||
If socket(2) (or bind(2)) fails, we (close the socket
|
||||
and) try the next address. */
|
||||
|
||||
for (rp = result; rp != nullptr; rp = rp->ai_next) {
|
||||
sfd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
|
||||
if (sfd == -1) {
|
||||
continue;
|
||||
}
|
||||
|
||||
int val = 1;
|
||||
setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, &val, sizeof(val));
|
||||
|
||||
if (bind(sfd, rp->ai_addr, rp->ai_addrlen) == 0) {
|
||||
break; /* Success */
|
||||
}
|
||||
|
||||
close(sfd);
|
||||
}
|
||||
|
||||
freeaddrinfo(result); /* No longer needed */
|
||||
|
||||
if (rp == nullptr) { /* No address succeeded */
|
||||
fprintf(stderr, "Could not bind\n");
|
||||
abort();
|
||||
}
|
||||
|
||||
int rv = listen(sfd, SOMAXCONN);
|
||||
if (rv) {
|
||||
perror("listen()");
|
||||
abort();
|
||||
}
|
||||
|
||||
return sfd;
|
||||
}
|
||||
|
||||
// HTTP response
|
||||
//
|
||||
std::string_view part1 =
|
||||
"HTTP/1.1 200 OK \r\nContent-type: text/plain; version=0.0.4; "
|
||||
"charset=utf-8; escaping=values\r\nContent-Length: ";
|
||||
// Decimal content length
|
||||
std::string_view part2 = "\r\n\r\n";
|
||||
// Body
|
||||
|
||||
double toSeconds(timeval t) {
|
||||
return double(t.tv_sec) + double(t.tv_usec) * 1e-6;
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
if (argc != 3) {
|
||||
goto fail;
|
||||
}
|
||||
{
|
||||
int listenFd = getListenFd(argv[1], argv[2]);
|
||||
|
||||
weaselab::ConflictSet cs{0};
|
||||
weaselab::ConflictSet::MetricsV1 *metrics;
|
||||
int metricsCount;
|
||||
cs.getMetricsV1(&metrics, &metricsCount);
|
||||
|
||||
auto w = std::thread{workload, &cs};
|
||||
|
||||
for (;;) {
|
||||
struct sockaddr_storage peer_addr = {};
|
||||
socklen_t peer_addr_len = sizeof(peer_addr);
|
||||
const int connfd =
|
||||
accept(listenFd, (struct sockaddr *)&peer_addr, &peer_addr_len);
|
||||
|
||||
std::string body;
|
||||
|
||||
rusage r;
|
||||
getrusage(RUSAGE_SELF, &r);
|
||||
body += "# HELP process_cpu_seconds_total Total user and system CPU time "
|
||||
"spent in seconds.\n# TYPE process_cpu_seconds_total counter\n"
|
||||
"process_cpu_seconds_total ";
|
||||
body += std::to_string(toSeconds(r.ru_utime) + toSeconds(r.ru_stime));
|
||||
body += "\n";
|
||||
body += "# HELP process_resident_memory_bytes Resident memory size in "
|
||||
"bytes.\n# TYPE process_resident_memory_bytes gauge\n"
|
||||
"process_resident_memory_bytes ";
|
||||
body += std::to_string(getCurrentRSS());
|
||||
body += "\n";
|
||||
body += "# HELP transactions_total Total number of transactions\n"
|
||||
"# TYPE transactions_total counter\n"
|
||||
"transactions_total ";
|
||||
body += std::to_string(transactions.load(std::memory_order_relaxed));
|
||||
body += "\n";
|
||||
|
||||
for (int i = 0; i < metricsCount; ++i) {
|
||||
body += "# HELP ";
|
||||
body += metrics[i].name;
|
||||
body += " ";
|
||||
body += metrics[i].help;
|
||||
body += "\n";
|
||||
body += "# TYPE ";
|
||||
body += metrics[i].name;
|
||||
body += " ";
|
||||
body += metrics[i].type == metrics[i].Counter ? "counter" : "gauge";
|
||||
body += "\n";
|
||||
body += metrics[i].name;
|
||||
body += " ";
|
||||
body += std::to_string(metrics[i].getValue());
|
||||
body += "\n";
|
||||
}
|
||||
|
||||
auto len = std::to_string(body.size());
|
||||
iovec iov[] = {
|
||||
{(void *)part1.data(), part1.size()},
|
||||
{(void *)len.data(), len.size()},
|
||||
{(void *)part2.data(), part2.size()},
|
||||
{(void *)body.data(), body.size()},
|
||||
};
|
||||
int written;
|
||||
do {
|
||||
written = writev(connfd, iov, sizeof(iov) / sizeof(iov[0]));
|
||||
} while (written < 0 && errno == EINTR);
|
||||
close(connfd);
|
||||
}
|
||||
}
|
||||
fail:
|
||||
fprintf(stderr, "Expected ./%s <host> <port>\n", argv[0]);
|
||||
return 1;
|
||||
}
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
corpus/01c2a61d2b6e7fe40b38e46287afd69e01e52408
Normal file
BIN
corpus/01c2a61d2b6e7fe40b38e46287afd69e01e52408
Normal file
Binary file not shown.
BIN
corpus/027eeb2ee154aca4151298a6c31f3b80f58d3fae
Normal file
BIN
corpus/027eeb2ee154aca4151298a6c31f3b80f58d3fae
Normal file
Binary file not shown.
Binary file not shown.
BIN
corpus/02ce0b03d4e732ab6e7b264ccce3ea77d3787e5f
Normal file
BIN
corpus/02ce0b03d4e732ab6e7b264ccce3ea77d3787e5f
Normal file
Binary file not shown.
BIN
corpus/03feca4da73a0c72f67bbaee135d10aec44b68ed
Normal file
BIN
corpus/03feca4da73a0c72f67bbaee135d10aec44b68ed
Normal file
Binary file not shown.
Binary file not shown.
BIN
corpus/04b685b62d0428575c4b780e5aa5746a7b3c03c0
Normal file
BIN
corpus/04b685b62d0428575c4b780e5aa5746a7b3c03c0
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
corpus/0752e603296a3adaec12746ba9d09a39796be8a1
Normal file
BIN
corpus/0752e603296a3adaec12746ba9d09a39796be8a1
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
corpus/0838105dab8b8b9912d520ecd3910d104a3cba4e
Normal file
BIN
corpus/0838105dab8b8b9912d520ecd3910d104a3cba4e
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
corpus/0879e0de6c13b47a0af9883ae76f839cd1527336
Normal file
BIN
corpus/0879e0de6c13b47a0af9883ae76f839cd1527336
Normal file
Binary file not shown.
BIN
corpus/0959377018632d4e8f772d3da38229e91918f1e0
Normal file
BIN
corpus/0959377018632d4e8f772d3da38229e91918f1e0
Normal file
Binary file not shown.
BIN
corpus/0964feb74dbe0ec93d02d17bce02f550b3dd72b1
Normal file
BIN
corpus/0964feb74dbe0ec93d02d17bce02f550b3dd72b1
Normal file
Binary file not shown.
BIN
corpus/09e805380a8b9e956017d65d48add2e0264c3613
Normal file
BIN
corpus/09e805380a8b9e956017d65d48add2e0264c3613
Normal file
Binary file not shown.
BIN
corpus/09fb712b33e82457001200373bfaba282c64647e
Normal file
BIN
corpus/09fb712b33e82457001200373bfaba282c64647e
Normal file
Binary file not shown.
BIN
corpus/0a04d2659955ca0470906f41a62f87f1c32fc3f1
Normal file
BIN
corpus/0a04d2659955ca0470906f41a62f87f1c32fc3f1
Normal file
Binary file not shown.
BIN
corpus/0a08831927d30683bb7c636b98d4f11178133550
Normal file
BIN
corpus/0a08831927d30683bb7c636b98d4f11178133550
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
corpus/0c136f7f6d3f5f13c3279e9d4f1fbe96df6107fc
Normal file
BIN
corpus/0c136f7f6d3f5f13c3279e9d4f1fbe96df6107fc
Normal file
Binary file not shown.
BIN
corpus/0d65e979b921d29db29996214ba9ada06760214d
Normal file
BIN
corpus/0d65e979b921d29db29996214ba9ada06760214d
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
corpus/0efd7398293b9926eab2486d2e3bdb4fa4741bc3
Normal file
BIN
corpus/0efd7398293b9926eab2486d2e3bdb4fa4741bc3
Normal file
Binary file not shown.
BIN
corpus/0f4cc5b5da82090c3c93ace9dd99043d309969cf
Normal file
BIN
corpus/0f4cc5b5da82090c3c93ace9dd99043d309969cf
Normal file
Binary file not shown.
@@ -1,5 +0,0 @@
|
||||
|
||||
<EFBFBD><EFBFBD>
|
||||
|
||||
|
||||
2
|
Binary file not shown.
Binary file not shown.
BIN
corpus/0f8e595cfa8b91f008d5bafa0d13187bc4692aa5
Normal file
BIN
corpus/0f8e595cfa8b91f008d5bafa0d13187bc4692aa5
Normal file
Binary file not shown.
Binary file not shown.
BIN
corpus/0fa0e8462497fe8f56774c6f9569131e0d78dea1
Normal file
BIN
corpus/0fa0e8462497fe8f56774c6f9569131e0d78dea1
Normal file
Binary file not shown.
Binary file not shown.
BIN
corpus/10817b7422a5e55a5507ae2cc6c79924c4e91e86
Normal file
BIN
corpus/10817b7422a5e55a5507ae2cc6c79924c4e91e86
Normal file
Binary file not shown.
BIN
corpus/10a19953b8e4e320ccac6f71ab44442ee75b1d3a
Normal file
BIN
corpus/10a19953b8e4e320ccac6f71ab44442ee75b1d3a
Normal file
Binary file not shown.
Binary file not shown.
BIN
corpus/11503ceb0f64208cd4c98ed7b6bf70a4f0a604de
Normal file
BIN
corpus/11503ceb0f64208cd4c98ed7b6bf70a4f0a604de
Normal file
Binary file not shown.
BIN
corpus/1172a9bde2deff2cbe3fcd4628d925d962991359
Normal file
BIN
corpus/1172a9bde2deff2cbe3fcd4628d925d962991359
Normal file
Binary file not shown.
BIN
corpus/11c36378ed15fb4fc147f503b5c6b6284bffbbd6
Normal file
BIN
corpus/11c36378ed15fb4fc147f503b5c6b6284bffbbd6
Normal file
Binary file not shown.
BIN
corpus/120347a5591760d3208403325dfbaf9680c8c811
Normal file
BIN
corpus/120347a5591760d3208403325dfbaf9680c8c811
Normal file
Binary file not shown.
BIN
corpus/1258c23ab656c67943a1a29fa53326f1265e3161
Normal file
BIN
corpus/1258c23ab656c67943a1a29fa53326f1265e3161
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1 +0,0 @@
|
||||
<EFBFBD>
|
BIN
corpus/130005d6d89fab7ba74ec6dcf63fc2743e90e99c
Normal file
BIN
corpus/130005d6d89fab7ba74ec6dcf63fc2743e90e99c
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
corpus/13758922ea9cacbc9e60102e6031b6bf642a9555
Normal file
BIN
corpus/13758922ea9cacbc9e60102e6031b6bf642a9555
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
corpus/14bddb239b44cc23bb5a83123f3f695f99bddee2
Normal file
BIN
corpus/14bddb239b44cc23bb5a83123f3f695f99bddee2
Normal file
Binary file not shown.
BIN
corpus/15af00b4a5e1a8994cebe6d73e81061fc0a1c83f
Normal file
BIN
corpus/15af00b4a5e1a8994cebe6d73e81061fc0a1c83f
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
corpus/183fc0b217e29efc19cc7f5502de35f318210a6d
Normal file
BIN
corpus/183fc0b217e29efc19cc7f5502de35f318210a6d
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
corpus/18d578b77571b863bbea95a471524d44918aa9be
Normal file
BIN
corpus/18d578b77571b863bbea95a471524d44918aa9be
Normal file
Binary file not shown.
BIN
corpus/18d7130f6c6ce63b91b26659568b171ab4c144e1
Normal file
BIN
corpus/18d7130f6c6ce63b91b26659568b171ab4c144e1
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
corpus/1d7c8f7811dbf57cceed7e487ad641f2f081d4d5
Normal file
BIN
corpus/1d7c8f7811dbf57cceed7e487ad641f2f081d4d5
Normal file
Binary file not shown.
BIN
corpus/1dcb5faae22e59490de6347d65470be6e0721d04
Normal file
BIN
corpus/1dcb5faae22e59490de6347d65470be6e0721d04
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
corpus/1ff6ebb7ed73f269f8698ade75bfd0050969b4fe
Normal file
BIN
corpus/1ff6ebb7ed73f269f8698ade75bfd0050969b4fe
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user