Compare commits
40 Commits
v0.0.5
..
5378a06c39
| Author | SHA1 | Date | |
|---|---|---|---|
| 5378a06c39 | |||
| 12c6ed2568 | |||
| a2bf839b19 | |||
| c065b185ae | |||
| 639518bed4 | |||
| 7de983cc15 | |||
| 1b4b61ddc6 | |||
| bff7b85de2 | |||
| 9108ee209a | |||
| f8bf1c6eb4 | |||
| 4da2a01614 | |||
| bb0e654040 | |||
| cce7d29410 | |||
| 13f8d3fa8a | |||
| 02866a8cae | |||
| fa86d3e707 | |||
| 7d1d1d7b2a | |||
| 789ecc29b3 | |||
| 08f2998a85 | |||
| c882d7663d | |||
| bfea4384ba | |||
| 6520e3d734 | |||
| 23ace8aac5 | |||
| 62e35de320 | |||
| 22e4ab01a1 | |||
| b3aeed0caa | |||
| 5f3833e965 | |||
| 8b1cd9c052 | |||
| bb9bc3d7b5 | |||
| 89b3354a80 | |||
| 488c723726 | |||
| 76d0785b33 | |||
| add0af11ad | |||
| 2c0adf4a8b | |||
| e8ac78cce6 | |||
| 13d447c9fe | |||
| da7523c5cf | |||
| a074bc6f72 | |||
| 1553a44986 | |||
| 859ac352e6 |
@@ -258,4 +258,107 @@ void benchConflictSet() {
|
||||
}
|
||||
}
|
||||
|
||||
int main(void) { benchConflictSet(); }
|
||||
constexpr int kKeyLenForWorstCase = 50;
|
||||
|
||||
ConflictSet worstCaseConflictSetForRadixRangeRead(int cardinality) {
|
||||
ConflictSet cs{0};
|
||||
|
||||
for (int i = 0; i < kKeyLenForWorstCase; ++i) {
|
||||
for (int j = 0; j < cardinality; ++j) {
|
||||
auto b = std::vector<uint8_t>(i, 0);
|
||||
b.push_back(j);
|
||||
auto e = std::vector<uint8_t>(i, 255);
|
||||
e.push_back(255 - j);
|
||||
weaselab::ConflictSet::WriteRange w[] = {{
|
||||
{b.data(), int(b.size())},
|
||||
{nullptr, 0},
|
||||
},
|
||||
{
|
||||
{e.data(), int(e.size())},
|
||||
{nullptr, 0},
|
||||
}};
|
||||
std::sort(std::begin(w), std::end(w),
|
||||
[](const auto &lhs, const auto &rhs) {
|
||||
int cl = std::min(lhs.begin.len, rhs.begin.len);
|
||||
if (cl > 0) {
|
||||
int c = memcmp(lhs.begin.p, rhs.begin.p, cl);
|
||||
if (c != 0) {
|
||||
return c < 0;
|
||||
}
|
||||
}
|
||||
return lhs.begin.len < rhs.begin.len;
|
||||
});
|
||||
cs.addWrites(w, sizeof(w) / sizeof(w[0]), 0);
|
||||
}
|
||||
}
|
||||
|
||||
// Defeat short-circuiting on the left
|
||||
{
|
||||
auto k = std::vector<uint8_t>(kKeyLenForWorstCase, 0);
|
||||
weaselab::ConflictSet::WriteRange w[] = {
|
||||
{
|
||||
{k.data(), int(k.size())},
|
||||
{nullptr, 0},
|
||||
},
|
||||
};
|
||||
cs.addWrites(w, sizeof(w) / sizeof(w[0]), 1);
|
||||
}
|
||||
|
||||
// Defeat short-circuiting on the right
|
||||
{
|
||||
auto k = std::vector<uint8_t>(kKeyLenForWorstCase, 255);
|
||||
weaselab::ConflictSet::WriteRange w[] = {
|
||||
{
|
||||
{k.data(), int(k.size())},
|
||||
{nullptr, 0},
|
||||
},
|
||||
};
|
||||
cs.addWrites(w, sizeof(w) / sizeof(w[0]), 1);
|
||||
}
|
||||
|
||||
return cs;
|
||||
}
|
||||
|
||||
void benchWorstCaseForRadixRangeRead() {
|
||||
ankerl::nanobench::Bench bench;
|
||||
|
||||
std::unique_ptr<ConflictSet> cs[256];
|
||||
for (int i = 0; i < 256; ++i) {
|
||||
cs[i] =
|
||||
std::make_unique<ConflictSet>(worstCaseConflictSetForRadixRangeRead(i));
|
||||
}
|
||||
|
||||
auto begin = std::vector<uint8_t>(kKeyLenForWorstCase - 1, 0);
|
||||
begin.push_back(1);
|
||||
auto end = std::vector<uint8_t>(kKeyLenForWorstCase - 1, 255);
|
||||
end.push_back(254);
|
||||
|
||||
weaselab::ConflictSet::Result result;
|
||||
weaselab::ConflictSet::ReadRange r{
|
||||
{begin.data(), int(begin.size())}, {end.data(), int(end.size())}, 0};
|
||||
|
||||
bench.run("worst case for radix tree", [&]() {
|
||||
for (int i = 0; i < 256; ++i) {
|
||||
result = weaselab::ConflictSet::TooOld;
|
||||
cs[i]->check(&r, &result, 1);
|
||||
if (result != weaselab::ConflictSet::Commit) {
|
||||
abort();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// for (int i = 0; i < 256; ++i) {
|
||||
// bench.run("worst case for radix tree, span " + std::to_string(i), [&]() {
|
||||
// result = weaselab::ConflictSet::TooOld;
|
||||
// cs[i]->check(&r, &result, 1);
|
||||
// if (result != weaselab::ConflictSet::Commit) {
|
||||
// abort();
|
||||
// }
|
||||
// });
|
||||
// }
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
benchConflictSet();
|
||||
benchWorstCaseForRadixRangeRead();
|
||||
}
|
||||
|
||||
+51
-38
@@ -1,14 +1,16 @@
|
||||
cmake_minimum_required(VERSION 3.18)
|
||||
project(
|
||||
conflict-set
|
||||
VERSION 0.0.5
|
||||
VERSION 0.0.7
|
||||
DESCRIPTION
|
||||
"A data structure for optimistic concurrency control on ranges of bitwise-lexicographically-ordered keys."
|
||||
HOMEPAGE_URL "https://git.weaselab.dev/weaselab/conflict-set"
|
||||
LANGUAGES C CXX)
|
||||
set(CMAKE_CXX_STANDARD 20)
|
||||
|
||||
file(WRITE ${CMAKE_BINARY_DIR}/version.txt ${PROJECT_VERSION})
|
||||
file(WRITE ${CMAKE_CURRENT_BINARY_DIR}/version.txt ${PROJECT_VERSION})
|
||||
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/version.txt.in
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/paper/version.txt)
|
||||
|
||||
include(CMakePushCheckState)
|
||||
include(CheckCXXCompilerFlag)
|
||||
@@ -47,7 +49,8 @@ if(HAS_FULL_RELRO)
|
||||
endif()
|
||||
cmake_pop_check_state()
|
||||
|
||||
set(version_script_flags LINKER:--version-script=${CMAKE_SOURCE_DIR}/linker.map)
|
||||
set(version_script_flags
|
||||
LINKER:--version-script=${CMAKE_CURRENT_SOURCE_DIR}/linker.map)
|
||||
cmake_push_check_state()
|
||||
list(APPEND CMAKE_REQUIRED_LINK_OPTIONS ${version_script_flags})
|
||||
check_cxx_source_compiles("int main(){}" HAS_VERSION_SCRIPT FAIL_REGEX
|
||||
@@ -59,7 +62,7 @@ option(USE_SIMD_FALLBACK
|
||||
|
||||
# This is encouraged according to
|
||||
# https://valgrind.org/docs/manual/manual-core-adv.html#manual-core-adv.clientreq
|
||||
include_directories(SYSTEM ${CMAKE_SOURCE_DIR}/third_party/valgrind)
|
||||
include_directories(SYSTEM ${CMAKE_CURRENT_SOURCE_DIR}/third_party/valgrind)
|
||||
|
||||
add_compile_options($<$<COMPILE_LANGUAGE:CXX>:-Wno-invalid-offsetof>)
|
||||
|
||||
@@ -106,19 +109,20 @@ add_library(${PROJECT_NAME}-object OBJECT ConflictSet.cpp)
|
||||
target_compile_options(${PROJECT_NAME}-object PRIVATE -fno-exceptions
|
||||
-fvisibility=hidden)
|
||||
target_include_directories(${PROJECT_NAME}-object
|
||||
PRIVATE ${CMAKE_SOURCE_DIR}/include)
|
||||
PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include)
|
||||
|
||||
add_library(${PROJECT_NAME} SHARED $<TARGET_OBJECTS:${PROJECT_NAME}-object>)
|
||||
set_target_properties(
|
||||
${PROJECT_NAME} PROPERTIES LIBRARY_OUTPUT_DIRECTORY
|
||||
"${CMAKE_BINARY_DIR}/radix_tree")
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/radix_tree")
|
||||
if(NOT CMAKE_BUILD_TYPE STREQUAL Debug)
|
||||
set_target_properties(${PROJECT_NAME} PROPERTIES LINKER_LANGUAGE C)
|
||||
endif()
|
||||
|
||||
if(HAS_VERSION_SCRIPT)
|
||||
target_link_options(${PROJECT_NAME} PRIVATE
|
||||
LINKER:--version-script=${CMAKE_SOURCE_DIR}/linker.map)
|
||||
target_link_options(
|
||||
${PROJECT_NAME} PRIVATE
|
||||
LINKER:--version-script=${CMAKE_CURRENT_SOURCE_DIR}/linker.map)
|
||||
endif()
|
||||
|
||||
add_library(${PROJECT_NAME}-static STATIC
|
||||
@@ -131,7 +135,7 @@ if(APPLE)
|
||||
add_custom_command(
|
||||
TARGET ${PROJECT_NAME}-static
|
||||
PRE_LINK
|
||||
COMMAND ${CMAKE_SOURCE_DIR}/privatize_symbols_macos.sh
|
||||
COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/privatize_symbols_macos.sh
|
||||
$<TARGET_OBJECTS:${PROJECT_NAME}-object>)
|
||||
else()
|
||||
add_custom_command(
|
||||
@@ -139,21 +143,22 @@ else()
|
||||
POST_BUILD
|
||||
COMMAND
|
||||
${CMAKE_OBJCOPY}
|
||||
--keep-global-symbols=${CMAKE_SOURCE_DIR}/symbol-exports.txt
|
||||
--keep-global-symbols=${CMAKE_CURRENT_SOURCE_DIR}/symbol-exports.txt
|
||||
$<TARGET_FILE:${PROJECT_NAME}-static> || echo
|
||||
"Proceeding with all symbols global in static library")
|
||||
endif()
|
||||
|
||||
set(TEST_FLAGS -Wall -Wextra -Wunreachable-code -Wpedantic -UNDEBUG)
|
||||
|
||||
include(CTest)
|
||||
|
||||
if(BUILD_TESTING)
|
||||
# disable tests if this is being used through e.g. FetchContent
|
||||
if(CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR AND BUILD_TESTING)
|
||||
|
||||
set(TEST_FLAGS -Wall -Wextra -Wunreachable-code -Wpedantic -UNDEBUG)
|
||||
|
||||
# corpus tests, which are tests curated by libfuzzer. The goal is to get broad
|
||||
# coverage with a small number of tests.
|
||||
|
||||
file(GLOB CORPUS_TESTS ${CMAKE_SOURCE_DIR}/corpus/*)
|
||||
file(GLOB CORPUS_TESTS ${CMAKE_CURRENT_SOURCE_DIR}/corpus/*)
|
||||
|
||||
# extra testing that relies on shared libraries, which aren't available with
|
||||
# wasm
|
||||
@@ -162,9 +167,11 @@ if(BUILD_TESTING)
|
||||
add_library(skip_list SHARED SkipList.cpp)
|
||||
target_compile_options(skip_list PRIVATE -fno-exceptions
|
||||
-fvisibility=hidden)
|
||||
target_include_directories(skip_list PUBLIC ${CMAKE_SOURCE_DIR}/include)
|
||||
set_target_properties(skip_list PROPERTIES LIBRARY_OUTPUT_DIRECTORY
|
||||
"${CMAKE_BINARY_DIR}/skip_list")
|
||||
target_include_directories(skip_list
|
||||
PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include)
|
||||
set_target_properties(
|
||||
skip_list PROPERTIES LIBRARY_OUTPUT_DIRECTORY
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/skip_list")
|
||||
set_target_properties(skip_list PROPERTIES OUTPUT_NAME ${PROJECT_NAME})
|
||||
set_target_properties(
|
||||
skip_list PROPERTIES VERSION ${PROJECT_VERSION} SOVERSION
|
||||
@@ -175,10 +182,11 @@ if(BUILD_TESTING)
|
||||
add_library(hash_table SHARED HashTable.cpp)
|
||||
target_compile_options(hash_table PRIVATE -fno-exceptions
|
||||
-fvisibility=hidden)
|
||||
target_include_directories(hash_table PUBLIC ${CMAKE_SOURCE_DIR}/include)
|
||||
target_include_directories(hash_table
|
||||
PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include)
|
||||
set_target_properties(
|
||||
hash_table PROPERTIES LIBRARY_OUTPUT_DIRECTORY
|
||||
"${CMAKE_BINARY_DIR}/hash_table")
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/hash_table")
|
||||
set_target_properties(hash_table PROPERTIES OUTPUT_NAME ${PROJECT_NAME})
|
||||
set_target_properties(
|
||||
hash_table PROPERTIES VERSION ${PROJECT_VERSION} SOVERSION
|
||||
@@ -266,15 +274,19 @@ if(BUILD_TESTING)
|
||||
set_property(
|
||||
DIRECTORY
|
||||
APPEND
|
||||
PROPERTY CMAKE_CONFIGURE_DEPENDS ${CMAKE_SOURCE_DIR}/test_conflict_set.py)
|
||||
PROPERTY CMAKE_CONFIGURE_DEPENDS
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/test_conflict_set.py)
|
||||
execute_process(
|
||||
COMMAND ${Python3_EXECUTABLE} ${CMAKE_SOURCE_DIR}/test_conflict_set.py
|
||||
list OUTPUT_VARIABLE SCRIPT_TESTS)
|
||||
COMMAND ${Python3_EXECUTABLE}
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/test_conflict_set.py list
|
||||
OUTPUT_VARIABLE SCRIPT_TESTS)
|
||||
foreach(TEST ${SCRIPT_TESTS})
|
||||
add_test(
|
||||
NAME script_test_${TEST}
|
||||
COMMAND ${Python3_EXECUTABLE} ${CMAKE_SOURCE_DIR}/test_conflict_set.py
|
||||
test ${TEST} --build-dir ${CMAKE_BINARY_DIR})
|
||||
COMMAND
|
||||
${Python3_EXECUTABLE}
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/test_conflict_set.py test ${TEST}
|
||||
--build-dir ${CMAKE_CURRENT_BINARY_DIR})
|
||||
endforeach()
|
||||
endif()
|
||||
|
||||
@@ -308,25 +320,26 @@ if(BUILD_TESTING)
|
||||
# symbol visibility tests
|
||||
if(NOT WASM AND NOT CMAKE_BUILD_TYPE STREQUAL Debug)
|
||||
if(APPLE)
|
||||
set(symbol_exports ${CMAKE_SOURCE_DIR}/apple-symbol-exports.txt)
|
||||
set(symbol_imports ${CMAKE_SOURCE_DIR}/apple-symbol-imports.txt)
|
||||
set(symbol_exports ${CMAKE_CURRENT_SOURCE_DIR}/apple-symbol-exports.txt)
|
||||
set(symbol_imports ${CMAKE_CURRENT_SOURCE_DIR}/apple-symbol-imports.txt)
|
||||
else()
|
||||
set(symbol_exports ${CMAKE_SOURCE_DIR}/symbol-exports.txt)
|
||||
set(symbol_exports ${CMAKE_CURRENT_SOURCE_DIR}/symbol-exports.txt)
|
||||
if(CMAKE_SYSTEM_PROCESSOR STREQUAL aarch64)
|
||||
set(symbol_imports ${CMAKE_SOURCE_DIR}/aarch64-symbol-imports.txt)
|
||||
set(symbol_imports
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/aarch64-symbol-imports.txt)
|
||||
else()
|
||||
set(symbol_imports ${CMAKE_SOURCE_DIR}/symbol-imports.txt)
|
||||
set(symbol_imports ${CMAKE_CURRENT_SOURCE_DIR}/symbol-imports.txt)
|
||||
endif()
|
||||
endif()
|
||||
add_test(
|
||||
NAME conflict_set_shared_symbols
|
||||
COMMAND
|
||||
${CMAKE_SOURCE_DIR}/test_symbols.sh $<TARGET_FILE:${PROJECT_NAME}>
|
||||
${symbol_exports} ${symbol_imports})
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/test_symbols.sh
|
||||
$<TARGET_FILE:${PROJECT_NAME}> ${symbol_exports} ${symbol_imports})
|
||||
add_test(
|
||||
NAME conflict_set_static_symbols
|
||||
COMMAND
|
||||
${CMAKE_SOURCE_DIR}/test_symbols.sh
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/test_symbols.sh
|
||||
$<TARGET_FILE:${PROJECT_NAME}-static> ${symbol_exports}
|
||||
${symbol_imports})
|
||||
endif()
|
||||
@@ -369,13 +382,13 @@ set(CMAKE_OSX_DEPLOYMENT_TARGET 11.0)
|
||||
if(APPLE)
|
||||
find_program(PANDOC_EXE pandoc)
|
||||
if(PANDOC_EXE)
|
||||
execute_process(COMMAND ${PANDOC_EXE} ${CMAKE_SOURCE_DIR}/README.md -o
|
||||
${CMAKE_BINARY_DIR}/README.txt)
|
||||
set(CPACK_RESOURCE_FILE_README ${CMAKE_BINARY_DIR}/README.txt)
|
||||
execute_process(COMMAND ${PANDOC_EXE} ${CMAKE_CURRENT_SOURCE_DIR}/README.md
|
||||
-o ${CMAKE_CURRENT_BINARY_DIR}/README.txt)
|
||||
set(CPACK_RESOURCE_FILE_README ${CMAKE_CURRENT_BINARY_DIR}/README.txt)
|
||||
endif()
|
||||
configure_file(${CMAKE_SOURCE_DIR}/LICENSE ${CMAKE_BINARY_DIR}/LICENSE.txt
|
||||
COPYONLY)
|
||||
set(CPACK_RESOURCE_FILE_LICENSE ${CMAKE_BINARY_DIR}/LICENSE.txt)
|
||||
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/LICENSE
|
||||
${CMAKE_CURRENT_BINARY_DIR}/LICENSE.txt COPYONLY)
|
||||
set(CPACK_RESOURCE_FILE_LICENSE ${CMAKE_CURRENT_BINARY_DIR}/LICENSE.txt)
|
||||
endif()
|
||||
|
||||
include(CPack)
|
||||
|
||||
+361
-127
@@ -236,11 +236,6 @@ constexpr int kNodeCopyBegin = offsetof(Node, entry);
|
||||
constexpr int kNodeCopySize =
|
||||
offsetof(Node, parentsIndex) + sizeof(Node::parentsIndex) - kNodeCopyBegin;
|
||||
|
||||
struct Child {
|
||||
int64_t childMaxVersion;
|
||||
Node *child;
|
||||
};
|
||||
|
||||
// copyChildrenAndKeyFrom is responsible for copying all
|
||||
// public members of Node, copying the partial key, logically copying the
|
||||
// children (converting representation if necessary), and updating all the
|
||||
@@ -262,7 +257,8 @@ struct Node3 : Node {
|
||||
constexpr static auto kType = Type_Node3;
|
||||
// Sorted
|
||||
uint8_t index[kMaxNodes];
|
||||
Child children[kMaxNodes];
|
||||
Node *children[kMaxNodes];
|
||||
int64_t childMaxVersion[kMaxNodes];
|
||||
uint8_t *partialKey() { return (uint8_t *)(this + 1); }
|
||||
|
||||
void copyChildrenAndKeyFrom(const Node0 &other);
|
||||
@@ -277,7 +273,8 @@ struct Node16 : Node {
|
||||
constexpr static auto kMaxNodes = 16;
|
||||
// Sorted
|
||||
uint8_t index[kMaxNodes];
|
||||
Child children[kMaxNodes];
|
||||
Node *children[kMaxNodes];
|
||||
int64_t childMaxVersion[kMaxNodes];
|
||||
uint8_t *partialKey() { return (uint8_t *)(this + 1); }
|
||||
|
||||
void copyChildrenAndKeyFrom(const Node3 &other);
|
||||
@@ -293,7 +290,14 @@ struct Node48 : Node {
|
||||
BitSet bitSet;
|
||||
int8_t nextFree;
|
||||
int8_t index[256];
|
||||
Child children[kMaxNodes];
|
||||
Node *children[kMaxNodes];
|
||||
int64_t childMaxVersion[kMaxNodes];
|
||||
uint8_t reverseIndex[kMaxNodes];
|
||||
constexpr static int kMaxOfMaxPageSize = 16;
|
||||
constexpr static int kMaxOfMaxShift =
|
||||
std::countr_zero(uint32_t(kMaxOfMaxPageSize));
|
||||
constexpr static int kMaxOfMaxTotalPages = kMaxNodes / kMaxOfMaxPageSize;
|
||||
int64_t maxOfMax[kMaxOfMaxTotalPages];
|
||||
|
||||
uint8_t *partialKey() { return (uint8_t *)(this + 1); }
|
||||
|
||||
@@ -307,7 +311,13 @@ struct Node48 : Node {
|
||||
struct Node256 : Node {
|
||||
constexpr static auto kType = Type_Node256;
|
||||
BitSet bitSet;
|
||||
Child children[256];
|
||||
Node *children[256];
|
||||
int64_t childMaxVersion[256];
|
||||
constexpr static int kMaxOfMaxPageSize = 16;
|
||||
constexpr static int kMaxOfMaxShift =
|
||||
std::countr_zero(uint32_t(kMaxOfMaxPageSize));
|
||||
constexpr static int kMaxOfMaxTotalPages = 256 / kMaxOfMaxPageSize;
|
||||
int64_t maxOfMax[kMaxOfMaxTotalPages];
|
||||
uint8_t *partialKey() { return (uint8_t *)(this + 1); }
|
||||
void copyChildrenAndKeyFrom(const Node48 &other);
|
||||
void copyChildrenAndKeyFrom(const Node256 &other);
|
||||
@@ -339,8 +349,8 @@ inline void Node3::copyChildrenAndKeyFrom(const Node3 &other) {
|
||||
memcpy(index, other.index, sizeof(*this) - sizeof(Node));
|
||||
memcpy(partialKey(), &other + 1, partialKeyLen);
|
||||
for (int i = 0; i < numChildren; ++i) {
|
||||
assert(children[i].child->parent == &other);
|
||||
children[i].child->parent = this;
|
||||
assert(children[i]->parent == &other);
|
||||
children[i]->parent = this;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -348,11 +358,13 @@ inline void Node3::copyChildrenAndKeyFrom(const Node16 &other) {
|
||||
memcpy((char *)this + kNodeCopyBegin, (char *)&other + kNodeCopyBegin,
|
||||
kNodeCopySize);
|
||||
memcpy(index, other.index, kMaxNodes);
|
||||
memcpy(children, other.children, kMaxNodes * sizeof(Child));
|
||||
memcpy(children, other.children, kMaxNodes * sizeof(children[0])); // NOLINT
|
||||
memcpy(childMaxVersion, other.childMaxVersion,
|
||||
kMaxNodes * sizeof(childMaxVersion[0]));
|
||||
memcpy(partialKey(), &other + 1, partialKeyLen);
|
||||
for (int i = 0; i < numChildren; ++i) {
|
||||
assert(children[i].child->parent == &other);
|
||||
children[i].child->parent = this;
|
||||
assert(children[i]->parent == &other);
|
||||
children[i]->parent = this;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -360,12 +372,15 @@ inline void Node16::copyChildrenAndKeyFrom(const Node3 &other) {
|
||||
memcpy((char *)this + kNodeCopyBegin, (char *)&other + kNodeCopyBegin,
|
||||
kNodeCopySize);
|
||||
memcpy(index, other.index, Node3::kMaxNodes);
|
||||
memcpy(children, other.children, Node3::kMaxNodes * sizeof(Child));
|
||||
memcpy(children, other.children,
|
||||
Node3::kMaxNodes * sizeof(children[0])); // NOLINT
|
||||
memcpy(childMaxVersion, other.childMaxVersion,
|
||||
Node3::kMaxNodes * sizeof(childMaxVersion[0]));
|
||||
memcpy(partialKey(), &other + 1, partialKeyLen);
|
||||
assert(numChildren == Node3::kMaxNodes);
|
||||
for (int i = 0; i < Node3::kMaxNodes; ++i) {
|
||||
assert(children[i].child->parent == &other);
|
||||
children[i].child->parent = this;
|
||||
assert(children[i]->parent == &other);
|
||||
children[i]->parent = this;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -375,8 +390,9 @@ inline void Node16::copyChildrenAndKeyFrom(const Node16 &other) {
|
||||
memcpy(index, other.index, sizeof(index));
|
||||
for (int i = 0; i < numChildren; ++i) {
|
||||
children[i] = other.children[i];
|
||||
assert(children[i].child->parent == &other);
|
||||
children[i].child->parent = this;
|
||||
childMaxVersion[i] = other.childMaxVersion[i];
|
||||
assert(children[i]->parent == &other);
|
||||
children[i]->parent = this;
|
||||
}
|
||||
memcpy(partialKey(), &other + 1, partialKeyLen);
|
||||
}
|
||||
@@ -392,8 +408,9 @@ inline void Node16::copyChildrenAndKeyFrom(const Node48 &other) {
|
||||
assume(i < Node16::kMaxNodes);
|
||||
index[i] = c;
|
||||
children[i] = other.children[other.index[c]];
|
||||
assert(children[i].child->parent == &other);
|
||||
children[i].child->parent = this;
|
||||
childMaxVersion[i] = other.childMaxVersion[other.index[c]];
|
||||
assert(children[i]->parent == &other);
|
||||
children[i]->parent = this;
|
||||
++i;
|
||||
},
|
||||
0, 256);
|
||||
@@ -405,6 +422,8 @@ inline void Node48::copyChildrenAndKeyFrom(const Node16 &other) {
|
||||
kNodeCopySize);
|
||||
assert(numChildren == Node16::kMaxNodes);
|
||||
memset(index, -1, sizeof(index));
|
||||
memset(children, 0, sizeof(children));
|
||||
memset(childMaxVersion, 0, sizeof(childMaxVersion));
|
||||
memcpy(partialKey(), &other + 1, partialKeyLen);
|
||||
bitSet.init();
|
||||
nextFree = Node16::kMaxNodes;
|
||||
@@ -413,8 +432,12 @@ inline void Node48::copyChildrenAndKeyFrom(const Node16 &other) {
|
||||
bitSet.set(x);
|
||||
index[x] = i;
|
||||
children[i] = other.children[i];
|
||||
assert(children[i].child->parent == &other);
|
||||
children[i].child->parent = this;
|
||||
childMaxVersion[i] = other.childMaxVersion[i];
|
||||
assert(children[i]->parent == &other);
|
||||
children[i]->parent = this;
|
||||
reverseIndex[i] = x;
|
||||
maxOfMax[i >> Node48::kMaxOfMaxShift] =
|
||||
std::max(maxOfMax[i >> Node48::kMaxOfMaxShift], childMaxVersion[i]);
|
||||
++i;
|
||||
}
|
||||
}
|
||||
@@ -422,13 +445,19 @@ inline void Node48::copyChildrenAndKeyFrom(const Node16 &other) {
|
||||
inline void Node48::copyChildrenAndKeyFrom(const Node48 &other) {
|
||||
memcpy((char *)this + kNodeCopyBegin, (char *)&other + kNodeCopyBegin,
|
||||
kNodeCopySize);
|
||||
memcpy(&bitSet, &other.bitSet,
|
||||
sizeof(*this) - sizeof(Node) - sizeof(children));
|
||||
bitSet = other.bitSet;
|
||||
nextFree = other.nextFree;
|
||||
memcpy(index, other.index, sizeof(index));
|
||||
memset(children, 0, sizeof(children));
|
||||
memset(childMaxVersion, 0, sizeof(childMaxVersion));
|
||||
for (int i = 0; i < numChildren; ++i) {
|
||||
children[i] = other.children[i];
|
||||
assert(children[i].child->parent == &other);
|
||||
children[i].child->parent = this;
|
||||
childMaxVersion[i] = other.childMaxVersion[i];
|
||||
assert(children[i]->parent == &other);
|
||||
children[i]->parent = this;
|
||||
}
|
||||
memcpy(reverseIndex, other.reverseIndex, sizeof(reverseIndex));
|
||||
memcpy(maxOfMax, other.maxOfMax, sizeof(maxOfMax));
|
||||
memcpy(partialKey(), &other + 1, partialKeyLen);
|
||||
}
|
||||
|
||||
@@ -436,6 +465,8 @@ inline void Node48::copyChildrenAndKeyFrom(const Node256 &other) {
|
||||
memcpy((char *)this + kNodeCopyBegin, (char *)&other + kNodeCopyBegin,
|
||||
kNodeCopySize);
|
||||
memset(index, -1, sizeof(index));
|
||||
memset(children, 0, sizeof(children));
|
||||
memset(childMaxVersion, 0, sizeof(childMaxVersion));
|
||||
nextFree = other.numChildren;
|
||||
bitSet = other.bitSet;
|
||||
int i = 0;
|
||||
@@ -446,8 +477,12 @@ inline void Node48::copyChildrenAndKeyFrom(const Node256 &other) {
|
||||
assume(i < Node48::kMaxNodes);
|
||||
index[c] = i;
|
||||
children[i] = other.children[c];
|
||||
assert(children[i].child->parent == &other);
|
||||
children[i].child->parent = this;
|
||||
childMaxVersion[i] = other.childMaxVersion[c];
|
||||
assert(children[i]->parent == &other);
|
||||
children[i]->parent = this;
|
||||
reverseIndex[i] = c;
|
||||
maxOfMax[i >> Node48::kMaxOfMaxShift] =
|
||||
std::max(maxOfMax[i >> Node48::kMaxOfMaxShift], childMaxVersion[i]);
|
||||
++i;
|
||||
},
|
||||
0, 256);
|
||||
@@ -457,13 +492,18 @@ inline void Node48::copyChildrenAndKeyFrom(const Node256 &other) {
|
||||
inline void Node256::copyChildrenAndKeyFrom(const Node48 &other) {
|
||||
memcpy((char *)this + kNodeCopyBegin, (char *)&other + kNodeCopyBegin,
|
||||
kNodeCopySize);
|
||||
memset(children, 0, sizeof(children));
|
||||
bitSet = other.bitSet;
|
||||
memset(children, 0, sizeof(children));
|
||||
memset(childMaxVersion, 0, sizeof(childMaxVersion));
|
||||
memset(maxOfMax, 0, sizeof(maxOfMax));
|
||||
bitSet.forEachInRange(
|
||||
[&](int c) {
|
||||
children[c] = other.children[other.index[c]];
|
||||
assert(children[c].child->parent == &other);
|
||||
children[c].child->parent = this;
|
||||
childMaxVersion[c] = other.childMaxVersion[other.index[c]];
|
||||
assert(children[c]->parent == &other);
|
||||
children[c]->parent = this;
|
||||
maxOfMax[c >> Node256::kMaxOfMaxShift] = std::max(
|
||||
maxOfMax[c >> Node256::kMaxOfMaxShift], childMaxVersion[c]);
|
||||
},
|
||||
0, 256);
|
||||
memcpy(partialKey(), &other + 1, partialKeyLen);
|
||||
@@ -473,14 +513,17 @@ inline void Node256::copyChildrenAndKeyFrom(const Node256 &other) {
|
||||
memcpy((char *)this + kNodeCopyBegin, (char *)&other + kNodeCopyBegin,
|
||||
kNodeCopySize);
|
||||
memset(children, 0, sizeof(children));
|
||||
memset(childMaxVersion, 0, sizeof(childMaxVersion));
|
||||
bitSet = other.bitSet;
|
||||
bitSet.forEachInRange(
|
||||
[&](int c) {
|
||||
children[c] = other.children[c];
|
||||
assert(children[c].child->parent == &other);
|
||||
children[c].child->parent = this;
|
||||
childMaxVersion[c] = other.childMaxVersion[c];
|
||||
assert(children[c]->parent == &other);
|
||||
children[c]->parent = this;
|
||||
},
|
||||
0, 256);
|
||||
memcpy(maxOfMax, other.maxOfMax, sizeof(maxOfMax));
|
||||
memcpy(partialKey(), &other + 1, partialKeyLen);
|
||||
}
|
||||
|
||||
@@ -532,7 +575,7 @@ template <class T> struct BoundedFreeListAllocator {
|
||||
static_assert(std::derived_from<T, Node>);
|
||||
static_assert(std::is_trivial_v<T>);
|
||||
|
||||
T *allocate(int partialKeyCapacity) {
|
||||
T *allocate_helper(int partialKeyCapacity) {
|
||||
if (freeList != nullptr) {
|
||||
T *n = (T *)freeList;
|
||||
VALGRIND_MAKE_MEM_DEFINED(freeList, sizeof(freeList));
|
||||
@@ -560,6 +603,18 @@ template <class T> struct BoundedFreeListAllocator {
|
||||
return result;
|
||||
}
|
||||
|
||||
T *allocate(int partialKeyCapacity) {
|
||||
T *result = allocate_helper(partialKeyCapacity);
|
||||
if constexpr (!std::is_same_v<T, Node0>) {
|
||||
memset(result->children, 0, sizeof(result->children));
|
||||
memset(result->childMaxVersion, 0, sizeof(result->childMaxVersion));
|
||||
}
|
||||
if constexpr (std::is_same_v<T, Node48> || std::is_same_v<T, Node256>) {
|
||||
memset(result->maxOfMax, 0, sizeof(result->maxOfMax));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void release(T *p) {
|
||||
if (freeListBytes >= kFreeListMaxMemory) {
|
||||
removeNode(p);
|
||||
@@ -711,29 +766,30 @@ Node *&getChildExists(Node *self, uint8_t index) {
|
||||
__builtin_unreachable(); // GCOVR_EXCL_LINE
|
||||
case Type_Node3: {
|
||||
auto *self3 = static_cast<Node3 *>(self);
|
||||
return self3->children[getNodeIndex(self3, index)].child;
|
||||
return self3->children[getNodeIndex(self3, index)];
|
||||
}
|
||||
case Type_Node16: {
|
||||
auto *self16 = static_cast<Node16 *>(self);
|
||||
return self16->children[getNodeIndex(self16, index)].child;
|
||||
return self16->children[getNodeIndex(self16, index)];
|
||||
}
|
||||
case Type_Node48: {
|
||||
auto *self48 = static_cast<Node48 *>(self);
|
||||
assert(self48->bitSet.test(index));
|
||||
return self48->children[self48->index[index]].child;
|
||||
return self48->children[self48->index[index]];
|
||||
}
|
||||
case Type_Node256: {
|
||||
auto *self256 = static_cast<Node256 *>(self);
|
||||
assert(self256->bitSet.test(index));
|
||||
return self256->children[index].child;
|
||||
return self256->children[index];
|
||||
}
|
||||
default: // GCOVR_EXCL_LINE
|
||||
__builtin_unreachable(); // GCOVR_EXCL_LINE
|
||||
}
|
||||
}
|
||||
|
||||
// Precondition - an entry for index must exist in the node
|
||||
int64_t &maxVersion(Node *n, ConflictSet::Impl *);
|
||||
int64_t maxVersion(Node *n, ConflictSet::Impl *);
|
||||
|
||||
void setMaxVersion(Node *n, ConflictSet::Impl *, int64_t maxVersion);
|
||||
|
||||
Node *&getInTree(Node *n, ConflictSet::Impl *);
|
||||
|
||||
@@ -744,21 +800,21 @@ Node *getChild(Node *self, uint8_t index) {
|
||||
case Type_Node3: {
|
||||
auto *self3 = static_cast<Node3 *>(self);
|
||||
int i = getNodeIndex(self3, index);
|
||||
return i < 0 ? nullptr : self3->children[i].child;
|
||||
return i < 0 ? nullptr : self3->children[i];
|
||||
}
|
||||
case Type_Node16: {
|
||||
auto *self16 = static_cast<Node16 *>(self);
|
||||
int i = getNodeIndex(self16, index);
|
||||
return i < 0 ? nullptr : self16->children[i].child;
|
||||
return i < 0 ? nullptr : self16->children[i];
|
||||
}
|
||||
case Type_Node48: {
|
||||
auto *self48 = static_cast<Node48 *>(self);
|
||||
int i = self48->index[index];
|
||||
return i < 0 ? nullptr : self48->children[i].child;
|
||||
return i < 0 ? nullptr : self48->children[i];
|
||||
}
|
||||
case Type_Node256: {
|
||||
auto *self256 = static_cast<Node256 *>(self);
|
||||
return self256->children[index].child;
|
||||
return self256->children[index];
|
||||
}
|
||||
default: // GCOVR_EXCL_LINE
|
||||
__builtin_unreachable(); // GCOVR_EXCL_LINE
|
||||
@@ -874,26 +930,26 @@ Node *&getOrCreateChild(Node *&self, uint8_t index,
|
||||
auto *self3 = static_cast<Node3 *>(self);
|
||||
int i = getNodeIndex(self3, index);
|
||||
if (i >= 0) {
|
||||
return self3->children[i].child;
|
||||
return self3->children[i];
|
||||
}
|
||||
} break;
|
||||
case Type_Node16: {
|
||||
auto *self16 = static_cast<Node16 *>(self);
|
||||
int i = getNodeIndex(self16, index);
|
||||
if (i >= 0) {
|
||||
return self16->children[i].child;
|
||||
return self16->children[i];
|
||||
}
|
||||
} break;
|
||||
case Type_Node48: {
|
||||
auto *self48 = static_cast<Node48 *>(self);
|
||||
int secondIndex = self48->index[index];
|
||||
if (secondIndex >= 0) {
|
||||
return self48->children[secondIndex].child;
|
||||
return self48->children[secondIndex];
|
||||
}
|
||||
} break;
|
||||
case Type_Node256: {
|
||||
auto *self256 = static_cast<Node256 *>(self);
|
||||
if (auto &result = self256->children[index].child; result != nullptr) {
|
||||
if (auto &result = self256->children[index]; result != nullptr) {
|
||||
return result;
|
||||
}
|
||||
} break;
|
||||
@@ -931,12 +987,16 @@ Node *&getOrCreateChild(Node *&self, uint8_t index,
|
||||
memmove(self3->index + i + 1, self3->index + i,
|
||||
self->numChildren - (i + 1));
|
||||
memmove(self3->children + i + 1, self3->children + i,
|
||||
(self->numChildren - (i + 1)) * sizeof(Child));
|
||||
(self->numChildren - (i + 1)) *
|
||||
sizeof(self3->children[0])); // NOLINT
|
||||
memmove(self3->childMaxVersion + i + 1, self3->childMaxVersion + i,
|
||||
(self->numChildren - (i + 1)) *
|
||||
sizeof(self3->childMaxVersion[0]));
|
||||
break;
|
||||
}
|
||||
}
|
||||
self3->index[i] = index;
|
||||
auto &result = self3->children[i].child;
|
||||
auto &result = self3->children[i];
|
||||
result = nullptr;
|
||||
return result;
|
||||
}
|
||||
@@ -968,7 +1028,10 @@ Node *&getOrCreateChild(Node *&self, uint8_t index,
|
||||
memmove(self16->index + i + 1, self16->index + i,
|
||||
self->numChildren - (i + 1));
|
||||
memmove(self16->children + i + 1, self16->children + i,
|
||||
(self->numChildren - (i + 1)) * sizeof(Child));
|
||||
(self->numChildren - (i + 1)) * sizeof(self16->children[0]));
|
||||
memmove(self16->childMaxVersion + i + 1, self16->childMaxVersion + i,
|
||||
(self->numChildren - (i + 1)) *
|
||||
sizeof(self16->childMaxVersion[0]));
|
||||
}
|
||||
#elif defined(HAS_ARM_NEON)
|
||||
uint8x16_t indices;
|
||||
@@ -988,7 +1051,11 @@ Node *&getOrCreateChild(Node *&self, uint8_t index,
|
||||
memmove(self16->index + i + 1, self16->index + i,
|
||||
self->numChildren - (i + 1));
|
||||
memmove(self16->children + i + 1, self16->children + i,
|
||||
(self->numChildren - (i + 1)) * sizeof(Child));
|
||||
(self->numChildren - (i + 1)) *
|
||||
sizeof(self16->children[0])); // NOLINT
|
||||
memmove(self16->childMaxVersion + i + 1, self16->childMaxVersion + i,
|
||||
(self->numChildren - (i + 1)) *
|
||||
sizeof(self16->childMaxVersion[0]));
|
||||
}
|
||||
#else
|
||||
int i = 0;
|
||||
@@ -997,13 +1064,16 @@ Node *&getOrCreateChild(Node *&self, uint8_t index,
|
||||
memmove(self16->index + i + 1, self16->index + i,
|
||||
self->numChildren - (i + 1));
|
||||
memmove(self16->children + i + 1, self16->children + i,
|
||||
(self->numChildren - (i + 1)) * sizeof(Child));
|
||||
(self->numChildren - (i + 1)) * sizeof(self16->children[0]));
|
||||
memmove(self16->childMaxVersion + i + 1, self16->childMaxVersion + i,
|
||||
(self->numChildren - (i + 1)) *
|
||||
sizeof(self16->childMaxVersion[0]));
|
||||
break;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
self16->index[i] = index;
|
||||
auto &result = self16->children[i].child;
|
||||
auto &result = self16->children[i];
|
||||
result = nullptr;
|
||||
return result;
|
||||
}
|
||||
@@ -1025,7 +1095,8 @@ Node *&getOrCreateChild(Node *&self, uint8_t index,
|
||||
assert(self48->nextFree < 48);
|
||||
int nextFree = self48->nextFree++;
|
||||
self48->index[index] = nextFree;
|
||||
auto &result = self48->children[nextFree].child;
|
||||
self48->reverseIndex[nextFree] = index;
|
||||
auto &result = self48->children[nextFree];
|
||||
result = nullptr;
|
||||
return result;
|
||||
}
|
||||
@@ -1035,7 +1106,7 @@ Node *&getOrCreateChild(Node *&self, uint8_t index,
|
||||
auto *self256 = static_cast<Node256 *>(self);
|
||||
++self->numChildren;
|
||||
self256->bitSet.set(index);
|
||||
return self256->children[index].child;
|
||||
return self256->children[index];
|
||||
}
|
||||
default: // GCOVR_EXCL_LINE
|
||||
__builtin_unreachable(); // GCOVR_EXCL_LINE
|
||||
@@ -1178,7 +1249,7 @@ void maybeDownsize(Node *self, NodeAllocators *allocators,
|
||||
getInTree(self, impl) = newSelf;
|
||||
allocators->node3.release(self3);
|
||||
} else if (self->numChildren == 1 && !self->entryPresent) {
|
||||
auto *child = self3->children[0].child;
|
||||
auto *child = self3->children[0];
|
||||
int minCapacity = self3->partialKeyLen + 1 + child->partialKeyLen;
|
||||
|
||||
if (minCapacity > child->getCapacity()) {
|
||||
@@ -1210,7 +1281,7 @@ void maybeDownsize(Node *self, NodeAllocators *allocators,
|
||||
|
||||
// Max versions are stored in the parent, so we need to update it now
|
||||
// that we have a new parent.
|
||||
maxVersion(child, impl) = childMaxVersion;
|
||||
setMaxVersion(child, impl, childMaxVersion);
|
||||
|
||||
getInTree(self, impl) = child;
|
||||
allocators->node3.release(self3);
|
||||
@@ -1291,7 +1362,11 @@ Node *erase(Node *self, NodeAllocators *allocators, ConflictSet::Impl *impl,
|
||||
sizeof(parent3->index[0]) *
|
||||
(parent->numChildren - (nodeIndex + 1)));
|
||||
memmove(parent3->children + nodeIndex, parent3->children + nodeIndex + 1,
|
||||
sizeof(parent3->children[0]) *
|
||||
sizeof(parent3->children[0]) * // NOLINT
|
||||
(parent->numChildren - (nodeIndex + 1)));
|
||||
memmove(parent3->childMaxVersion + nodeIndex,
|
||||
parent3->childMaxVersion + nodeIndex + 1,
|
||||
sizeof(parent3->childMaxVersion[0]) *
|
||||
(parent->numChildren - (nodeIndex + 1)));
|
||||
|
||||
--parent->numChildren;
|
||||
@@ -1305,7 +1380,11 @@ Node *erase(Node *self, NodeAllocators *allocators, ConflictSet::Impl *impl,
|
||||
sizeof(parent16->index[0]) *
|
||||
(parent->numChildren - (nodeIndex + 1)));
|
||||
memmove(parent16->children + nodeIndex, parent16->children + nodeIndex + 1,
|
||||
sizeof(parent16->children[0]) *
|
||||
sizeof(parent16->children[0]) * // NOLINT
|
||||
(parent->numChildren - (nodeIndex + 1)));
|
||||
memmove(parent16->childMaxVersion + nodeIndex,
|
||||
parent16->childMaxVersion + nodeIndex + 1,
|
||||
sizeof(parent16->childMaxVersion[0]) *
|
||||
(parent->numChildren - (nodeIndex + 1)));
|
||||
|
||||
--parent->numChildren;
|
||||
@@ -1324,9 +1403,18 @@ Node *erase(Node *self, NodeAllocators *allocators, ConflictSet::Impl *impl,
|
||||
if (toRemoveChildrenIndex != lastChildrenIndex) {
|
||||
parent48->children[toRemoveChildrenIndex] =
|
||||
parent48->children[lastChildrenIndex];
|
||||
parent48->index[parent48->children[toRemoveChildrenIndex]
|
||||
.child->parentsIndex] = toRemoveChildrenIndex;
|
||||
parent48->childMaxVersion[toRemoveChildrenIndex] =
|
||||
parent48->childMaxVersion[lastChildrenIndex];
|
||||
parent48->maxOfMax[toRemoveChildrenIndex >> Node48::kMaxOfMaxShift] =
|
||||
std::max(parent48->maxOfMax[toRemoveChildrenIndex >>
|
||||
Node48::kMaxOfMaxShift],
|
||||
parent48->childMaxVersion[toRemoveChildrenIndex]);
|
||||
auto parentIndex =
|
||||
parent48->children[toRemoveChildrenIndex]->parentsIndex;
|
||||
parent48->index[parentIndex] = toRemoveChildrenIndex;
|
||||
parent48->reverseIndex[toRemoveChildrenIndex] = parentIndex;
|
||||
}
|
||||
parent48->childMaxVersion[lastChildrenIndex] = 0;
|
||||
|
||||
--parent->numChildren;
|
||||
|
||||
@@ -1336,7 +1424,7 @@ Node *erase(Node *self, NodeAllocators *allocators, ConflictSet::Impl *impl,
|
||||
case Type_Node256: {
|
||||
auto *parent256 = static_cast<Node256 *>(parent);
|
||||
parent256->bitSet.reset(parentsIndex);
|
||||
parent256->children[parentsIndex].child = nullptr;
|
||||
parent256->children[parentsIndex] = nullptr;
|
||||
|
||||
--parent->numChildren;
|
||||
|
||||
@@ -1621,75 +1709,183 @@ downLeftSpine:
|
||||
}
|
||||
}
|
||||
|
||||
// Return the max version among all keys starting with the search path of n +
|
||||
// [child], where child in (begin, end). Does not account for the range version
|
||||
// of firstGt(searchpath(n) + [end - 1])
|
||||
int64_t maxBetweenExclusive(Node *n, int begin, int end) {
|
||||
// Returns true if all in-bounds vs are <= readVersion
|
||||
bool scan16(const int64_t *vs, const uint8_t *is, int begin, int end,
|
||||
int64_t readVersion) {
|
||||
|
||||
#ifdef HAS_ARM_NEON
|
||||
|
||||
assert(end - begin < 256);
|
||||
uint8x16_t indices;
|
||||
memcpy(&indices, is, 16);
|
||||
// 0xff for each in bounds
|
||||
auto results =
|
||||
vcltq_u8(vsubq_u8(indices, vdupq_n_u8(begin)), vdupq_n_u8(end - begin));
|
||||
// 0xf for each 0xff
|
||||
uint64_t mask = vget_lane_u64(
|
||||
vreinterpret_u64_u8(vshrn_n_u16(vreinterpretq_u16_u8(results), 4)), 0);
|
||||
|
||||
uint64_t compared = 0;
|
||||
for (int i = 0; i < 16; ++i) {
|
||||
compared |= (uint64_t(vs[i] > readVersion) << (i << 2));
|
||||
}
|
||||
return !(compared & mask);
|
||||
|
||||
#else
|
||||
|
||||
const unsigned shiftUpperBound = end - begin;
|
||||
const unsigned shiftAmount = begin;
|
||||
auto inBounds = [&](unsigned c) { return c - shiftAmount < shiftUpperBound; };
|
||||
|
||||
uint32_t compared = 0;
|
||||
for (int i = 0; i < 16; ++i) {
|
||||
compared |= ((vs[i] > readVersion) << i);
|
||||
}
|
||||
uint32_t mask = 0;
|
||||
for (int i = 0; i < 16; ++i) {
|
||||
mask |= inBounds(is[i]) << i;
|
||||
}
|
||||
return !(compared & mask);
|
||||
|
||||
#endif
|
||||
}
|
||||
|
||||
// Return whether or not the max version among all keys starting with the search
|
||||
// path of n + [child], where child in (begin, end) is <= readVersion. Does not
|
||||
// account for the range version of firstGt(searchpath(n) + [end - 1])
|
||||
bool checkMaxBetweenExclusive(Node *n, int begin, int end,
|
||||
int64_t readVersion) {
|
||||
assume(-1 <= begin);
|
||||
assume(begin <= 256);
|
||||
assume(-1 <= end);
|
||||
assume(end <= 256);
|
||||
assume(begin < end);
|
||||
int64_t result = std::numeric_limits<int64_t>::lowest();
|
||||
|
||||
assert(!(begin == -1 && end == 256));
|
||||
|
||||
{
|
||||
int c = getChildGeq(n, begin + 1);
|
||||
if (c >= 0 && c < end) {
|
||||
auto *child = getChildExists(n, c);
|
||||
if (child->entryPresent) {
|
||||
result = std::max(result, child->entry.rangeVersion);
|
||||
if (!(child->entry.rangeVersion <= readVersion)) {
|
||||
return false;
|
||||
};
|
||||
}
|
||||
begin = c;
|
||||
} else {
|
||||
return result;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// [begin, end) is now the half-open interval of children we're interested in.
|
||||
assert(begin < end);
|
||||
|
||||
const unsigned shiftUpperBound = end - begin;
|
||||
const unsigned shiftAmount = begin;
|
||||
auto inBounds = [&](unsigned c) { return c - shiftAmount < shiftUpperBound; };
|
||||
|
||||
switch (n->getType()) {
|
||||
case Type_Node0: // GCOVR_EXCL_LINE
|
||||
// We would have returned above, after not finding a child
|
||||
__builtin_unreachable(); // GCOVR_EXCL_LINE
|
||||
case Type_Node3: {
|
||||
auto *self = static_cast<Node3 *>(n);
|
||||
for (int i = 0; i < self->numChildren && self->index[i] < end; ++i) {
|
||||
if (begin <= self->index[i]) {
|
||||
result = std::max(result, self->children[i].childMaxVersion);
|
||||
}
|
||||
bool result = true;
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
result &= !((self->childMaxVersion[i] > readVersion) &
|
||||
inBounds(self->index[i]));
|
||||
}
|
||||
return result;
|
||||
} break;
|
||||
case Type_Node16: {
|
||||
auto *self = static_cast<Node16 *>(n);
|
||||
for (int i = 0; i < self->numChildren && self->index[i] < end; ++i) {
|
||||
if (begin <= self->index[i]) {
|
||||
result = std::max(result, self->children[i].childMaxVersion);
|
||||
}
|
||||
}
|
||||
|
||||
return scan16(self->childMaxVersion, self->index, begin, end, readVersion);
|
||||
|
||||
} break;
|
||||
case Type_Node48: {
|
||||
auto *self = static_cast<Node48 *>(n);
|
||||
self->bitSet.forEachInRange(
|
||||
[&](int i) {
|
||||
result =
|
||||
std::max(result, self->children[self->index[i]].childMaxVersion);
|
||||
},
|
||||
begin, end);
|
||||
break;
|
||||
// Check all pages
|
||||
static_assert(Node48::kMaxOfMaxPageSize == 16);
|
||||
for (int i = 0; i < Node48::kMaxOfMaxTotalPages; ++i) {
|
||||
if (self->maxOfMax[i] > readVersion) {
|
||||
if (!scan16(self->childMaxVersion + (i << Node48::kMaxOfMaxShift),
|
||||
self->reverseIndex + (i << Node48::kMaxOfMaxShift), begin,
|
||||
end, readVersion)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
case Type_Node256: {
|
||||
auto *self = static_cast<Node256 *>(n);
|
||||
self->bitSet.forEachInRange(
|
||||
[&](int i) {
|
||||
result = std::max(result, self->children[i].childMaxVersion);
|
||||
},
|
||||
begin, end);
|
||||
break;
|
||||
if (end <= 0) {
|
||||
return true;
|
||||
}
|
||||
const int firstPage = begin >> Node256::kMaxOfMaxShift;
|
||||
const int lastPage = (end - 1) >> Node256::kMaxOfMaxShift;
|
||||
if (firstPage == lastPage) {
|
||||
if (self->maxOfMax[firstPage] <= readVersion) {
|
||||
return true;
|
||||
}
|
||||
uint64_t conflict = 0;
|
||||
// Check all in page
|
||||
for (int i = 0; i < Node256::kMaxOfMaxPageSize; ++i) {
|
||||
conflict |=
|
||||
(self->childMaxVersion[(firstPage << Node256::kMaxOfMaxShift) + i] >
|
||||
readVersion)
|
||||
<< i;
|
||||
}
|
||||
// Mask away out of bounds
|
||||
const int intraPageBegin = begin & (Node256::kMaxOfMaxPageSize - 1);
|
||||
const int intraPageEnd = end - (lastPage << Node256::kMaxOfMaxShift);
|
||||
conflict &= (1 << intraPageEnd) - 1;
|
||||
conflict >>= intraPageBegin;
|
||||
return !conflict;
|
||||
}
|
||||
// Check the first page
|
||||
if (self->maxOfMax[firstPage] > readVersion) {
|
||||
uint64_t conflict = 0;
|
||||
for (int i = 0; i < Node256::kMaxOfMaxPageSize; ++i) {
|
||||
int j = (firstPage << Node256::kMaxOfMaxShift) + i;
|
||||
conflict |= (self->childMaxVersion[j] > readVersion) << i;
|
||||
}
|
||||
const int intraPageBegin = begin & (Node256::kMaxOfMaxPageSize - 1);
|
||||
conflict >>= intraPageBegin;
|
||||
if (conflict) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// Check the last page
|
||||
if (self->maxOfMax[lastPage] > readVersion) {
|
||||
uint64_t conflict = 0;
|
||||
for (int i = 0; i < Node256::kMaxOfMaxPageSize; ++i) {
|
||||
int j = (lastPage << Node256::kMaxOfMaxShift) + i;
|
||||
conflict |= (self->childMaxVersion[j] > readVersion) << i;
|
||||
}
|
||||
const int intraPageEnd = end - (lastPage << Node256::kMaxOfMaxShift);
|
||||
conflict &= (1 << intraPageEnd) - 1;
|
||||
if (conflict) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
uint64_t conflict = 0;
|
||||
// Check all possible inner pages
|
||||
for (int i = 1; i < Node256::kMaxOfMaxTotalPages - 1; ++i) {
|
||||
conflict |= (self->maxOfMax[i] > readVersion) << i;
|
||||
}
|
||||
// Only keep inner pages
|
||||
const int innerPageBegin = (begin >> Node256::kMaxOfMaxShift) + 1;
|
||||
const int innerPageEnd = (end - 1) >> Node256::kMaxOfMaxShift;
|
||||
conflict &= (1 << innerPageEnd) - 1;
|
||||
conflict >>= innerPageBegin;
|
||||
return !conflict;
|
||||
}
|
||||
default: // GCOVR_EXCL_LINE
|
||||
__builtin_unreachable(); // GCOVR_EXCL_LINE
|
||||
}
|
||||
#if DEBUG_VERBOSE && !defined(NDEBUG)
|
||||
fprintf(stderr, "At `%s', max version in (%02x, %02x) is %" PRId64 "\n",
|
||||
getSearchPathPrintable(n).c_str(), begin, end, result);
|
||||
#endif
|
||||
return result;
|
||||
}
|
||||
|
||||
Vector<uint8_t> getSearchPath(Arena &arena, Node *n) {
|
||||
@@ -1722,7 +1918,7 @@ bool checkRangeStartsWith(Node *n, std::span<const uint8_t> key, int begin,
|
||||
#endif
|
||||
auto remaining = key;
|
||||
if (remaining.size() == 0) {
|
||||
return maxBetweenExclusive(n, begin, end) <= readVersion;
|
||||
return checkMaxBetweenExclusive(n, begin, end, readVersion);
|
||||
}
|
||||
|
||||
auto *child = getChild(n, remaining[0]);
|
||||
@@ -1821,7 +2017,7 @@ struct CheckRangeLeftSide {
|
||||
}
|
||||
|
||||
if (searchPathLen >= prefixLen) {
|
||||
if (maxBetweenExclusive(n, remaining[0], 256) > readVersion) {
|
||||
if (!checkMaxBetweenExclusive(n, remaining[0], 256, readVersion)) {
|
||||
ok = false;
|
||||
return true;
|
||||
}
|
||||
@@ -1963,7 +2159,7 @@ struct CheckRangeRightSide {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (maxBetweenExclusive(n, -1, remaining[0]) > readVersion) {
|
||||
if (!checkMaxBetweenExclusive(n, -1, remaining[0], readVersion)) {
|
||||
ok = false;
|
||||
return true;
|
||||
}
|
||||
@@ -2174,7 +2370,7 @@ template <bool kBegin>
|
||||
allocators) = old;
|
||||
old->parent = *self;
|
||||
old->parentsIndex = old->partialKey()[partialKeyIndex];
|
||||
maxVersion(old, impl) = oldMaxVersion;
|
||||
setMaxVersion(old, impl, oldMaxVersion);
|
||||
|
||||
memmove(old->partialKey(), old->partialKey() + partialKeyIndex + 1,
|
||||
old->partialKeyLen - (partialKeyIndex + 1));
|
||||
@@ -2198,9 +2394,8 @@ template <bool kBegin>
|
||||
}
|
||||
|
||||
if constexpr (kBegin) {
|
||||
auto &m = maxVersion(*self, impl);
|
||||
assert(writeVersion >= m);
|
||||
m = writeVersion;
|
||||
assert(maxVersion(*self, impl) <= writeVersion);
|
||||
setMaxVersion(*self, impl, writeVersion);
|
||||
}
|
||||
|
||||
if (key.size() == 0) {
|
||||
@@ -2208,9 +2403,8 @@ template <bool kBegin>
|
||||
}
|
||||
|
||||
if constexpr (!kBegin) {
|
||||
auto &m = maxVersion(*self, impl);
|
||||
assert(writeVersion >= m);
|
||||
m = writeVersion;
|
||||
assert(maxVersion(*self, impl) <= writeVersion);
|
||||
setMaxVersion(*self, impl, writeVersion);
|
||||
}
|
||||
|
||||
auto &child = getOrCreateChild(*self, key.front(), allocators);
|
||||
@@ -2221,8 +2415,7 @@ template <bool kBegin>
|
||||
child->partialKeyLen = 0;
|
||||
child->parent = *self;
|
||||
child->parentsIndex = key.front();
|
||||
maxVersion(child, impl) =
|
||||
kBegin ? writeVersion : std::numeric_limits<int64_t>::lowest();
|
||||
setMaxVersion(child, impl, kBegin ? writeVersion : 0);
|
||||
}
|
||||
|
||||
self = &child;
|
||||
@@ -2267,7 +2460,7 @@ void addPointWrite(Node *&root, int64_t oldestVersion,
|
||||
n->entryPresent = true;
|
||||
|
||||
n->entry.pointVersion = writeVersion;
|
||||
maxVersion(n, impl) = writeVersion;
|
||||
setMaxVersion(n, impl, writeVersion);
|
||||
n->entry.rangeVersion =
|
||||
p != nullptr ? p->entry.rangeVersion : oldestVersion;
|
||||
} else {
|
||||
@@ -2308,9 +2501,8 @@ void addWriteRange(Node *&root, int64_t oldestVersion,
|
||||
break;
|
||||
}
|
||||
|
||||
auto &m = maxVersion(n, impl);
|
||||
assert(writeVersion >= m);
|
||||
m = writeVersion;
|
||||
assert(maxVersion(n, impl) <= writeVersion);
|
||||
setMaxVersion(n, impl, writeVersion);
|
||||
|
||||
remaining = remaining.subspan(n->partialKeyLen + 1,
|
||||
remaining.size() - (n->partialKeyLen + 1));
|
||||
@@ -2337,11 +2529,10 @@ void addWriteRange(Node *&root, int64_t oldestVersion,
|
||||
beginNode->entry.rangeVersion =
|
||||
p != nullptr ? p->entry.rangeVersion : oldestVersion;
|
||||
beginNode->entry.pointVersion = writeVersion;
|
||||
maxVersion(beginNode, impl) = writeVersion;
|
||||
assert(maxVersion(beginNode, impl) <= writeVersion);
|
||||
setMaxVersion(beginNode, impl, writeVersion);
|
||||
}
|
||||
auto &m = maxVersion(beginNode, impl);
|
||||
assert(writeVersion >= m);
|
||||
m = writeVersion;
|
||||
setMaxVersion(beginNode, impl, writeVersion);
|
||||
assert(writeVersion >= beginNode->entry.pointVersion);
|
||||
beginNode->entry.pointVersion = writeVersion;
|
||||
|
||||
@@ -2356,8 +2547,8 @@ void addWriteRange(Node *&root, int64_t oldestVersion,
|
||||
auto *p = nextLogical(endNode);
|
||||
endNode->entry.pointVersion =
|
||||
p != nullptr ? p->entry.rangeVersion : oldestVersion;
|
||||
auto &m = maxVersion(endNode, impl);
|
||||
m = std::max(m, endNode->entry.pointVersion);
|
||||
auto m = maxVersion(endNode, impl);
|
||||
setMaxVersion(endNode, impl, std::max(m, endNode->entry.pointVersion));
|
||||
}
|
||||
endNode->entry.rangeVersion = writeVersion;
|
||||
|
||||
@@ -2557,8 +2748,7 @@ struct __attribute__((visibility("hidden"))) ConflictSet::Impl {
|
||||
int64_t totalBytes = 0;
|
||||
};
|
||||
|
||||
// Precondition - an entry for index must exist in the node
|
||||
int64_t &maxVersion(Node *n, ConflictSet::Impl *impl) {
|
||||
int64_t maxVersion(Node *n, ConflictSet::Impl *impl) {
|
||||
int index = n->parentsIndex;
|
||||
n = n->parent;
|
||||
if (n == nullptr) {
|
||||
@@ -2570,22 +2760,66 @@ int64_t &maxVersion(Node *n, ConflictSet::Impl *impl) {
|
||||
case Type_Node3: {
|
||||
auto *n3 = static_cast<Node3 *>(n);
|
||||
int i = getNodeIndex(n3, index);
|
||||
return n3->children[i].childMaxVersion;
|
||||
return n3->childMaxVersion[i];
|
||||
}
|
||||
case Type_Node16: {
|
||||
auto *n16 = static_cast<Node16 *>(n);
|
||||
int i = getNodeIndex(n16, index);
|
||||
return n16->children[i].childMaxVersion;
|
||||
return n16->childMaxVersion[i];
|
||||
}
|
||||
case Type_Node48: {
|
||||
auto *n48 = static_cast<Node48 *>(n);
|
||||
assert(n48->bitSet.test(index));
|
||||
return n48->children[n48->index[index]].childMaxVersion;
|
||||
return n48->childMaxVersion[n48->index[index]];
|
||||
}
|
||||
case Type_Node256: {
|
||||
auto *n256 = static_cast<Node256 *>(n);
|
||||
assert(n256->bitSet.test(index));
|
||||
return n256->children[index].childMaxVersion;
|
||||
return n256->childMaxVersion[index];
|
||||
}
|
||||
default: // GCOVR_EXCL_LINE
|
||||
__builtin_unreachable(); // GCOVR_EXCL_LINE
|
||||
}
|
||||
}
|
||||
|
||||
void setMaxVersion(Node *n, ConflictSet::Impl *impl, int64_t newMax) {
|
||||
int index = n->parentsIndex;
|
||||
n = n->parent;
|
||||
if (n == nullptr) {
|
||||
impl->rootMaxVersion = newMax;
|
||||
return;
|
||||
}
|
||||
switch (n->getType()) {
|
||||
case Type_Node0: // GCOVR_EXCL_LINE
|
||||
__builtin_unreachable(); // GCOVR_EXCL_LINE
|
||||
case Type_Node3: {
|
||||
auto *n3 = static_cast<Node3 *>(n);
|
||||
int i = getNodeIndex(n3, index);
|
||||
n3->childMaxVersion[i] = newMax;
|
||||
return;
|
||||
}
|
||||
case Type_Node16: {
|
||||
auto *n16 = static_cast<Node16 *>(n);
|
||||
int i = getNodeIndex(n16, index);
|
||||
n16->childMaxVersion[i] = newMax;
|
||||
return;
|
||||
}
|
||||
case Type_Node48: {
|
||||
auto *n48 = static_cast<Node48 *>(n);
|
||||
assert(n48->bitSet.test(index));
|
||||
int i = n48->index[index];
|
||||
n48->childMaxVersion[i] = newMax;
|
||||
n48->maxOfMax[i >> Node48::kMaxOfMaxShift] =
|
||||
std::max(n48->maxOfMax[i >> Node48::kMaxOfMaxShift], newMax);
|
||||
return;
|
||||
}
|
||||
case Type_Node256: {
|
||||
auto *n256 = static_cast<Node256 *>(n);
|
||||
assert(n256->bitSet.test(index));
|
||||
n256->childMaxVersion[index] = newMax;
|
||||
n256->maxOfMax[index >> Node256::kMaxOfMaxShift] =
|
||||
std::max(n256->maxOfMax[index >> Node256::kMaxOfMaxShift], newMax);
|
||||
return;
|
||||
}
|
||||
default: // GCOVR_EXCL_LINE
|
||||
__builtin_unreachable(); // GCOVR_EXCL_LINE
|
||||
@@ -2847,7 +3081,7 @@ Iterator firstGeq(Node *n, std::string_view key) {
|
||||
[[maybe_unused]] int64_t checkMaxVersion(Node *root, Node *node,
|
||||
int64_t oldestVersion, bool &success,
|
||||
ConflictSet::Impl *impl) {
|
||||
int64_t expected = std::numeric_limits<int64_t>::lowest();
|
||||
int64_t expected = 0;
|
||||
if (node->entryPresent) {
|
||||
expected = std::max(expected, node->entry.pointVersion);
|
||||
}
|
||||
|
||||
@@ -58,27 +58,29 @@ Performance counters:
|
||||
|
||||
## Skip list
|
||||
|
||||
| ns/op | op/s | err% | total | benchmark |
|
||||
| -----: | -----------: | ---: | ----: | :---------------------------------- |
|
||||
| 246.99 | 4,048,700.59 | 0.2% | 0.01 | `point reads` |
|
||||
| 260.16 | 3,843,784.65 | 0.1% | 0.01 | `prefix reads` |
|
||||
| 493.35 | 2,026,953.19 | 0.1% | 0.01 | `range reads` |
|
||||
| 462.05 | 2,164,289.23 | 0.6% | 0.01 | `point writes` |
|
||||
| 448.19 | 2,231,205.25 | 0.9% | 0.01 | `prefix writes` |
|
||||
| 255.83 | 3,908,845.72 | 1.5% | 0.02 | `range writes` |
|
||||
| 582.63 | 1,716,349.02 | 1.3% | 0.01 | `monotonic increasing point writes` |
|
||||
| ns/op | op/s | err% | total | benchmark
|
||||
|--------------------:|--------------------:|--------:|----------:|:----------
|
||||
| 256.89 | 3,892,784.92 | 0.3% | 0.01 | `point reads`
|
||||
| 272.90 | 3,664,395.04 | 0.2% | 0.01 | `prefix reads`
|
||||
| 507.22 | 1,971,549.50 | 0.7% | 0.01 | `range reads`
|
||||
| 452.66 | 2,209,181.91 | 0.5% | 0.01 | `point writes`
|
||||
| 438.09 | 2,282,619.96 | 0.4% | 0.01 | `prefix writes`
|
||||
| 253.33 | 3,947,420.36 | 2.5% | 0.02 | `range writes`
|
||||
| 574.07 | 1,741,936.71 | 0.3% | 0.01 | `monotonic increasing point writes`
|
||||
| 151,562.50 | 6,597.94 | 1.5% | 0.01 | `worst case for radix tree`
|
||||
|
||||
## Radix tree (this implementation)
|
||||
|
||||
| ns/op | op/s | err% | total | benchmark |
|
||||
| -----: | ------------: | ---: | ----: | :---------------------------------- |
|
||||
| 19.42 | 51,483,206.67 | 0.3% | 0.01 | `point reads` |
|
||||
| 58.43 | 17,115,612.57 | 0.1% | 0.01 | `prefix reads` |
|
||||
| 216.09 | 4,627,766.60 | 0.2% | 0.01 | `range reads` |
|
||||
| 28.35 | 35,267,567.72 | 0.2% | 0.01 | `point writes` |
|
||||
| 43.43 | 23,026,226.17 | 0.2% | 0.01 | `prefix writes` |
|
||||
| 50.00 | 20,000,000.00 | 0.0% | 0.01 | `range writes` |
|
||||
| 92.38 | 10,824,863.69 | 4.1% | 0.01 | `monotonic increasing point writes` |
|
||||
| ns/op | op/s | err% | total | benchmark
|
||||
|--------------------:|--------------------:|--------:|----------:|:----------
|
||||
| 19.83 | 50,420,955.28 | 0.1% | 0.01 | `point reads`
|
||||
| 55.95 | 17,872,542.40 | 0.5% | 0.01 | `prefix reads`
|
||||
| 88.28 | 11,327,709.50 | 0.4% | 0.01 | `range reads`
|
||||
| 29.15 | 34,309,531.64 | 0.5% | 0.01 | `point writes`
|
||||
| 42.36 | 23,607,424.27 | 1.1% | 0.01 | `prefix writes`
|
||||
| 50.00 | 20,000,000.00 | 0.0% | 0.01 | `range writes`
|
||||
| 93.52 | 10,692,413.79 | 3.3% | 0.01 | `monotonic increasing point writes`
|
||||
| 2,388,417.00 | 418.69 | 0.4% | 0.03 | `worst case for radix tree`
|
||||
|
||||
# "Real data" test
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
___stack_chk_fail
|
||||
___stack_chk_guard
|
||||
__tlv_bootstrap
|
||||
_abort
|
||||
_bzero
|
||||
|
||||
+3
-1
@@ -115,7 +115,9 @@ class ConflictSet:
|
||||
|
||||
def check(self, *reads: ReadRange) -> list[Result]:
|
||||
r = (ctypes.c_int * len(reads))()
|
||||
self._lib.ConflictSet_check(self.p, *reads, r, 1)
|
||||
self._lib.ConflictSet_check(
|
||||
self.p, (ReadRange * len(reads))(*reads), r, len(reads)
|
||||
)
|
||||
return [Result(x) for x in r]
|
||||
|
||||
def setOldestVersion(self, version: int) -> None:
|
||||
|
||||
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
BIN
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.
BIN
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
After Width: | Height: | Size: 11 B |
BIN
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
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
|
Before Width: | Height: | Size: 1.6 KiB |
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.
BIN
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.
BIN
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.
BIN
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
Before Width: | Height: | Size: 2.5 KiB |
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.
BIN
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