28 Commits

Author SHA1 Message Date
cce7d29410 Update our benchmarks in README
Some checks failed
Tests / Clang total: 1130, passed: 1130
Clang |Total|New|Outstanding|Fixed|Trend |:-:|:-:|:-:|:-:|:-: |0|0|0|0|:clap:
Tests / SIMD fallback total: 1130, passed: 1130
Tests / Release [gcc] total: 1130, passed: 1130
GNU C Compiler (gcc) |Total|New|Outstanding|Fixed|Trend |:-:|:-:|:-:|:-:|:-: |0|0|0|0|:clap:
Tests / Release [gcc,aarch64] total: 844, passed: 844
Tests / Coverage total: 848, passed: 848
weaselab/conflict-set/pipeline/head There was a failure building this commit
2024-06-26 20:59:33 -07:00
13f8d3fa8a Add benchmarks for individual spans, but commented out 2024-06-26 20:57:51 -07:00
02866a8cae Save some bounds checking for scanning Node256 2024-06-26 20:55:18 -07:00
fa86d3e707 "max of max" for Node48 again, but physical instead of logical 2024-06-26 20:41:27 -07:00
7d1d1d7b2a Maintain childMaxVersion == 0 for unused children in Node48 2024-06-26 20:16:50 -07:00
789ecc29b3 Use unsigned compare trick to check in bounds 2024-06-26 19:41:25 -07:00
08f2998a85 Use 8 byte pages for "max of max"
This seems to benchmark better
2024-06-26 19:18:38 -07:00
c882d7663d Maintain "reverseIndex" in Node48 2024-06-26 19:11:34 -07:00
bfea4384ba Branchless inner page check for Node256 2024-06-26 18:28:41 -07:00
6520e3d734 "max of max" for Node48 2024-06-26 17:54:03 -07:00
23ace8aac5 Fill in leftward on right side in worst case for radix tree bench 2024-06-26 17:37:24 -07:00
62e35de320 Update our benchmark in readme 2024-06-26 16:36:02 -07:00
22e4ab01a1 Track "max of max" versions in Node256 2024-06-26 16:28:24 -07:00
b3aeed0caa Warning: interface change! Require versions >= 0 2024-06-26 15:46:36 -07:00
5f3833e965 Change maxVersion to return by value, and add setMaxVersion 2024-06-26 15:33:15 -07:00
8b1cd9c052 Minor improvements to checkMaxBetweenExclusive 2024-06-26 15:06:50 -07:00
bb9bc3d7b5 Measure across different cardinalities for radix worst case bench 2024-06-26 15:06:36 -07:00
89b3354a80 Update README with new benchmark
All checks were successful
Tests / Clang total: 1130, passed: 1130
Clang |Total|New|Outstanding|Fixed|Trend |:-:|:-:|:-:|:-:|:-: |0|0|0|0|:clap:
Tests / SIMD fallback total: 1130, passed: 1130
Tests / Release [gcc] total: 1130, passed: 1130
GNU C Compiler (gcc) |Total|New|Outstanding|Fixed|Trend |:-:|:-:|:-:|:-:|:-: |0|0|0|0|:clap:
Tests / Release [gcc,aarch64] total: 844, passed: 844
Tests / Coverage total: 848, passed: 848
weaselab/conflict-set/pipeline/head This commit looks good
2024-06-25 21:47:46 -07:00
488c723726 Improve worst-case radix tree checkRangeRead 2024-06-25 21:22:55 -07:00
76d0785b33 Add worst-case benchmark for radix tree
Closes #27
2024-06-25 20:50:22 -07:00
add0af11ad Don't check paper/version.txt into version control
Some checks reported errors
weaselab/conflict-set/pipeline/head Something is wrong with the build of this commit
But also don't remove it in `make -C paper clean`
2024-06-25 19:21:14 -07:00
2c0adf4a8b Fix test-only bug in script test
Previously conflict_set.py only worked for checking one read conflict
per call
2024-06-25 19:20:19 -07:00
e8ac78cce6 Bump version
All checks were successful
Tests / Clang total: 1130, passed: 1130
Clang |Total|New|Outstanding|Fixed|Trend |:-:|:-:|:-:|:-:|:-: |0|0|0|0|:clap:
Tests / SIMD fallback total: 1130, passed: 1130
Tests / Release [gcc] total: 1130, passed: 1130
GNU C Compiler (gcc) |Total|New|Outstanding|Fixed|Trend |:-:|:-:|:-:|:-:|:-: |0|0|0|0|:clap:
Tests / Release [gcc,aarch64] total: 844, passed: 844
Tests / Coverage total: 848, passed: 848
weaselab/conflict-set/pipeline/head This commit looks good
2024-06-12 14:07:34 -07:00
13d447c9fe Use version.txt instead of version.tex
All checks were successful
Tests / Clang total: 1130, passed: 1130
Clang |Total|New|Outstanding|Fixed|Trend |:-:|:-:|:-:|:-:|:-: |0|0|0|0|:clap:
Tests / SIMD fallback total: 1130, passed: 1130
Tests / Release [gcc] total: 1130, passed: 1130
GNU C Compiler (gcc) |Total|New|Outstanding|Fixed|Trend |:-:|:-:|:-:|:-:|:-: |0|0|0|0|:clap:
Tests / Release [gcc,aarch64] total: 844, passed: 844
Tests / Coverage total: 848, passed: 848
weaselab/conflict-set/pipeline/head This commit looks good
latexmk seemed to have some trouble with it being a tex file
2024-06-12 13:47:16 -07:00
da7523c5cf Add version to paper
Some checks failed
Tests / Clang total: 1130, passed: 1130
Clang |Total|New|Outstanding|Fixed|Trend |:-:|:-:|:-:|:-:|:-: |0|0|0|0|:clap:
Tests / SIMD fallback total: 1130, passed: 1130
Tests / Release [gcc] total: 1130, passed: 1130
GNU C Compiler (gcc) |Total|New|Outstanding|Fixed|Trend |:-:|:-:|:-:|:-:|:-: |0|0|0|0|:clap:
weaselab/conflict-set/pipeline/head There was a failure building this commit
2024-06-12 13:34:35 -07:00
a074bc6f72 include(CTest) before BUILD_TESTING
All checks were successful
Tests / Clang total: 1130, passed: 1130
Clang |Total|New|Outstanding|Fixed|Trend |:-:|:-:|:-:|:-:|:-: |0|0|0|0|:clap:
Tests / SIMD fallback total: 1130, passed: 1130
Tests / Release [gcc] total: 1130, passed: 1130
GNU C Compiler (gcc) |Total|New|Outstanding|Fixed|Trend |:-:|:-:|:-:|:-:|:-: |0|0|0|0|:clap:
Tests / Release [gcc,aarch64] total: 844, passed: 844
Tests / Coverage total: 848, passed: 848
weaselab/conflict-set/pipeline/head This commit looks good
2024-06-11 16:21:38 -07:00
1553a44986 Make possible to use from FetchContent
Some checks failed
Tests / Clang total: 0
Clang |Total|New|Outstanding|Fixed|Trend |:-:|:-:|:-:|:-:|:-: |0|0|0|0|:clap:
Tests / SIMD fallback total: 0
Tests / Release [gcc] total: 0
GNU C Compiler (gcc) |Total|New|Outstanding|Fixed|Trend |:-:|:-:|:-:|:-:|:-: |0|0|0|0|:clap:
Tests / Release [gcc,aarch64] total: 0
Tests / Coverage total: 0
weaselab/conflict-set/pipeline/head There was a failure building this commit
2024-06-11 16:12:35 -07:00
859ac352e6 Bump version
All checks were successful
Tests / Clang total: 1130, passed: 1130
Clang |Total|New|Outstanding|Fixed|Trend |:-:|:-:|:-:|:-:|:-: |0|0|0|0|:clap:
Tests / SIMD fallback total: 1130, passed: 1130
Tests / Release [gcc] total: 1130, passed: 1130
GNU C Compiler (gcc) |Total|New|Outstanding|Fixed|Trend |:-:|:-:|:-:|:-:|:-: |0|0|0|0|:clap:
Tests / Release [gcc,aarch64] total: 844, passed: 844
Tests / Coverage total: 848, passed: 848
weaselab/conflict-set/pipeline/head This commit looks good
2024-06-11 13:13:19 -07:00
12 changed files with 376 additions and 130 deletions

