7 Commits

Author SHA1 Message Date
af1e2299de Include childType in ChildAndMaxVersion
Some checks failed
Tests / Clang total: 3339, passed: 3339
Clang |Total|New|Outstanding|Fixed|Trend |:-:|:-:|:-:|:-:|:-: |0|0|0|0|:clap:
Tests / 64 bit versions total: 3339, passed: 3339
Tests / Debug total: 3337, passed: 3337
weaselab/conflict-set/pipeline/head There was a failure building this commit
2024-09-23 18:38:06 -07:00
230e96063d Make children tagged pointers 2024-09-23 17:58:09 -07:00
f41a62471b Add tail-call based interleaving approach
All checks were successful
Tests / Clang total: 3339, passed: 3339
Clang |Total|New|Outstanding|Fixed|Trend |:-:|:-:|:-:|:-:|:-: |0|0|0|0|:clap:
Tests / 64 bit versions total: 3339, passed: 3339
Tests / Debug total: 3337, passed: 3337
Tests / SIMD fallback total: 3339, passed: 3339
Tests / Release [gcc] total: 3339, passed: 3339
GNU C Compiler (gcc) |Total|New|Outstanding|Fixed|Trend |:-:|:-:|:-:|:-:|:-: |0|0|0|0|:clap:
Tests / Release [gcc,aarch64] total: 2482, passed: 2482
Tests / Coverage total: 2511, passed: 2511
Code Coverage #### Project Overview No changes detected, that affect the code coverage. * Line Coverage: 99.09% (1846/1863) * Branch Coverage: 67.65% (1447/2139) * Complexity Density: 0.00 * Lines of Code: 1863 #### Quality Gates Summary Output truncated.
weaselab/conflict-set/pipeline/head This commit looks good
2024-09-23 12:52:15 -07:00
d8f85dedc4 Add to corpus
All checks were successful
Tests / Clang total: 3339, passed: 3339
Clang |Total|New|Outstanding|Fixed|Trend |:-:|:-:|:-:|:-:|:-: |0|0|0|0|:clap:
Tests / 64 bit versions total: 3339, passed: 3339
Tests / Debug total: 3337, passed: 3337
Tests / SIMD fallback total: 3339, passed: 3339
Tests / Release [gcc] total: 3339, passed: 3339
GNU C Compiler (gcc) |Total|New|Outstanding|Fixed|Trend |:-:|:-:|:-:|:-:|:-: |0|0|0|0|:clap:
Tests / Release [gcc,aarch64] total: 2482, passed: 2482
Tests / Coverage total: 2511, passed: 2511
Code Coverage #### Project Overview No changes detected, that affect the code coverage. * Line Coverage: 99.09% (1846/1863) * Branch Coverage: 67.65% (1447/2139) * Complexity Density: 0.00 * Lines of Code: 1863 #### Quality Gates Summary Output truncated.
weaselab/conflict-set/pipeline/head This commit looks good
2024-09-14 15:02:47 -07:00
656939560b Add GCOVR_EXCL_LINE on covered line 2024-09-14 14:35:51 -07:00
5580f9b71d Run script tests under valgrind too
Some checks failed
Tests / Clang total: 3307, passed: 3307
Clang |Total|New|Outstanding|Fixed|Trend |:-:|:-:|:-:|:-:|:-: |0|0|0|0|:clap:
Tests / 64 bit versions total: 3307, passed: 3307
Tests / Debug total: 3305, passed: 3305
Tests / SIMD fallback total: 3307, passed: 3307
Tests / Release [gcc] total: 3307, passed: 3307
GNU C Compiler (gcc) |Total|New|Outstanding|Fixed|Trend |:-:|:-:|:-:|:-:|:-: |0|0|0|0|:clap:
Tests / Release [gcc,aarch64] total: 2458, passed: 2458
Tests / Coverage total: 2487, passed: 2487
Code Coverage #### Project Overview No changes detected, that affect the code coverage. * Line Coverage: 99.03% (1846/1864) * Branch Coverage: 67.65% (1447/2139) * Complexity Density: 0.00 * Lines of Code: 1864 #### Quality Gates Summary Output truncated.
weaselab/conflict-set/pipeline/head There was a failure building this commit
2024-09-14 13:51:45 -07:00
628d16b7e6 Annotate zero argument as maybe_unused 2024-09-14 13:47:08 -07:00
11 changed files with 231 additions and 66 deletions

