40 Commits

Author SHA1 Message Date
andrew 5378a06c39 Vectorize all bounds checks for Node256 scan
Tests / Clang total: 1038, passed: 1038
Clang |Total|New|Outstanding|Fixed|Trend |:-:|:-:|:-:|:-:|:-: |0|0|0|0|:clap:
Tests / SIMD fallback total: 1038, passed: 1038
Tests / Release [gcc] total: 1038, passed: 1038
GNU C Compiler (gcc) |Total|New|Outstanding|Fixed|Trend |:-:|:-:|:-:|:-:|:-: |0|0|0|0|:clap:
Tests / Release [gcc,aarch64] total: 775, passed: 775
Tests / Coverage total: 779, passed: 779
weaselab/conflict-set/pipeline/head There was a failure building this commit
2024-06-27 17:40:23 -07:00
andrew 12c6ed2568 Reorganize to prepare for better vectorized first/last page 2024-06-27 17:21:41 -07:00
andrew a2bf839b19 Update corpus 2024-06-27 17:15:35 -07:00
andrew c065b185ae Vectorize inner page check for Node256 2024-06-27 17:09:45 -07:00
andrew 639518bed4 Share "scan16" between Node16 and Node48
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-27 13:22:51 -07:00
andrew 7de983cc15 Simd bounds checking for scan for Node16 2024-06-27 13:08:12 -07:00
andrew 1b4b61ddc6 Write Node16 scan in a "more vectorized" style 2024-06-27 12:07:38 -07:00
andrew bff7b85de2 Remove "Child" struct 2024-06-27 10:03:14 -07:00
andrew 9108ee209a SoA instead of AoS for child, maxVersion 2024-06-27 09:57:54 -07:00
andrew f8bf1c6eb4 Remove unreachable code
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 22:14:27 -07:00
andrew 4da2a01614 Use single &, to show branch-free intent 2024-06-26 22:14:05 -07:00
andrew bb0e654040 Fix missed update for Node48::maxOfMax 2024-06-26 22:11:33 -07:00
andrew cce7d29410 Update our benchmarks in README
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
andrew 13f8d3fa8a Add benchmarks for individual spans, but commented out 2024-06-26 20:57:51 -07:00
andrew 02866a8cae Save some bounds checking for scanning Node256 2024-06-26 20:55:18 -07:00
andrew fa86d3e707 "max of max" for Node48 again, but physical instead of logical 2024-06-26 20:41:27 -07:00
andrew 7d1d1d7b2a Maintain childMaxVersion == 0 for unused children in Node48 2024-06-26 20:16:50 -07:00
andrew 789ecc29b3 Use unsigned compare trick to check in bounds 2024-06-26 19:41:25 -07:00
andrew 08f2998a85 Use 8 byte pages for "max of max"
This seems to benchmark better
2024-06-26 19:18:38 -07:00
andrew c882d7663d Maintain "reverseIndex" in Node48 2024-06-26 19:11:34 -07:00
andrew bfea4384ba Branchless inner page check for Node256 2024-06-26 18:28:41 -07:00
andrew 6520e3d734 "max of max" for Node48 2024-06-26 17:54:03 -07:00
andrew 23ace8aac5 Fill in leftward on right side in worst case for radix tree bench 2024-06-26 17:37:24 -07:00
andrew 62e35de320 Update our benchmark in readme 2024-06-26 16:36:02 -07:00
andrew 22e4ab01a1 Track "max of max" versions in Node256 2024-06-26 16:28:24 -07:00
andrew b3aeed0caa Warning: interface change! Require versions >= 0 2024-06-26 15:46:36 -07:00
andrew 5f3833e965 Change maxVersion to return by value, and add setMaxVersion 2024-06-26 15:33:15 -07:00
andrew 8b1cd9c052 Minor improvements to checkMaxBetweenExclusive 2024-06-26 15:06:50 -07:00
andrew bb9bc3d7b5 Measure across different cardinalities for radix worst case bench 2024-06-26 15:06:36 -07:00
andrew 89b3354a80 Update README with new benchmark
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
andrew 488c723726 Improve worst-case radix tree checkRangeRead 2024-06-25 21:22:55 -07:00
andrew 76d0785b33 Add worst-case benchmark for radix tree
Closes #27
2024-06-25 20:50:22 -07:00
andrew add0af11ad Don't check paper/version.txt into version control
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
andrew 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
andrew e8ac78cce6 Bump version
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
andrew 13d447c9fe Use version.txt instead of version.tex
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
andrew da7523c5cf Add version to paper
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
andrew a074bc6f72 include(CTest) before BUILD_TESTING
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
andrew 1553a44986 Make possible to use from FetchContent
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
andrew 859ac352e6 Bump version
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
402 changed files with 550 additions and 190 deletions
+104 -1
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();
}
+51 -38
View File
@@ -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
View File
@@ -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);
}
+20 -18
View File
@@ -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
+2
View File
@@ -1,3 +1,5 @@
___stack_chk_fail
___stack_chk_guard
__tlv_bootstrap
_abort
_bzero
+3 -1
View File
@@ -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.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
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.

After

Width:  |  Height:  |  Size: 11 B

Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
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.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
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.
Binary file not shown.

Some files were not shown because too many files have changed in this diff Show More