105
Bench.cpp
View File

@@ -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();
}

View File

@@ -1,14 +1,16 @@
cmake_minimum_required(VERSION 3.18) cmake_minimum_required(VERSION 3.18)
project( project(
conflict-set conflict-set
VERSION 0.0.5 VERSION 0.0.7
DESCRIPTION DESCRIPTION
"A data structure for optimistic concurrency control on ranges of bitwise-lexicographically-ordered keys." "A data structure for optimistic concurrency control on ranges of bitwise-lexicographically-ordered keys."
HOMEPAGE_URL "https://git.weaselab.dev/weaselab/conflict-set" HOMEPAGE_URL "https://git.weaselab.dev/weaselab/conflict-set"
LANGUAGES C CXX) LANGUAGES C CXX)
set(CMAKE_CXX_STANDARD 20) 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(CMakePushCheckState)
include(CheckCXXCompilerFlag) include(CheckCXXCompilerFlag)
@@ -47,7 +49,8 @@ if(HAS_FULL_RELRO)
endif() endif()
cmake_pop_check_state() 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() cmake_push_check_state()
list(APPEND CMAKE_REQUIRED_LINK_OPTIONS ${version_script_flags}) list(APPEND CMAKE_REQUIRED_LINK_OPTIONS ${version_script_flags})
check_cxx_source_compiles("int main(){}" HAS_VERSION_SCRIPT FAIL_REGEX check_cxx_source_compiles("int main(){}" HAS_VERSION_SCRIPT FAIL_REGEX
@@ -59,7 +62,7 @@ option(USE_SIMD_FALLBACK
# This is encouraged according to # This is encouraged according to
# https://valgrind.org/docs/manual/manual-core-adv.html#manual-core-adv.clientreq # https://valgrind.org/docs/manual/manual-core-adv.html#manual-core-adv.clientreq
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>) 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 target_compile_options(${PROJECT_NAME}-object PRIVATE -fno-exceptions
-fvisibility=hidden) -fvisibility=hidden)
target_include_directories(${PROJECT_NAME}-object 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>) add_library(${PROJECT_NAME} SHARED $<TARGET_OBJECTS:${PROJECT_NAME}-object>)
set_target_properties( set_target_properties(
${PROJECT_NAME} PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${PROJECT_NAME} PROPERTIES LIBRARY_OUTPUT_DIRECTORY
"${CMAKE_BINARY_DIR}/radix_tree") "${CMAKE_CURRENT_BINARY_DIR}/radix_tree")
if(NOT CMAKE_BUILD_TYPE STREQUAL Debug) if(NOT CMAKE_BUILD_TYPE STREQUAL Debug)
set_target_properties(${PROJECT_NAME} PROPERTIES LINKER_LANGUAGE C) set_target_properties(${PROJECT_NAME} PROPERTIES LINKER_LANGUAGE C)
endif() endif()
if(HAS_VERSION_SCRIPT) if(HAS_VERSION_SCRIPT)
target_link_options(${PROJECT_NAME} PRIVATE target_link_options(
LINKER:--version-script=${CMAKE_SOURCE_DIR}/linker.map) ${PROJECT_NAME} PRIVATE
LINKER:--version-script=${CMAKE_CURRENT_SOURCE_DIR}/linker.map)
endif() endif()
add_library(${PROJECT_NAME}-static STATIC add_library(${PROJECT_NAME}-static STATIC
@@ -131,7 +135,7 @@ if(APPLE)
add_custom_command( add_custom_command(
TARGET ${PROJECT_NAME}-static TARGET ${PROJECT_NAME}-static
PRE_LINK PRE_LINK
COMMAND ${CMAKE_SOURCE_DIR}/privatize_symbols_macos.sh COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/privatize_symbols_macos.sh
$<TARGET_OBJECTS:${PROJECT_NAME}-object>) $<TARGET_OBJECTS:${PROJECT_NAME}-object>)
else() else()
add_custom_command( add_custom_command(
@@ -139,21 +143,22 @@ else()
POST_BUILD POST_BUILD
COMMAND COMMAND
${CMAKE_OBJCOPY} ${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 $<TARGET_FILE:${PROJECT_NAME}-static> || echo
"Proceeding with all symbols global in static library") "Proceeding with all symbols global in static library")
endif() endif()
set(TEST_FLAGS -Wall -Wextra -Wunreachable-code -Wpedantic -UNDEBUG)
include(CTest) 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 # corpus tests, which are tests curated by libfuzzer. The goal is to get broad
# coverage with a small number of tests. # 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 # extra testing that relies on shared libraries, which aren't available with
# wasm # wasm
@@ -162,9 +167,11 @@ if(BUILD_TESTING)
add_library(skip_list SHARED SkipList.cpp) add_library(skip_list SHARED SkipList.cpp)
target_compile_options(skip_list PRIVATE -fno-exceptions target_compile_options(skip_list PRIVATE -fno-exceptions
-fvisibility=hidden) -fvisibility=hidden)
target_include_directories(skip_list PUBLIC ${CMAKE_SOURCE_DIR}/include) target_include_directories(skip_list
set_target_properties(skip_list PROPERTIES LIBRARY_OUTPUT_DIRECTORY PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include)
"${CMAKE_BINARY_DIR}/skip_list") 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 OUTPUT_NAME ${PROJECT_NAME})
set_target_properties( set_target_properties(
skip_list PROPERTIES VERSION ${PROJECT_VERSION} SOVERSION skip_list PROPERTIES VERSION ${PROJECT_VERSION} SOVERSION
@@ -175,10 +182,11 @@ if(BUILD_TESTING)
add_library(hash_table SHARED HashTable.cpp) add_library(hash_table SHARED HashTable.cpp)
target_compile_options(hash_table PRIVATE -fno-exceptions target_compile_options(hash_table PRIVATE -fno-exceptions
-fvisibility=hidden) -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( set_target_properties(
hash_table PROPERTIES LIBRARY_OUTPUT_DIRECTORY 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 OUTPUT_NAME ${PROJECT_NAME})
set_target_properties( set_target_properties(
hash_table PROPERTIES VERSION ${PROJECT_VERSION} SOVERSION hash_table PROPERTIES VERSION ${PROJECT_VERSION} SOVERSION
@@ -266,15 +274,19 @@ if(BUILD_TESTING)
set_property( set_property(
DIRECTORY DIRECTORY
APPEND 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( execute_process(
COMMAND ${Python3_EXECUTABLE} ${CMAKE_SOURCE_DIR}/test_conflict_set.py COMMAND ${Python3_EXECUTABLE}
list OUTPUT_VARIABLE SCRIPT_TESTS) ${CMAKE_CURRENT_SOURCE_DIR}/test_conflict_set.py list
OUTPUT_VARIABLE SCRIPT_TESTS)
foreach(TEST ${SCRIPT_TESTS}) foreach(TEST ${SCRIPT_TESTS})
add_test( add_test(
NAME script_test_${TEST} NAME script_test_${TEST}
COMMAND ${Python3_EXECUTABLE} ${CMAKE_SOURCE_DIR}/test_conflict_set.py COMMAND
test ${TEST} --build-dir ${CMAKE_BINARY_DIR}) ${Python3_EXECUTABLE}
${CMAKE_CURRENT_SOURCE_DIR}/test_conflict_set.py test ${TEST}
--build-dir ${CMAKE_CURRENT_BINARY_DIR})
endforeach() endforeach()
endif() endif()
@@ -308,25 +320,26 @@ if(BUILD_TESTING)
# symbol visibility tests # symbol visibility tests
if(NOT WASM AND NOT CMAKE_BUILD_TYPE STREQUAL Debug) if(NOT WASM AND NOT CMAKE_BUILD_TYPE STREQUAL Debug)
if(APPLE) if(APPLE)
set(symbol_exports ${CMAKE_SOURCE_DIR}/apple-symbol-exports.txt) set(symbol_exports ${CMAKE_CURRENT_SOURCE_DIR}/apple-symbol-exports.txt)
set(symbol_imports ${CMAKE_SOURCE_DIR}/apple-symbol-imports.txt) set(symbol_imports ${CMAKE_CURRENT_SOURCE_DIR}/apple-symbol-imports.txt)
else() 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) 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() else()
set(symbol_imports ${CMAKE_SOURCE_DIR}/symbol-imports.txt) set(symbol_imports ${CMAKE_CURRENT_SOURCE_DIR}/symbol-imports.txt)
endif() endif()
endif() endif()
add_test( add_test(
NAME conflict_set_shared_symbols NAME conflict_set_shared_symbols
COMMAND COMMAND
${CMAKE_SOURCE_DIR}/test_symbols.sh $<TARGET_FILE:${PROJECT_NAME}> ${CMAKE_CURRENT_SOURCE_DIR}/test_symbols.sh
${symbol_exports} ${symbol_imports}) $<TARGET_FILE:${PROJECT_NAME}> ${symbol_exports} ${symbol_imports})
add_test( add_test(
NAME conflict_set_static_symbols NAME conflict_set_static_symbols
COMMAND COMMAND
${CMAKE_SOURCE_DIR}/test_symbols.sh ${CMAKE_CURRENT_SOURCE_DIR}/test_symbols.sh
$<TARGET_FILE:${PROJECT_NAME}-static> ${symbol_exports} $<TARGET_FILE:${PROJECT_NAME}-static> ${symbol_exports}
${symbol_imports}) ${symbol_imports})
endif() endif()
@@ -369,13 +382,13 @@ set(CMAKE_OSX_DEPLOYMENT_TARGET 11.0)
if(APPLE) if(APPLE)
find_program(PANDOC_EXE pandoc) find_program(PANDOC_EXE pandoc)
if(PANDOC_EXE) if(PANDOC_EXE)
execute_process(COMMAND ${PANDOC_EXE} ${CMAKE_SOURCE_DIR}/README.md -o execute_process(COMMAND ${PANDOC_EXE} ${CMAKE_CURRENT_SOURCE_DIR}/README.md
${CMAKE_BINARY_DIR}/README.txt) -o ${CMAKE_CURRENT_BINARY_DIR}/README.txt)
set(CPACK_RESOURCE_FILE_README ${CMAKE_BINARY_DIR}/README.txt) set(CPACK_RESOURCE_FILE_README ${CMAKE_CURRENT_BINARY_DIR}/README.txt)
endif() endif()
configure_file(${CMAKE_SOURCE_DIR}/LICENSE ${CMAKE_BINARY_DIR}/LICENSE.txt configure_file(${CMAKE_CURRENT_SOURCE_DIR}/LICENSE
COPYONLY) ${CMAKE_CURRENT_BINARY_DIR}/LICENSE.txt COPYONLY)
set(CPACK_RESOURCE_FILE_LICENSE ${CMAKE_BINARY_DIR}/LICENSE.txt) set(CPACK_RESOURCE_FILE_LICENSE ${CMAKE_CURRENT_BINARY_DIR}/LICENSE.txt)
endif() endif()
include(CPack) include(CPack)

View File

@@ -294,6 +294,12 @@ struct Node48 : Node {
int8_t nextFree; int8_t nextFree;
int8_t index[256]; int8_t index[256];
Child children[kMaxNodes]; Child children[kMaxNodes];
uint8_t reverseIndex[kMaxNodes];
constexpr static int kMaxOfMaxPageSize = 8;
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); } uint8_t *partialKey() { return (uint8_t *)(this + 1); }
@@ -308,6 +314,11 @@ struct Node256 : Node {
constexpr static auto kType = Type_Node256; constexpr static auto kType = Type_Node256;
BitSet bitSet; BitSet bitSet;
Child children[256]; Child children[256];
constexpr static int kMaxOfMaxPageSize = 8;
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); } uint8_t *partialKey() { return (uint8_t *)(this + 1); }
void copyChildrenAndKeyFrom(const Node48 &other); void copyChildrenAndKeyFrom(const Node48 &other);
void copyChildrenAndKeyFrom(const Node256 &other); void copyChildrenAndKeyFrom(const Node256 &other);
@@ -405,6 +416,7 @@ inline void Node48::copyChildrenAndKeyFrom(const Node16 &other) {
kNodeCopySize); kNodeCopySize);
assert(numChildren == Node16::kMaxNodes); assert(numChildren == Node16::kMaxNodes);
memset(index, -1, sizeof(index)); memset(index, -1, sizeof(index));
memset(children, 0, sizeof(children));
memcpy(partialKey(), &other + 1, partialKeyLen); memcpy(partialKey(), &other + 1, partialKeyLen);
bitSet.init(); bitSet.init();
nextFree = Node16::kMaxNodes; nextFree = Node16::kMaxNodes;
@@ -415,6 +427,9 @@ inline void Node48::copyChildrenAndKeyFrom(const Node16 &other) {
children[i] = other.children[i]; children[i] = other.children[i];
assert(children[i].child->parent == &other); assert(children[i].child->parent == &other);
children[i].child->parent = this; children[i].child->parent = this;
reverseIndex[i] = x;
maxOfMax[i >> Node48::kMaxOfMaxShift] = std::max(
maxOfMax[i >> Node48::kMaxOfMaxShift], children[i].childMaxVersion);
++i; ++i;
} }
} }
@@ -422,13 +437,17 @@ inline void Node48::copyChildrenAndKeyFrom(const Node16 &other) {
inline void Node48::copyChildrenAndKeyFrom(const Node48 &other) { inline void Node48::copyChildrenAndKeyFrom(const Node48 &other) {
memcpy((char *)this + kNodeCopyBegin, (char *)&other + kNodeCopyBegin, memcpy((char *)this + kNodeCopyBegin, (char *)&other + kNodeCopyBegin,
kNodeCopySize); kNodeCopySize);
memcpy(&bitSet, &other.bitSet, bitSet = other.bitSet;
sizeof(*this) - sizeof(Node) - sizeof(children)); nextFree = other.nextFree;
memcpy(index, other.index, sizeof(index));
memset(children, 0, sizeof(children));
for (int i = 0; i < numChildren; ++i) { for (int i = 0; i < numChildren; ++i) {
children[i] = other.children[i]; children[i] = other.children[i];
assert(children[i].child->parent == &other); assert(children[i].child->parent == &other);
children[i].child->parent = this; children[i].child->parent = this;
} }
memcpy(reverseIndex, other.reverseIndex, sizeof(reverseIndex));
memcpy(maxOfMax, other.maxOfMax, sizeof(maxOfMax));
memcpy(partialKey(), &other + 1, partialKeyLen); memcpy(partialKey(), &other + 1, partialKeyLen);
} }
@@ -436,6 +455,7 @@ inline void Node48::copyChildrenAndKeyFrom(const Node256 &other) {
memcpy((char *)this + kNodeCopyBegin, (char *)&other + kNodeCopyBegin, memcpy((char *)this + kNodeCopyBegin, (char *)&other + kNodeCopyBegin,
kNodeCopySize); kNodeCopySize);
memset(index, -1, sizeof(index)); memset(index, -1, sizeof(index));
memset(children, 0, sizeof(children));
nextFree = other.numChildren; nextFree = other.numChildren;
bitSet = other.bitSet; bitSet = other.bitSet;
int i = 0; int i = 0;
@@ -448,6 +468,9 @@ inline void Node48::copyChildrenAndKeyFrom(const Node256 &other) {
children[i] = other.children[c]; children[i] = other.children[c];
assert(children[i].child->parent == &other); assert(children[i].child->parent == &other);
children[i].child->parent = this; children[i].child->parent = this;
reverseIndex[i] = c;
maxOfMax[i >> Node48::kMaxOfMaxShift] = std::max(
maxOfMax[i >> Node48::kMaxOfMaxShift], children[i].childMaxVersion);
++i; ++i;
}, },
0, 256); 0, 256);
@@ -457,13 +480,17 @@ inline void Node48::copyChildrenAndKeyFrom(const Node256 &other) {
inline void Node256::copyChildrenAndKeyFrom(const Node48 &other) { inline void Node256::copyChildrenAndKeyFrom(const Node48 &other) {
memcpy((char *)this + kNodeCopyBegin, (char *)&other + kNodeCopyBegin, memcpy((char *)this + kNodeCopyBegin, (char *)&other + kNodeCopyBegin,
kNodeCopySize); kNodeCopySize);
memset(children, 0, sizeof(children));
bitSet = other.bitSet; bitSet = other.bitSet;
memset(children, 0, sizeof(children));
memset(maxOfMax, 0, sizeof(maxOfMax));
bitSet.forEachInRange( bitSet.forEachInRange(
[&](int c) { [&](int c) {
children[c] = other.children[other.index[c]]; children[c] = other.children[other.index[c]];
assert(children[c].child->parent == &other); assert(children[c].child->parent == &other);
children[c].child->parent = this; children[c].child->parent = this;
maxOfMax[c >> Node256::kMaxOfMaxShift] =
std::max(maxOfMax[c >> Node256::kMaxOfMaxShift],
children[c].childMaxVersion);
}, },
0, 256); 0, 256);
memcpy(partialKey(), &other + 1, partialKeyLen); memcpy(partialKey(), &other + 1, partialKeyLen);
@@ -481,6 +508,7 @@ inline void Node256::copyChildrenAndKeyFrom(const Node256 &other) {
children[c].child->parent = this; children[c].child->parent = this;
}, },
0, 256); 0, 256);
memcpy(maxOfMax, other.maxOfMax, sizeof(maxOfMax));
memcpy(partialKey(), &other + 1, partialKeyLen); memcpy(partialKey(), &other + 1, partialKeyLen);
} }
@@ -532,7 +560,7 @@ template <class T> struct BoundedFreeListAllocator {
static_assert(std::derived_from<T, Node>); static_assert(std::derived_from<T, Node>);
static_assert(std::is_trivial_v<T>); static_assert(std::is_trivial_v<T>);
T *allocate(int partialKeyCapacity) { T *allocate_helper(int partialKeyCapacity) {
if (freeList != nullptr) { if (freeList != nullptr) {
T *n = (T *)freeList; T *n = (T *)freeList;
VALGRIND_MAKE_MEM_DEFINED(freeList, sizeof(freeList)); VALGRIND_MAKE_MEM_DEFINED(freeList, sizeof(freeList));
@@ -560,6 +588,17 @@ template <class T> struct BoundedFreeListAllocator {
return result; 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));
}
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) { void release(T *p) {
if (freeListBytes >= kFreeListMaxMemory) { if (freeListBytes >= kFreeListMaxMemory) {
removeNode(p); removeNode(p);
@@ -732,8 +771,9 @@ Node *&getChildExists(Node *self, uint8_t index) {
} }
} }
// 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 *); Node *&getInTree(Node *n, ConflictSet::Impl *);
@@ -1025,6 +1065,7 @@ Node *&getOrCreateChild(Node *&self, uint8_t index,
assert(self48->nextFree < 48); assert(self48->nextFree < 48);
int nextFree = self48->nextFree++; int nextFree = self48->nextFree++;
self48->index[index] = nextFree; self48->index[index] = nextFree;
self48->reverseIndex[nextFree] = index;
auto &result = self48->children[nextFree].child; auto &result = self48->children[nextFree].child;
result = nullptr; result = nullptr;
return result; return result;
@@ -1210,7 +1251,7 @@ void maybeDownsize(Node *self, NodeAllocators *allocators,
// Max versions are stored in the parent, so we need to update it now // Max versions are stored in the parent, so we need to update it now
// that we have a new parent. // that we have a new parent.
maxVersion(child, impl) = childMaxVersion; setMaxVersion(child, impl, childMaxVersion);
getInTree(self, impl) = child; getInTree(self, impl) = child;
allocators->node3.release(self3); allocators->node3.release(self3);
@@ -1324,9 +1365,12 @@ Node *erase(Node *self, NodeAllocators *allocators, ConflictSet::Impl *impl,
if (toRemoveChildrenIndex != lastChildrenIndex) { if (toRemoveChildrenIndex != lastChildrenIndex) {
parent48->children[toRemoveChildrenIndex] = parent48->children[toRemoveChildrenIndex] =
parent48->children[lastChildrenIndex]; parent48->children[lastChildrenIndex];
parent48->index[parent48->children[toRemoveChildrenIndex] auto parentIndex =
.child->parentsIndex] = toRemoveChildrenIndex; parent48->children[toRemoveChildrenIndex].child->parentsIndex;
parent48->index[parentIndex] = toRemoveChildrenIndex;
parent48->reverseIndex[toRemoveChildrenIndex] = parentIndex;
} }
parent48->children[lastChildrenIndex].childMaxVersion = 0;
--parent->numChildren; --parent->numChildren;
@@ -1621,75 +1665,113 @@ downLeftSpine:
} }
} }
// Return the max version among all keys starting with the search path of n + // Return whether or not the max version among all keys starting with the search
// [child], where child in (begin, end). Does not account for the range version // path of n + [child], where child in (begin, end) is <= readVersion. Does not
// of firstGt(searchpath(n) + [end - 1]) // account for the range version of firstGt(searchpath(n) + [end - 1])
int64_t maxBetweenExclusive(Node *n, int begin, int end) { bool checkMaxBetweenExclusive(Node *n, int begin, int end,
int64_t readVersion) {
assume(-1 <= begin); assume(-1 <= begin);
assume(begin <= 256); assume(begin <= 256);
assume(-1 <= end); assume(-1 <= end);
assume(end <= 256); assume(end <= 256);
assume(begin < end); assume(begin < end);
int64_t result = std::numeric_limits<int64_t>::lowest();
{ {
int c = getChildGeq(n, begin + 1); int c = getChildGeq(n, begin + 1);
if (c >= 0 && c < end) { if (c >= 0 && c < end) {
auto *child = getChildExists(n, c); auto *child = getChildExists(n, c);
if (child->entryPresent) { if (child->entryPresent) {
result = std::max(result, child->entry.rangeVersion); if (!(child->entry.rangeVersion <= readVersion)) {
return false;
};
} }
begin = c; begin = c;
} else { } else {
return result; return true;
} }
} }
// [begin, end) is now the half-open interval of children we're interested in.
const unsigned shiftUpperBound = end - begin;
const unsigned shiftAmount = begin;
auto inBounds = [&](unsigned c) { return c - shiftAmount < shiftUpperBound; };
switch (n->getType()) { switch (n->getType()) {
case Type_Node0: // GCOVR_EXCL_LINE case Type_Node0: // GCOVR_EXCL_LINE
// We would have returned above, after not finding a child // We would have returned above, after not finding a child
__builtin_unreachable(); // GCOVR_EXCL_LINE __builtin_unreachable(); // GCOVR_EXCL_LINE
case Type_Node3: { case Type_Node3: {
auto *self = static_cast<Node3 *>(n); auto *self = static_cast<Node3 *>(n);
for (int i = 0; i < self->numChildren && self->index[i] < end; ++i) { bool result = true;
if (begin <= self->index[i]) { for (int i = 0; i < 3; ++i) {
result = std::max(result, self->children[i].childMaxVersion); result &= !((self->children[i].childMaxVersion > readVersion) &
} inBounds(self->index[i]));
} }
return result;
} break; } break;
case Type_Node16: { case Type_Node16: {
auto *self = static_cast<Node16 *>(n); auto *self = static_cast<Node16 *>(n);
for (int i = 0; i < self->numChildren && self->index[i] < end; ++i) { bool result = true;
if (begin <= self->index[i]) { for (int i = 0; i < 16; ++i) {
result = std::max(result, self->children[i].childMaxVersion); result &= !((self->children[i].childMaxVersion > readVersion) &
} inBounds(self->index[i]));
} }
return result;
} break; } break;
case Type_Node48: { case Type_Node48: {
auto *self = static_cast<Node48 *>(n); auto *self = static_cast<Node48 *>(n);
self->bitSet.forEachInRange( // Check all pages
[&](int i) { bool result = true;
result = for (int i = 0; i < Node48::kMaxOfMaxTotalPages; ++i) {
std::max(result, self->children[self->index[i]].childMaxVersion); if (self->maxOfMax[i] > readVersion) {
}, for (int j = 0; j < Node48::kMaxOfMaxPageSize; ++j) {
begin, end); int k = (i << Node48::kMaxOfMaxShift) + j;
break; result &= !(self->children[k].childMaxVersion > readVersion &&
inBounds(self->reverseIndex[k]));
}
}
}
return result;
} }
case Type_Node256: { case Type_Node256: {
auto *self = static_cast<Node256 *>(n); auto *self = static_cast<Node256 *>(n);
self->bitSet.forEachInRange( // Check the first page
[&](int i) { if (self->maxOfMax[begin >> Node256::kMaxOfMaxShift] > readVersion) {
result = std::max(result, self->children[i].childMaxVersion); bool result = true;
}, for (int i = 0; i < Node256::kMaxOfMaxPageSize; ++i) {
begin, end); int j = (begin & ~(Node256::kMaxOfMaxPageSize - 1)) + i;
break; result &=
!((self->children[j].childMaxVersion > readVersion) & inBounds(j));
}
if (!result) {
return result;
}
}
// Check the last page
if (end >= 1 &&
self->maxOfMax[(end - 1) >> Node256::kMaxOfMaxShift] > readVersion) {
bool result = true;
for (int i = 0; i < Node256::kMaxOfMaxPageSize; ++i) {
int j = ((end - 1) & ~(Node256::kMaxOfMaxPageSize - 1)) + i;
result &=
!((self->children[j].childMaxVersion > readVersion) & inBounds(j));
}
if (!result) {
return result;
}
}
// Check inner pages
bool result = true;
for (int i = (begin >> Node256::kMaxOfMaxShift) + 1;
i < ((end - 1) >> Node256::kMaxOfMaxShift); ++i) {
result &= self->maxOfMax[i] <= readVersion;
}
return result;
} }
default: // GCOVR_EXCL_LINE default: // GCOVR_EXCL_LINE
__builtin_unreachable(); // GCOVR_EXCL_LINE __builtin_unreachable(); // GCOVR_EXCL_LINE
} }
#if DEBUG_VERBOSE && !defined(NDEBUG) return true;
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) { Vector<uint8_t> getSearchPath(Arena &arena, Node *n) {
@@ -1722,7 +1804,7 @@ bool checkRangeStartsWith(Node *n, std::span<const uint8_t> key, int begin,
#endif #endif
auto remaining = key; auto remaining = key;
if (remaining.size() == 0) { if (remaining.size() == 0) {
return maxBetweenExclusive(n, begin, end) <= readVersion; return checkMaxBetweenExclusive(n, begin, end, readVersion);
} }
auto *child = getChild(n, remaining[0]); auto *child = getChild(n, remaining[0]);
@@ -1821,7 +1903,7 @@ struct CheckRangeLeftSide {
} }
if (searchPathLen >= prefixLen) { if (searchPathLen >= prefixLen) {
if (maxBetweenExclusive(n, remaining[0], 256) > readVersion) { if (!checkMaxBetweenExclusive(n, remaining[0], 256, readVersion)) {
ok = false; ok = false;
return true; return true;
} }
@@ -1963,7 +2045,7 @@ struct CheckRangeRightSide {
return true; return true;
} }
if (maxBetweenExclusive(n, -1, remaining[0]) > readVersion) { if (!checkMaxBetweenExclusive(n, -1, remaining[0], readVersion)) {
ok = false; ok = false;
return true; return true;
} }
@@ -2174,7 +2256,7 @@ template <bool kBegin>
allocators) = old; allocators) = old;
old->parent = *self; old->parent = *self;
old->parentsIndex = old->partialKey()[partialKeyIndex]; old->parentsIndex = old->partialKey()[partialKeyIndex];
maxVersion(old, impl) = oldMaxVersion; setMaxVersion(old, impl, oldMaxVersion);
memmove(old->partialKey(), old->partialKey() + partialKeyIndex + 1, memmove(old->partialKey(), old->partialKey() + partialKeyIndex + 1,
old->partialKeyLen - (partialKeyIndex + 1)); old->partialKeyLen - (partialKeyIndex + 1));
@@ -2198,9 +2280,8 @@ template <bool kBegin>
} }
if constexpr (kBegin) { if constexpr (kBegin) {
auto &m = maxVersion(*self, impl); assert(maxVersion(*self, impl) <= writeVersion);
assert(writeVersion >= m); setMaxVersion(*self, impl, writeVersion);
m = writeVersion;
} }
if (key.size() == 0) { if (key.size() == 0) {
@@ -2208,9 +2289,8 @@ template <bool kBegin>
} }
if constexpr (!kBegin) { if constexpr (!kBegin) {
auto &m = maxVersion(*self, impl); assert(maxVersion(*self, impl) <= writeVersion);
assert(writeVersion >= m); setMaxVersion(*self, impl, writeVersion);
m = writeVersion;
} }
auto &child = getOrCreateChild(*self, key.front(), allocators); auto &child = getOrCreateChild(*self, key.front(), allocators);
@@ -2221,8 +2301,7 @@ template <bool kBegin>
child->partialKeyLen = 0; child->partialKeyLen = 0;
child->parent = *self; child->parent = *self;
child->parentsIndex = key.front(); child->parentsIndex = key.front();
maxVersion(child, impl) = setMaxVersion(child, impl, kBegin ? writeVersion : 0);
kBegin ? writeVersion : std::numeric_limits<int64_t>::lowest();
} }
self = &child; self = &child;
@@ -2267,7 +2346,7 @@ void addPointWrite(Node *&root, int64_t oldestVersion,
n->entryPresent = true; n->entryPresent = true;
n->entry.pointVersion = writeVersion; n->entry.pointVersion = writeVersion;
maxVersion(n, impl) = writeVersion; setMaxVersion(n, impl, writeVersion);
n->entry.rangeVersion = n->entry.rangeVersion =
p != nullptr ? p->entry.rangeVersion : oldestVersion; p != nullptr ? p->entry.rangeVersion : oldestVersion;
} else { } else {
@@ -2308,9 +2387,8 @@ void addWriteRange(Node *&root, int64_t oldestVersion,
break; break;
} }
auto &m = maxVersion(n, impl); assert(maxVersion(n, impl) <= writeVersion);
assert(writeVersion >= m); setMaxVersion(n, impl, writeVersion);
m = writeVersion;
remaining = remaining.subspan(n->partialKeyLen + 1, remaining = remaining.subspan(n->partialKeyLen + 1,
remaining.size() - (n->partialKeyLen + 1)); remaining.size() - (n->partialKeyLen + 1));
@@ -2337,11 +2415,10 @@ void addWriteRange(Node *&root, int64_t oldestVersion,
beginNode->entry.rangeVersion = beginNode->entry.rangeVersion =
p != nullptr ? p->entry.rangeVersion : oldestVersion; p != nullptr ? p->entry.rangeVersion : oldestVersion;
beginNode->entry.pointVersion = writeVersion; beginNode->entry.pointVersion = writeVersion;
maxVersion(beginNode, impl) = writeVersion; assert(maxVersion(beginNode, impl) <= writeVersion);
setMaxVersion(beginNode, impl, writeVersion);
} }
auto &m = maxVersion(beginNode, impl); setMaxVersion(beginNode, impl, writeVersion);
assert(writeVersion >= m);
m = writeVersion;
assert(writeVersion >= beginNode->entry.pointVersion); assert(writeVersion >= beginNode->entry.pointVersion);
beginNode->entry.pointVersion = writeVersion; beginNode->entry.pointVersion = writeVersion;
@@ -2356,8 +2433,8 @@ void addWriteRange(Node *&root, int64_t oldestVersion,
auto *p = nextLogical(endNode); auto *p = nextLogical(endNode);
endNode->entry.pointVersion = endNode->entry.pointVersion =
p != nullptr ? p->entry.rangeVersion : oldestVersion; p != nullptr ? p->entry.rangeVersion : oldestVersion;
auto &m = maxVersion(endNode, impl); auto m = maxVersion(endNode, impl);
m = std::max(m, endNode->entry.pointVersion); setMaxVersion(endNode, impl, std::max(m, endNode->entry.pointVersion));
} }
endNode->entry.rangeVersion = writeVersion; endNode->entry.rangeVersion = writeVersion;
@@ -2557,8 +2634,7 @@ struct __attribute__((visibility("hidden"))) ConflictSet::Impl {
int64_t totalBytes = 0; 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; int index = n->parentsIndex;
n = n->parent; n = n->parent;
if (n == nullptr) { if (n == nullptr) {
@@ -2592,6 +2668,50 @@ int64_t &maxVersion(Node *n, ConflictSet::Impl *impl) {
} }
} }
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->children[i].childMaxVersion = newMax;
return;
}
case Type_Node16: {
auto *n16 = static_cast<Node16 *>(n);
int i = getNodeIndex(n16, index);
n16->children[i].childMaxVersion = newMax;
return;
}
case Type_Node48: {
auto *n48 = static_cast<Node48 *>(n);
assert(n48->bitSet.test(index));
int i = n48->index[index];
n48->children[i].childMaxVersion = 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->children[index].childMaxVersion = newMax;
n256->maxOfMax[index >> Node256::kMaxOfMaxShift] =
std::max(n256->maxOfMax[index >> Node256::kMaxOfMaxShift], newMax);
return;
}
default: // GCOVR_EXCL_LINE
__builtin_unreachable(); // GCOVR_EXCL_LINE
}
}
Node *&getInTree(Node *n, ConflictSet::Impl *impl) { Node *&getInTree(Node *n, ConflictSet::Impl *impl) {
return n->parent == nullptr ? impl->root return n->parent == nullptr ? impl->root
: getChildExists(n->parent, n->parentsIndex); : getChildExists(n->parent, n->parentsIndex);
@@ -2847,7 +2967,7 @@ Iterator firstGeq(Node *n, std::string_view key) {
[[maybe_unused]] int64_t checkMaxVersion(Node *root, Node *node, [[maybe_unused]] int64_t checkMaxVersion(Node *root, Node *node,
int64_t oldestVersion, bool &success, int64_t oldestVersion, bool &success,
ConflictSet::Impl *impl) { ConflictSet::Impl *impl) {
int64_t expected = std::numeric_limits<int64_t>::lowest(); int64_t expected = 0;
if (node->entryPresent) { if (node->entryPresent) {
expected = std::max(expected, node->entry.pointVersion); expected = std::max(expected, node->entry.pointVersion);
} }

View File

@@ -58,27 +58,29 @@ Performance counters:
## Skip list ## Skip list
| ns/op | op/s | err% | total | benchmark | | ns/op | op/s | err% | total | benchmark
| -----: | -----------: | ---: | ----: | :---------------------------------- | |--------------------:|--------------------:|--------:|----------:|:----------
| 246.99 | 4,048,700.59 | 0.2% | 0.01 | `point reads` | | 256.89 | 3,892,784.92 | 0.3% | 0.01 | `point reads`
| 260.16 | 3,843,784.65 | 0.1% | 0.01 | `prefix reads` | | 272.90 | 3,664,395.04 | 0.2% | 0.01 | `prefix reads`
| 493.35 | 2,026,953.19 | 0.1% | 0.01 | `range reads` | | 507.22 | 1,971,549.50 | 0.7% | 0.01 | `range reads`
| 462.05 | 2,164,289.23 | 0.6% | 0.01 | `point writes` | | 452.66 | 2,209,181.91 | 0.5% | 0.01 | `point writes`
| 448.19 | 2,231,205.25 | 0.9% | 0.01 | `prefix writes` | | 438.09 | 2,282,619.96 | 0.4% | 0.01 | `prefix writes`
| 255.83 | 3,908,845.72 | 1.5% | 0.02 | `range writes` | | 253.33 | 3,947,420.36 | 2.5% | 0.02 | `range writes`
| 582.63 | 1,716,349.02 | 1.3% | 0.01 | `monotonic increasing point 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) ## Radix tree (this implementation)
| ns/op | op/s | err% | total | benchmark | | ns/op | op/s | err% | total | benchmark
| -----: | ------------: | ---: | ----: | :---------------------------------- | |--------------------:|--------------------:|--------:|----------:|:----------
| 19.42 | 51,483,206.67 | 0.3% | 0.01 | `point reads` | | 19.83 | 50,420,955.28 | 0.1% | 0.01 | `point reads`
| 58.43 | 17,115,612.57 | 0.1% | 0.01 | `prefix reads` | | 55.95 | 17,872,542.40 | 0.5% | 0.01 | `prefix reads`
| 216.09 | 4,627,766.60 | 0.2% | 0.01 | `range reads` | | 88.28 | 11,327,709.50 | 0.4% | 0.01 | `range reads`
| 28.35 | 35,267,567.72 | 0.2% | 0.01 | `point writes` | | 29.15 | 34,309,531.64 | 0.5% | 0.01 | `point writes`
| 43.43 | 23,026,226.17 | 0.2% | 0.01 | `prefix 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` | | 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` | | 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 # "Real data" test

View File

@@ -1,3 +1,5 @@
___stack_chk_fail
___stack_chk_guard
__tlv_bootstrap __tlv_bootstrap
_abort _abort
_bzero _bzero

View File

@@ -115,7 +115,9 @@ class ConflictSet:
def check(self, *reads: ReadRange) -> list[Result]: def check(self, *reads: ReadRange) -> list[Result]:
r = (ctypes.c_int * len(reads))() 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] return [Result(x) for x in r]
def setOldestVersion(self, version: int) -> None: def setOldestVersion(self, version: int) -> None:

View File

@@ -21,7 +21,7 @@ limitations under the License.
#ifdef __cplusplus #ifdef __cplusplus
namespace weaselab { namespace weaselab {
/** A data structure for optimistic concurrency control on ranges of /** A data structure for optimistic concurrency control on ranges of
* bitwise-lexicographically-ordered keys. * bitwise-lexicographically-ordered keys. All versions must be >= 0.
* *
* Thread safety: * Thread safety:
* - It's safe to operate on two different ConflictSets in two different * - It's safe to operate on two different ConflictSets in two different
@@ -106,7 +106,7 @@ private:
#else #else
/** A data structure for optimistic concurrency control on ranges of /** A data structure for optimistic concurrency control on ranges of
* bitwise-lexicographically-ordered keys. * bitwise-lexicographically-ordered keys. All versions must be >= 0.
* *
* Thread safety: * Thread safety:
* - It's safe to operate on two different ConflictSets in two different * - It's safe to operate on two different ConflictSets in two different

1
paper/.gitignore vendored
View File

@@ -10,3 +10,4 @@
*.pdf *.pdf
*.run.xml *.run.xml
*.synctex.gz *.synctex.gz
version.txt

View File

@@ -6,4 +6,4 @@ paper.pdf: paper.tex $(wildcard *.tikz)
latexmk -pdf latexmk -pdf
clean: clean:
xargs -I '{}' bash -c "rm -f {}" < .gitignore grep -v version.txt .gitignore |xargs -I '{}' bash -c "rm -f {}"

View File

@@ -8,9 +8,11 @@
\usepackage[edges]{forest} \usepackage[edges]{forest}
\usepackage{amsmath} \usepackage{amsmath}
\input{version.txt}
\title{ARTful Concurrency Control à la FoundationDB} \title{ARTful Concurrency Control à la FoundationDB}
\author{Andrew Noyes \thanks{\href{mailto:andrew@weaselab.dev}{andrew@weaselab.dev}}} \author{Andrew Noyes \thanks{\href{mailto:andrew@weaselab.dev}{andrew@weaselab.dev}}}
\date{} \date{Version \versionnumber}
\usepackage{biblatex} \usepackage{biblatex}
\bibliography{bibliography} \bibliography{bibliography}

View File

@@ -53,7 +53,7 @@ def test_conflict_set():
assert cs.getBytes() - before > 0 assert cs.getBytes() - before > 0
assert cs.check(read(0, key)) == [Result.CONFLICT] assert cs.check(read(0, key)) == [Result.CONFLICT]
cs.setOldestVersion(1) cs.setOldestVersion(1)
assert cs.check(read(0, key)) == [Result.TOO_OLD] assert cs.check(read(0, key), read(1, key)) == [Result.TOO_OLD, Result.COMMIT]
def test_inner_full_words(): def test_inner_full_words():

1
version.txt.in Normal file
View File

@@ -0,0 +1 @@
\providecommand{\versionnumber}{@PROJECT_VERSION@}