View File

@@ -248,6 +248,19 @@ if(CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR AND BUILD_TESTING)
add_test(NAME conflict_set_blackbox_${hash} COMMAND driver ${TEST})
endforeach()
find_program(VALGRIND_EXE valgrind)
if(VALGRIND_EXE AND NOT CMAKE_CROSSCOMPILING)
list(LENGTH CORPUS_TESTS len)
math(EXPR last "${len} - 1")
set(partition_size 100)
foreach(i RANGE 0 ${last} ${partition_size})
list(SUBLIST CORPUS_TESTS ${i} ${partition_size} partition)
add_test(NAME conflict_set_blackbox_valgrind_${i}
COMMAND ${VALGRIND_EXE} --error-exitcode=99 --
$<TARGET_FILE:driver> ${partition})
endforeach()
endif()
# scripted tests. Written manually to fill in anything libfuzzer couldn't
# find.
if(NOT CMAKE_CROSSCOMPILING)
@@ -268,19 +281,14 @@ if(CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR AND BUILD_TESTING)
${Python3_EXECUTABLE}
${CMAKE_CURRENT_SOURCE_DIR}/test_conflict_set.py test ${TEST}
--build-dir ${CMAKE_CURRENT_BINARY_DIR})
endforeach()
endif()
find_program(VALGRIND_EXE valgrind)
if(VALGRIND_EXE AND NOT CMAKE_CROSSCOMPILING)
list(LENGTH CORPUS_TESTS len)
math(EXPR last "${len} - 1")
set(partition_size 100)
foreach(i RANGE 0 ${last} ${partition_size})
list(SUBLIST CORPUS_TESTS ${i} ${partition_size} partition)
add_test(NAME conflict_set_blackbox_valgrind_${i}
COMMAND ${VALGRIND_EXE} --error-exitcode=99 --
$<TARGET_FILE:driver> ${partition})
if(VALGRIND_EXE AND NOT CMAKE_CROSSCOMPILING)
add_test(
NAME script_test_${TEST}_valgrind
COMMAND
${VALGRIND_EXE} ${Python3_EXECUTABLE}
${CMAKE_CURRENT_SOURCE_DIR}/test_conflict_set.py test ${TEST}
--build-dir ${CMAKE_CURRENT_BINARY_DIR})
endif()
endforeach()
endif()
@@ -350,6 +358,11 @@ if(CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR AND BUILD_TESTING)
set_target_properties(server_bench PROPERTIES SKIP_BUILD_RPATH ON)
add_executable(interleaving_test InterleavingTest.cpp)
# work around lack of musttail for gcc
if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" AND CMAKE_BUILD_TYPE STREQUAL "Debug")
target_compile_options(interleaving_test PRIVATE -Og
-foptimize-sibling-calls)
endif()
target_link_libraries(interleaving_test PRIVATE nanobench)
endif()

View File

@@ -203,6 +203,60 @@ enum Type : int8_t {
template <class T> struct BoundedFreeListAllocator;
struct TaggedNodePointer {
TaggedNodePointer() = default;
operator struct Node *() { return (struct Node *)(uintptr_t)p; }
operator struct Node0 *() {
assert(t == Type_Node0);
return (struct Node0 *)(uintptr_t)p;
}
operator struct Node3 *() {
assert(t == Type_Node3);
return (struct Node3 *)(uintptr_t)p;
}
operator struct Node16 *() {
assert(t == Type_Node16);
return (struct Node16 *)(uintptr_t)p;
}
operator struct Node48 *() {
assert(t == Type_Node48);
return (struct Node48 *)(uintptr_t)p;
}
operator struct Node256 *() {
assert(t == Type_Node256);
return (struct Node256 *)(uintptr_t)p;
}
/*implicit*/ TaggedNodePointer(std::nullptr_t) : p(0), t(Type_Node0) {}
/*implicit*/ TaggedNodePointer(Node0 *x)
: TaggedNodePointer((struct Node *)x, Type_Node0) {}
/*implicit*/ TaggedNodePointer(Node3 *x)
: TaggedNodePointer((struct Node *)x, Type_Node3) {}
/*implicit*/ TaggedNodePointer(Node16 *x)
: TaggedNodePointer((struct Node *)x, Type_Node16) {}
/*implicit*/ TaggedNodePointer(Node48 *x)
: TaggedNodePointer((struct Node *)x, Type_Node48) {}
/*implicit*/ TaggedNodePointer(Node256 *x)
: TaggedNodePointer((struct Node *)x, Type_Node256) {}
bool operator!=(std::nullptr_t) { return p != 0 || t != Type_Node0; }
bool operator==(std::nullptr_t) { return p == 0 && t == Type_Node0; }
bool operator==(const TaggedNodePointer &) const = default;
bool operator==(Node *n) const { return (uintptr_t)n == p; }
Node *operator->() { return (Node *)(uintptr_t)p; }
Type getType() { return t; }
TaggedNodePointer(const TaggedNodePointer &) = default;
TaggedNodePointer &operator=(const TaggedNodePointer &) = default;
/*implicit*/ TaggedNodePointer(Node *n);
private:
TaggedNodePointer(struct Node *p, Type t) : p((uintptr_t)p), t(t) {
assume(p != 0);
}
uintptr_t p : 56;
Type t : 8;
};
struct Node {
/* begin section that's copied to the next node */
@@ -228,6 +282,9 @@ private:
int32_t partialKeyCapacity;
};
TaggedNodePointer::TaggedNodePointer(Node *n)
: TaggedNodePointer(n, n->getType()) {}
constexpr int kNodeCopyBegin = offsetof(Node, entry);
constexpr int kNodeCopySize =
offsetof(Node, parentsIndex) + sizeof(Node::parentsIndex) - kNodeCopyBegin;
@@ -251,7 +308,7 @@ struct Node3 : Node {
constexpr static auto kMaxNodes = 3;
constexpr static auto kType = Type_Node3;
Node *children[kMaxNodes];
TaggedNodePointer children[kMaxNodes];
InternalVersionT childMaxVersion[kMaxNodes];
// Sorted
uint8_t index[kMaxNodes];
@@ -267,7 +324,7 @@ struct Node16 : Node {
constexpr static auto kType = Type_Node16;
constexpr static auto kMaxNodes = 16;
Node *children[kMaxNodes];
TaggedNodePointer children[kMaxNodes];
InternalVersionT childMaxVersion[kMaxNodes];
// Sorted
uint8_t index[kMaxNodes];
@@ -288,7 +345,7 @@ struct Node48 : Node {
constexpr static int kMaxOfMaxTotalPages = kMaxNodes / kMaxOfMaxPageSize;
BitSet bitSet;
Node *children[kMaxNodes];
TaggedNodePointer children[kMaxNodes];
InternalVersionT childMaxVersion[kMaxNodes];
InternalVersionT maxOfMax[kMaxOfMaxTotalPages];
uint8_t reverseIndex[kMaxNodes];
@@ -310,7 +367,7 @@ struct Node256 : Node {
constexpr static int kMaxOfMaxTotalPages = kMaxNodes / kMaxOfMaxPageSize;
BitSet bitSet;
Node *children[kMaxNodes];
TaggedNodePointer children[kMaxNodes];
InternalVersionT childMaxVersion[kMaxNodes];
InternalVersionT maxOfMax[kMaxOfMaxTotalPages];
@@ -888,26 +945,26 @@ int getNodeIndexExists(Node16 *self, uint8_t index) {
}
// Precondition - an entry for index must exist in the node
Node *&getChildExists(Node3 *self, uint8_t index) {
TaggedNodePointer &getChildExists(Node3 *self, uint8_t index) {
return self->children[getNodeIndexExists(self, index)];
}
// Precondition - an entry for index must exist in the node
Node *&getChildExists(Node16 *self, uint8_t index) {
TaggedNodePointer &getChildExists(Node16 *self, uint8_t index) {
return self->children[getNodeIndexExists(self, index)];
}
// Precondition - an entry for index must exist in the node
Node *&getChildExists(Node48 *self, uint8_t index) {
TaggedNodePointer &getChildExists(Node48 *self, uint8_t index) {
assert(self->bitSet.test(index));
return self->children[self->index[index]];
}
// Precondition - an entry for index must exist in the node
Node *&getChildExists(Node256 *self, uint8_t index) {
TaggedNodePointer &getChildExists(Node256 *self, uint8_t index) {
assert(self->bitSet.test(index));
return self->children[index];
}
// Precondition - an entry for index must exist in the node
Node *&getChildExists(Node *self, uint8_t index) {
TaggedNodePointer &getChildExists(Node *self, uint8_t index) {
switch (self->getType()) {
case Type_Node0: // GCOVR_EXCL_LINE
__builtin_unreachable(); // GCOVR_EXCL_LINE
@@ -1038,7 +1095,7 @@ void setMaxVersion(Node *n, InternalVersionT newMax) {
}
}
Node *&getInTree(Node *n, ConflictSet::Impl *);
TaggedNodePointer &getInTree(Node *n, ConflictSet::Impl *);
Node *getChild(Node0 *, uint8_t) { return nullptr; }
Node *getChild(Node3 *self, uint8_t index) {
@@ -1075,6 +1132,7 @@ Node *getChild(Node *self, uint8_t index) {
struct ChildAndMaxVersion {
Node *child;
InternalVersionT maxVersion;
Type childType;
};
ChildAndMaxVersion getChildAndMaxVersion(Node0 *, uint8_t) { return {}; }
@@ -1083,24 +1141,28 @@ ChildAndMaxVersion getChildAndMaxVersion(Node3 *self, uint8_t index) {
if (i < 0) {
return {};
}
return {self->children[i], self->childMaxVersion[i]};
return {self->children[i], self->childMaxVersion[i],
self->children[i].getType()};
}
ChildAndMaxVersion getChildAndMaxVersion(Node16 *self, uint8_t index) {
int i = getNodeIndex(self, index);
if (i < 0) {
return {};
}
return {self->children[i], self->childMaxVersion[i]};
return {self->children[i], self->childMaxVersion[i],
self->children[i].getType()};
}
ChildAndMaxVersion getChildAndMaxVersion(Node48 *self, uint8_t index) {
int i = self->index[index];
if (i < 0) {
return {};
}
return {self->children[i], self->childMaxVersion[i]};
return {self->children[i], self->childMaxVersion[i],
self->children[i].getType()};
}
ChildAndMaxVersion getChildAndMaxVersion(Node256 *self, uint8_t index) {
return {self->children[index], self->childMaxVersion[index]};
return {self->children[index], self->childMaxVersion[index],
self->children[index].getType()};
}
ChildAndMaxVersion getChildAndMaxVersion(Node *self, uint8_t index) {
@@ -1245,14 +1307,15 @@ Node *getFirstChildExists(Node *self) {
}
}
void consumePartialKeyFull(Node *&self, std::span<const uint8_t> &key,
void consumePartialKeyFull(TaggedNodePointer &self,
std::span<const uint8_t> &key,
InternalVersionT writeVersion, WriteContext *tls) {
// Handle an existing partial key
int commonLen = std::min<int>(self->partialKeyLen, key.size());
int partialKeyIndex =
longestCommonPrefix(self->partialKey(), key.data(), commonLen);
if (partialKeyIndex < self->partialKeyLen) {
auto *old = self;
Node *old = self;
// Since root cannot have a partial key
assert(old->parent != nullptr);
InternalVersionT oldMaxVersion = exchangeMaxVersion(old, writeVersion);
@@ -1290,7 +1353,7 @@ void consumePartialKeyFull(Node *&self, std::span<const uint8_t> &key,
// Consume any partial key of `self`, and update `self` and
// `key` such that `self` is along the search path of `key`
inline __attribute__((always_inline)) void
consumePartialKey(Node *&self, std::span<const uint8_t> &key,
consumePartialKey(TaggedNodePointer &self, std::span<const uint8_t> &key,
InternalVersionT writeVersion, WriteContext *tls) {
if (self->partialKeyLen > 0) {
consumePartialKeyFull(self, key, writeVersion, tls);
@@ -1301,8 +1364,10 @@ consumePartialKey(Node *&self, std::span<const uint8_t> &key,
// such that the search path of the result + key is the same as the search path
// of self + key before the call. Creates a node if necessary. Updates
// `maxVersion` for result.
Node *&getOrCreateChild(Node *&self, std::span<const uint8_t> &key,
InternalVersionT newMaxVersion, WriteContext *tls) {
TaggedNodePointer &getOrCreateChild(TaggedNodePointer &self,
std::span<const uint8_t> &key,
InternalVersionT newMaxVersion,
WriteContext *tls) {
int index = key.front();
key = key.subspan(1, key.size() - 1);
@@ -1660,10 +1725,11 @@ void rezero(Node *n, InternalVersionT z) {
}
#endif
void mergeWithChild(Node *&self, WriteContext *tls, ConflictSet::Impl *impl,
Node *&dontInvalidate, Node3 *self3) {
void mergeWithChild(TaggedNodePointer &self, WriteContext *tls,
ConflictSet::Impl *impl, Node *&dontInvalidate,
Node3 *self3) {
assert(!self3->entryPresent);
auto *child = self3->children[0];
Node *child = self3->children[0];
int minCapacity = self3->partialKeyLen + 1 + child->partialKeyLen;
if (minCapacity > child->getCapacity()) {
@@ -1912,7 +1978,7 @@ bool checkPointRead(Node *n, const std::span<const uint8_t> key,
goto downLeftSpine;
}
auto [child, maxV] = getChildAndMaxVersion(n, remaining[0]);
auto [child, maxV, childT] = getChildAndMaxVersion(n, remaining[0]);
if (child == nullptr) {
auto c = getChildGeq(n, remaining[0]);
if (c != nullptr) {
@@ -1982,7 +2048,7 @@ bool checkPrefixRead(Node *n, const std::span<const uint8_t> key,
return maxVersion(n) <= readVersion;
}
auto [child, maxV] = getChildAndMaxVersion(n, remaining[0]);
auto [child, maxV, childT] = getChildAndMaxVersion(n, remaining[0]);
if (child == nullptr) {
auto c = getChildGeq(n, remaining[0]);
if (c != nullptr) {
@@ -2255,7 +2321,7 @@ bool checkMaxBetweenExclusiveImpl(Node *n, int begin, int end,
if (!mask) {
return true;
}
auto *child = self->children[std::countr_zero(mask)];
Node *child = self->children[std::countr_zero(mask)];
const bool firstRangeOk =
!child->entryPresent || child->entry.rangeVersion <= readVersion;
uint32_t compared = 0;
@@ -2330,7 +2396,7 @@ bool checkMaxBetweenExclusiveImpl(Node *n, int begin, int end,
if (!mask) {
return true;
}
auto *child = self->children[std::countr_zero(mask)];
Node *child = self->children[std::countr_zero(mask)];
const bool firstRangeOk =
!child->entryPresent || child->entry.rangeVersion <= readVersion;
@@ -2375,7 +2441,7 @@ bool checkMaxBetweenExclusiveImpl(Node *n, int begin, int end,
{
int c = self->bitSet.firstSetGeq(begin + 1);
if (c >= 0 && c < end) {
auto *child = self->children[self->index[c]];
Node *child = self->children[self->index[c]];
if (child->entryPresent && child->entry.rangeVersion > readVersion) {
return false;
}
@@ -2409,7 +2475,7 @@ bool checkMaxBetweenExclusiveImpl(Node *n, int begin, int end,
{
int c = self->bitSet.firstSetGeq(begin + 1);
if (c >= 0 && c < end) {
auto *child = self->children[c];
Node *child = self->children[c];
if (child->entryPresent && child->entry.rangeVersion > readVersion) {
return false;
}
@@ -2581,7 +2647,7 @@ bool checkRangeLeftSide(Node *n, std::span<const uint8_t> key, int prefixLen,
}
}
auto [child, maxV] = getChildAndMaxVersion(n, remaining[0]);
auto [child, maxV, childT] = getChildAndMaxVersion(n, remaining[0]);
if (child == nullptr) {
auto c = getChildGeq(n, remaining[0]);
if (c != nullptr) {
@@ -2769,7 +2835,7 @@ bool checkRangeRead(Node *n, std::span<const uint8_t> begin,
if (remaining.size() == 0) {
break;
}
auto [child, v] = getChildAndMaxVersion(n, remaining[0]);
auto [child, v, childT] = getChildAndMaxVersion(n, remaining[0]);
if (child == nullptr) {
break;
}
@@ -2835,8 +2901,10 @@ checkMaxBetweenExclusiveImpl<true>(Node *n, int begin, int end,
// of the result will have `maxVersion` set to `writeVersion` as a
// postcondition. Nodes along the search path may be invalidated. Callers must
// ensure that the max version of the self argument is updated.
[[nodiscard]] Node **insert(Node **self, std::span<const uint8_t> key,
InternalVersionT writeVersion, WriteContext *tls) {
[[nodiscard]] TaggedNodePointer *insert(TaggedNodePointer *self,
std::span<const uint8_t> key,
InternalVersionT writeVersion,
WriteContext *tls) {
for (; key.size() != 0; ++tls->accum.insert_iterations) {
self = &getOrCreateChild(*self, key, writeVersion, tls);
@@ -2864,17 +2932,23 @@ void eraseTree(Node *root, WriteContext *tls) {
} break;
case Type_Node3: {
auto *n3 = static_cast<Node3 *>(n);
toFree.append(std::span<Node *>(n3->children, n3->numChildren));
for (int i = 0; i < n3->numChildren; ++i) {
toFree.push_back(n3->children[i]);
}
tls->release(n3);
} break;
case Type_Node16: {
auto *n16 = static_cast<Node16 *>(n);
toFree.append(std::span<Node *>(n16->children, n16->numChildren));
for (int i = 0; i < n16->numChildren; ++i) {
toFree.push_back(n16->children[i]);
}
tls->release(n16);
} break;
case Type_Node48: {
auto *n48 = static_cast<Node48 *>(n);
toFree.append(std::span<Node *>(n48->children, n48->numChildren));
for (int i = 0; i < n48->numChildren; ++i) {
toFree.push_back(n48->children[i]);
}
tls->release(n48);
} break;
case Type_Node256: {
@@ -2890,10 +2964,10 @@ void eraseTree(Node *root, WriteContext *tls) {
}
}
void addPointWrite(Node *&root, std::span<const uint8_t> key,
void addPointWrite(TaggedNodePointer &root, std::span<const uint8_t> key,
InternalVersionT writeVersion, WriteContext *tls) {
++tls->accum.point_writes;
auto *n = *insert(&root, key, writeVersion, tls);
auto n = *insert(&root, key, writeVersion, tls);
if (!n->entryPresent) {
++tls->accum.entries_inserted;
auto *p = nextLogical(n);
@@ -2911,8 +2985,8 @@ void addPointWrite(Node *&root, std::span<const uint8_t> key,
}
#if defined(HAS_AVX) && !defined(__SANITIZE_THREAD__)
__attribute__((target("avx512f"))) InternalVersionT
horizontalMaxUpTo16(InternalVersionT *vs, InternalVersionT z, int len) {
__attribute__((target("avx512f"))) InternalVersionT horizontalMaxUpTo16(
InternalVersionT *vs, [[maybe_unused]] InternalVersionT z, int len) {
assume(len <= 16);
#if USE_64_BIT
// Hope it gets vectorized
@@ -2948,7 +3022,7 @@ horizontalMaxUpTo16(InternalVersionT *vs, InternalVersionT, int len) {
#if defined(HAS_AVX) && !defined(__SANITIZE_THREAD__)
__attribute__((target("avx512f"))) InternalVersionT
horizontalMax16(InternalVersionT *vs, InternalVersionT z) {
horizontalMax16(InternalVersionT *vs, [[maybe_unused]] InternalVersionT z) {
#if USE_64_BIT
// Hope it gets vectorized
InternalVersionT max = vs[0];
@@ -2957,7 +3031,7 @@ horizontalMax16(InternalVersionT *vs, InternalVersionT z) {
}
return max;
#else
uint32_t zero;
uint32_t zero; // GCOVR_EXCL_LINE
memcpy(&zero, &z, sizeof(zero));
auto zeroVec = _mm512_set1_epi32(zero);
return InternalVersionT(zero + _mm512_reduce_max_epi32(_mm512_sub_epi32(
@@ -3011,7 +3085,7 @@ void fixupMaxVersion(Node *node, WriteContext *tls) {
setMaxVersion(node, max);
}
void addWriteRange(Node *&root, std::span<const uint8_t> begin,
void addWriteRange(TaggedNodePointer &root, std::span<const uint8_t> begin,
std::span<const uint8_t> end, InternalVersionT writeVersion,
WriteContext *tls, ConflictSet::Impl *impl) {
@@ -3024,12 +3098,12 @@ void addWriteRange(Node *&root, std::span<const uint8_t> begin,
++tls->accum.range_writes;
const bool beginIsPrefix = lcp == int(begin.size());
Node **useAsRoot = insert(&root, begin.subspan(0, lcp), writeVersion, tls);
auto useAsRoot = insert(&root, begin.subspan(0, lcp), writeVersion, tls);
begin = begin.subspan(lcp, begin.size() - lcp);
end = end.subspan(lcp, end.size() - lcp);
auto *beginNode = *insert(useAsRoot, begin, writeVersion, tls);
Node *beginNode = *insert(useAsRoot, begin, writeVersion, tls);
addKey(beginNode);
if (!beginNode->entryPresent) {
++tls->accum.entries_inserted;
@@ -3040,7 +3114,7 @@ void addWriteRange(Node *&root, std::span<const uint8_t> begin,
}
beginNode->entry.pointVersion = writeVersion;
auto *endNode = *insert(useAsRoot, end, writeVersion, tls);
Node *endNode = *insert(useAsRoot, end, writeVersion, tls);
addKey(endNode);
if (!endNode->entryPresent) {
++tls->accum.entries_inserted;
@@ -3060,7 +3134,7 @@ void addWriteRange(Node *&root, std::span<const uint8_t> begin,
assert(!beginNode->endOfRange);
assert(!endNode->endOfRange);
endNode->endOfRange = true;
auto *iter = beginNode;
Node *iter = beginNode;
for (iter = nextLogical(iter); !iter->endOfRange;
iter = erase(iter, tls, impl, /*logical*/ true)) {
assert(!iter->endOfRange);
@@ -3375,7 +3449,7 @@ struct __attribute__((visibility("hidden"))) ConflictSet::Impl {
std::span<const uint8_t> removalKey;
int64_t keyUpdates;
Node *root;
TaggedNodePointer root;
InternalVersionT oldestVersion;
int64_t oldestVersionFullPrecision;
int64_t oldestExtantVersion;
@@ -3456,7 +3530,7 @@ struct __attribute__((visibility("hidden"))) ConflictSet::Impl {
}
};
Node *&getInTree(Node *n, ConflictSet::Impl *impl) {
TaggedNodePointer &getInTree(Node *n, ConflictSet::Impl *impl) {
return n->parent == nullptr ? impl->root
: getChildExists(n->parent, n->parentsIndex);
}

View File

@@ -22,9 +22,6 @@ void *stepJob(Job *j) {
return done ? nullptr : (void *)stepJob;
}
// So we can look at the disassembly more easily
extern "C" {
void sequential(Job **jobs, int count) {
for (int i = 0; i < count; ++i) {
do {
@@ -94,6 +91,87 @@ void interleaveBoundedCyclicList(Job **jobs, int count) {
}
}
#ifndef __has_attribute
#define __has_attribute(x) 0
#endif
#if __has_attribute(musttail)
#define MUSTTAIL __attribute__((musttail))
#else
#define MUSTTAIL
#endif
struct Context {
constexpr static int kConcurrent = 32;
Job **jobs;
Job *inProgress[kConcurrent];
void (*continuation[kConcurrent])(Context *, int64_t prevJob, int64_t job,
int64_t started, int64_t count);
int nextJob[kConcurrent];
};
void keepGoing(Context *context, int64_t prevJob, int64_t job, int64_t started,
int64_t count) {
prevJob = job;
job = context->nextJob[job];
MUSTTAIL return context->continuation[job](context, prevJob, job, started,
count);
}
void stepJobTailCall(Context *context, int64_t prevJob, int64_t job,
int64_t started, int64_t count);
void complete(Context *context, int64_t prevJob, int64_t job, int64_t started,
int64_t count) {
if (started == count) {
if (prevJob == job) {
return;
}
context->nextJob[prevJob] = context->nextJob[job];
job = prevJob;
} else {
context->inProgress[job] = context->jobs[started++];
context->continuation[job] = stepJobTailCall;
}
prevJob = job;
job = context->nextJob[job];
MUSTTAIL return context->continuation[job](context, prevJob, job, started,
count);
}
void stepJobTailCall(Context *context, int64_t prevJob, int64_t job,
int64_t started, int64_t count) {
auto *j = context->inProgress[job];
auto done = --(*j->input) == 0;
#ifdef __x86_64__
_mm_clflush(j->input);
#endif
if (done) {
MUSTTAIL return complete(context, prevJob, job, started, count);
} else {
context->continuation[job] = stepJobTailCall;
MUSTTAIL return keepGoing(context, prevJob, job, started, count);
}
}
void useTailCalls(Job **jobs, int count) {
if (count == 0) {
return;
}
Context context;
context.jobs = jobs;
int64_t started = std::min(Context::kConcurrent, count);
for (int i = 0; i < started; i++) {
context.inProgress[i] = jobs[i];
context.nextJob[i] = i + 1;
context.continuation[i] = stepJobTailCall;
}
context.nextJob[started - 1] = 0;
int prevJob = started - 1;
int job = 0;
return context.continuation[job](&context, prevJob, job, started, count);
}
void interleaveCyclicList(Job **jobs, int count) {
auto *nextJob = (int *)alloca(sizeof(int) * count);
@@ -117,12 +195,11 @@ void interleaveCyclicList(Job **jobs, int count) {
job = nextJob[job];
}
}
}
int main() {
ankerl::nanobench::Bench bench;
constexpr int kNumJobs = 100;
constexpr int kNumJobs = 10000;
bench.relative(true);
Job jobs[kNumJobs];
@@ -140,6 +217,7 @@ int main() {
for (auto [scheduler, name] :
{std::make_pair(sequentialNoFuncPtr, "sequentialNoFuncPtr"),
std::make_pair(sequential, "sequential"),
std::make_pair(useTailCalls, "useTailCalls"),
std::make_pair(interleaveSwapping, "interleavingSwapping"),
std::make_pair(interleaveBoundedCyclicList,
"interleaveBoundedCyclicList"),

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.