Compare commits
177 Commits
v0.0.3
...
55271ad06c
Author | SHA1 | Date | |
---|---|---|---|
55271ad06c | |||
e675612599 | |||
42b5d50492 | |||
6394995def | |||
c649bc7964 | |||
ec85a06d01 | |||
fb9f5ce6f4 | |||
2b1c710953 | |||
ebf281220b | |||
6051b2fb2e | |||
11c3ca6766 | |||
b45dec2f1f | |||
c5e9f18c47 | |||
cebbf89cbe | |||
abb791d86b | |||
12f361f33a | |||
640c1ca9dd | |||
b7d54d44e1 | |||
95596f831f | |||
542371d562 | |||
958a4e2d0e | |||
8ce14c58a4 | |||
56e847b63c | |||
7fd1c9e140 | |||
ebaac253e2 | |||
9b470a367c | |||
e7806a36d1 | |||
ffd1dfe74d | |||
c39af9117f | |||
ed274c24d7 | |||
cecfcc0da7 | |||
f6edde0e50 | |||
04ac41a7e7 | |||
354920f86f | |||
bfd02503e7 | |||
d0bd293f8d | |||
41e887c358 | |||
e394e3d96a | |||
3288c583e4 | |||
ef14003781 | |||
3ac16bc966 | |||
1e82f7fe22 | |||
4182d904c5 | |||
bd8ed4e7bd | |||
60cb274a15 | |||
687bc9c935 | |||
d50bb8bc80 | |||
f19b403f19 | |||
34cd210907 | |||
1a5da9e899 | |||
8ba9b04d8c | |||
d895be36d2 | |||
65f8462e88 | |||
46e01af027 | |||
c9d0d72684 | |||
9046dc5a8f | |||
e2927bf0fa | |||
75a2b8d06c | |||
76df63a9d7 | |||
9c5b38b09a | |||
7142dab7ae | |||
3db3d975fc | |||
982b31af34 | |||
cc716ef16b | |||
88bcc7b75c | |||
3e6be6bd83 | |||
e59fee39c7 | |||
3e2c8310bb | |||
8264f1342d | |||
5d7e9c6f85 | |||
cdf42fcb34 | |||
cbe40b5dba | |||
a04e81b3ff | |||
0be97a34b6 | |||
68ab9a9f08 | |||
01488880ef | |||
bb84792cff | |||
1f421e95ff | |||
66bd799f05 | |||
2646d5eaf1 | |||
0367ba9856 | |||
9dec45317e | |||
a68ad5dd17 | |||
8e3eacb54f | |||
0184e1d7f6 | |||
c52d50f4f9 | |||
447da11d59 | |||
daa8e02d4f | |||
fd3ea2c2a8 | |||
0b839b9d7e | |||
11a022dcf7 | |||
94da4c72a5 | |||
461e07822a | |||
75499543e7 | |||
81f44d352f | |||
45da8fb996 | |||
4958a4cced | |||
587874841f | |||
648b0b9238 | |||
d3f4afa167 | |||
f762add4d6 | |||
b311e5f1f0 | |||
ff81890921 | |||
0e96177f5c | |||
efb0e52a0a | |||
2df7000090 | |||
5378a06c39 | |||
12c6ed2568 | |||
a2bf839b19 | |||
c065b185ae | |||
639518bed4 | |||
7de983cc15 | |||
1b4b61ddc6 | |||
bff7b85de2 | |||
9108ee209a | |||
f8bf1c6eb4 | |||
4da2a01614 | |||
bb0e654040 | |||
cce7d29410 | |||
13f8d3fa8a | |||
02866a8cae | |||
fa86d3e707 | |||
7d1d1d7b2a | |||
789ecc29b3 | |||
08f2998a85 | |||
c882d7663d | |||
bfea4384ba | |||
6520e3d734 | |||
23ace8aac5 | |||
62e35de320 | |||
22e4ab01a1 | |||
b3aeed0caa | |||
5f3833e965 | |||
8b1cd9c052 | |||
bb9bc3d7b5 | |||
89b3354a80 | |||
488c723726 | |||
76d0785b33 | |||
add0af11ad | |||
2c0adf4a8b | |||
e8ac78cce6 | |||
13d447c9fe | |||
da7523c5cf | |||
a074bc6f72 | |||
1553a44986 | |||
859ac352e6 | |||
2eb461b8ea | |||
e2e92f4ef5 | |||
f6f25cfcce | |||
c13dc88ff4 | |||
aa5dbb2887 | |||
ea76e04cda | |||
452007e079 | |||
37c75f747b | |||
c96d682483 | |||
6e63fd5126 | |||
f2678de811 | |||
4d7ad075b2 | |||
d2e1863593 | |||
bf91bca16d | |||
08ed17f47b | |||
76a45f16ad | |||
c15d296432 | |||
64a98c529c | |||
ed1388ed21 | |||
309d315956 | |||
eab2e46a56 | |||
85db1a8786 | |||
717f9d6829 | |||
fd93300ce8 | |||
b7e16b31ff | |||
a324d31518 | |||
fdb05e0e33 | |||
7c27d4a972 | |||
738de01cb4 | |||
325cab6a95 | |||
0b2821941a |
112
Bench.cpp
112
Bench.cpp
@@ -258,4 +258,114 @@ 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();
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
void benchCreateAndDestroy() {
|
||||||
|
ankerl::nanobench::Bench bench;
|
||||||
|
|
||||||
|
bench.run("create and destroy", [&]() { ConflictSet cs{0}; });
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(void) {
|
||||||
|
benchConflictSet();
|
||||||
|
benchWorstCaseForRadixRangeRead();
|
||||||
|
benchCreateAndDestroy();
|
||||||
|
}
|
||||||
|
113
CMakeLists.txt
113
CMakeLists.txt
@@ -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.3
|
VERSION 0.0.10
|
||||||
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
|
||||||
@@ -57,9 +60,11 @@ cmake_pop_check_state()
|
|||||||
option(USE_SIMD_FALLBACK
|
option(USE_SIMD_FALLBACK
|
||||||
"Use fallback implementations of functions that use SIMD" OFF)
|
"Use fallback implementations of functions that use SIMD" OFF)
|
||||||
|
|
||||||
|
option(DISABLE_TSAN "Disable TSAN" OFF)
|
||||||
|
|
||||||
# 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 +111,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 +137,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 +145,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 +169,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 +184,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
|
||||||
@@ -237,7 +247,7 @@ if(BUILD_TESTING)
|
|||||||
endforeach()
|
endforeach()
|
||||||
|
|
||||||
# tsan tests
|
# tsan tests
|
||||||
if(NOT CMAKE_CROSSCOMPILING AND NOT CMAKE_BUILD_TYPE STREQUAL Debug)
|
if(NOT CMAKE_CROSSCOMPILING AND NOT DISABLE_TSAN)
|
||||||
add_executable(tsan_driver ConflictSet.cpp FuzzTestDriver.cpp)
|
add_executable(tsan_driver ConflictSet.cpp FuzzTestDriver.cpp)
|
||||||
target_compile_options(tsan_driver PRIVATE ${TEST_FLAGS} -fsanitize=thread)
|
target_compile_options(tsan_driver PRIVATE ${TEST_FLAGS} -fsanitize=thread)
|
||||||
target_link_options(tsan_driver PRIVATE -fsanitize=thread)
|
target_link_options(tsan_driver PRIVATE -fsanitize=thread)
|
||||||
@@ -261,14 +271,26 @@ if(BUILD_TESTING)
|
|||||||
|
|
||||||
# scripted tests. Written manually to fill in anything libfuzzer couldn't
|
# scripted tests. Written manually to fill in anything libfuzzer couldn't
|
||||||
# find.
|
# find.
|
||||||
add_executable(script_test ScriptTest.cpp)
|
if(NOT CMAKE_CROSSCOMPILING)
|
||||||
target_compile_options(script_test PRIVATE ${TEST_FLAGS})
|
find_package(Python3 REQUIRED COMPONENTS Interpreter)
|
||||||
target_link_libraries(script_test PRIVATE ${PROJECT_NAME})
|
set_property(
|
||||||
file(GLOB SCRIPT_TESTS ${CMAKE_SOURCE_DIR}/script_tests/*)
|
DIRECTORY
|
||||||
foreach(TEST ${SCRIPT_TESTS})
|
APPEND
|
||||||
get_filename_component(name ${TEST} NAME)
|
PROPERTY CMAKE_CONFIGURE_DEPENDS
|
||||||
add_test(NAME conflict_set_script_${name} COMMAND script_test ${TEST})
|
${CMAKE_CURRENT_SOURCE_DIR}/test_conflict_set.py)
|
||||||
endforeach()
|
execute_process(
|
||||||
|
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_CURRENT_SOURCE_DIR}/test_conflict_set.py test ${TEST}
|
||||||
|
--build-dir ${CMAKE_CURRENT_BINARY_DIR})
|
||||||
|
endforeach()
|
||||||
|
endif()
|
||||||
|
|
||||||
find_program(VALGRIND_EXE valgrind)
|
find_program(VALGRIND_EXE valgrind)
|
||||||
if(VALGRIND_EXE AND NOT CMAKE_CROSSCOMPILING)
|
if(VALGRIND_EXE AND NOT CMAKE_CROSSCOMPILING)
|
||||||
@@ -300,25 +322,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()
|
||||||
@@ -330,6 +353,11 @@ if(BUILD_TESTING)
|
|||||||
add_executable(real_data_bench RealDataBench.cpp)
|
add_executable(real_data_bench RealDataBench.cpp)
|
||||||
target_link_libraries(real_data_bench PRIVATE ${PROJECT_NAME})
|
target_link_libraries(real_data_bench PRIVATE ${PROJECT_NAME})
|
||||||
set_target_properties(real_data_bench PROPERTIES SKIP_BUILD_RPATH ON)
|
set_target_properties(real_data_bench PROPERTIES SKIP_BUILD_RPATH ON)
|
||||||
|
|
||||||
|
# fuzzer-based perf
|
||||||
|
add_executable(driver_perf TestDriver.cpp)
|
||||||
|
target_compile_definitions(driver_perf PRIVATE PERF_TEST=1)
|
||||||
|
target_link_libraries(driver_perf PRIVATE ${PROJECT_NAME})
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
# packaging
|
# packaging
|
||||||
@@ -357,16 +385,17 @@ else()
|
|||||||
endif()
|
endif()
|
||||||
|
|
||||||
# macos
|
# macos
|
||||||
|
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)
|
||||||
|
2532
ConflictSet.cpp
2532
ConflictSet.cpp
File diff suppressed because it is too large
Load Diff
@@ -98,6 +98,13 @@ void ConflictSet::setOldestVersion(int64_t oldestVersion) {
|
|||||||
|
|
||||||
int64_t ConflictSet::getBytes() const { return -1; }
|
int64_t ConflictSet::getBytes() const { return -1; }
|
||||||
|
|
||||||
|
void ConflictSet::getMetricsV1(MetricsV1 **metrics, int *count) const {
|
||||||
|
*metrics = nullptr;
|
||||||
|
*count = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
double ConflictSet::MetricsV1::getValue() const { return 0; }
|
||||||
|
|
||||||
ConflictSet::ConflictSet(int64_t oldestVersion)
|
ConflictSet::ConflictSet(int64_t oldestVersion)
|
||||||
: impl(new(safe_malloc(sizeof(Impl))) Impl{oldestVersion}) {}
|
: impl(new(safe_malloc(sizeof(Impl))) Impl{oldestVersion}) {}
|
||||||
|
|
||||||
|
301
Internal.h
301
Internal.h
@@ -99,8 +99,7 @@ __attribute__((always_inline)) inline void safe_free(void *p, size_t s) {
|
|||||||
mallocBytesDelta -= s;
|
mallocBytesDelta -= s;
|
||||||
#if SHOW_MEMORY
|
#if SHOW_MEMORY
|
||||||
mallocBytes -= s;
|
mallocBytes -= s;
|
||||||
free(p);
|
#endif
|
||||||
#else
|
|
||||||
#ifndef NDEBUG
|
#ifndef NDEBUG
|
||||||
(char *&)p -= kMallocHeaderSize;
|
(char *&)p -= kMallocHeaderSize;
|
||||||
size_t expected;
|
size_t expected;
|
||||||
@@ -108,7 +107,6 @@ __attribute__((always_inline)) inline void safe_free(void *p, size_t s) {
|
|||||||
assert(s == expected);
|
assert(s == expected);
|
||||||
#endif
|
#endif
|
||||||
free(p);
|
free(p);
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ==================== BEGIN ARENA IMPL ====================
|
// ==================== BEGIN ARENA IMPL ====================
|
||||||
@@ -257,10 +255,65 @@ template <class T> struct ArenaAlloc {
|
|||||||
void deallocate(T *, size_t) noexcept {}
|
void deallocate(T *, size_t) noexcept {}
|
||||||
};
|
};
|
||||||
|
|
||||||
template <class T> using Vector = std::vector<T, ArenaAlloc<T>>;
|
template <class T> struct Vector {
|
||||||
template <class T> auto vector(Arena &arena) {
|
static_assert(std::is_trivially_destructible_v<T>);
|
||||||
return Vector<T>(ArenaAlloc<T>(&arena));
|
static_assert(std::is_trivially_copyable_v<T>);
|
||||||
}
|
|
||||||
|
explicit Vector(Arena *arena)
|
||||||
|
: arena(arena), t(nullptr), size_(0), capacity(0) {}
|
||||||
|
|
||||||
|
void append(std::span<const T> slice) {
|
||||||
|
if (size_ + int(slice.size()) > capacity) {
|
||||||
|
grow(std::max<int>(size_ + slice.size(), capacity * 2));
|
||||||
|
}
|
||||||
|
if (slice.size() > 0) {
|
||||||
|
memcpy(const_cast<std::remove_const_t<T> *>(t) + size_, slice.data(),
|
||||||
|
slice.size() * sizeof(T));
|
||||||
|
}
|
||||||
|
size_ += slice.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
void push_back(const T &t) { append(std::span<const T>(&t, 1)); }
|
||||||
|
|
||||||
|
T *begin() { return t; }
|
||||||
|
T *end() { return t + size_; }
|
||||||
|
T *data() { return t; }
|
||||||
|
T &back() {
|
||||||
|
assert(size_ > 0);
|
||||||
|
return t[size_ - 1];
|
||||||
|
}
|
||||||
|
T &operator[](int i) {
|
||||||
|
assert(i >= 0 && i < size_);
|
||||||
|
return t[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
void pop_back() {
|
||||||
|
assert(size_ > 0);
|
||||||
|
--size_;
|
||||||
|
}
|
||||||
|
|
||||||
|
int size() const { return size_; }
|
||||||
|
|
||||||
|
operator std::span<const T>() const { return std::span(t, size_); }
|
||||||
|
|
||||||
|
private:
|
||||||
|
void grow(int newCapacity) {
|
||||||
|
capacity = newCapacity;
|
||||||
|
auto old = std::span<const T>(*this);
|
||||||
|
t = (T *)new (std::align_val_t(alignof(T)), *arena)
|
||||||
|
uint8_t[capacity * sizeof(T)];
|
||||||
|
size_ = 0;
|
||||||
|
append(old);
|
||||||
|
}
|
||||||
|
|
||||||
|
Arena *arena;
|
||||||
|
T *t;
|
||||||
|
int size_;
|
||||||
|
int capacity;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <class T> auto vector(Arena &arena) { return Vector<T>(&arena); }
|
||||||
|
|
||||||
template <class T, class C> using Set = std::set<T, C, ArenaAlloc<T>>;
|
template <class T, class C> using Set = std::set<T, C, ArenaAlloc<T>>;
|
||||||
template <class T, class C = std::less<T>> auto set(Arena &arena) {
|
template <class T, class C = std::less<T>> auto set(Arena &arena) {
|
||||||
return Set<T, C>(ArenaAlloc<T>(&arena));
|
return Set<T, C>(ArenaAlloc<T>(&arena));
|
||||||
@@ -414,13 +467,15 @@ inline uint32_t Arbitrary::bounded(uint32_t s) {
|
|||||||
// ==================== END ARBITRARY IMPL ====================
|
// ==================== END ARBITRARY IMPL ====================
|
||||||
|
|
||||||
struct ReferenceImpl {
|
struct ReferenceImpl {
|
||||||
explicit ReferenceImpl(int64_t oldestVersion) : oldestVersion(oldestVersion) {
|
explicit ReferenceImpl(int64_t oldestVersion)
|
||||||
|
: oldestVersion(oldestVersion), newestVersion(oldestVersion) {
|
||||||
writeVersionMap[""] = oldestVersion;
|
writeVersionMap[""] = oldestVersion;
|
||||||
}
|
}
|
||||||
void check(const ConflictSet::ReadRange *reads, ConflictSet::Result *results,
|
void check(const ConflictSet::ReadRange *reads, ConflictSet::Result *results,
|
||||||
int count) const {
|
int count) const {
|
||||||
for (int i = 0; i < count; ++i) {
|
for (int i = 0; i < count; ++i) {
|
||||||
if (reads[i].readVersion < oldestVersion) {
|
if (reads[i].readVersion < oldestVersion ||
|
||||||
|
reads[i].readVersion < newestVersion - 2e9) {
|
||||||
results[i] = ConflictSet::TooOld;
|
results[i] = ConflictSet::TooOld;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -442,6 +497,8 @@ struct ReferenceImpl {
|
|||||||
}
|
}
|
||||||
void addWrites(const ConflictSet::WriteRange *writes, int count,
|
void addWrites(const ConflictSet::WriteRange *writes, int count,
|
||||||
int64_t writeVersion) {
|
int64_t writeVersion) {
|
||||||
|
assert(writeVersion >= newestVersion);
|
||||||
|
newestVersion = writeVersion;
|
||||||
for (int i = 0; i < count; ++i) {
|
for (int i = 0; i < count; ++i) {
|
||||||
auto begin =
|
auto begin =
|
||||||
std::string((const char *)writes[i].begin.p, writes[i].begin.len);
|
std::string((const char *)writes[i].begin.p, writes[i].begin.len);
|
||||||
@@ -461,11 +518,12 @@ struct ReferenceImpl {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void setOldestVersion(int64_t oldestVersion) {
|
void setOldestVersion(int64_t oldestVersion) {
|
||||||
assert(oldestVersion >= oldestVersion);
|
assert(oldestVersion >= this->oldestVersion);
|
||||||
this->oldestVersion = oldestVersion;
|
this->oldestVersion = oldestVersion;
|
||||||
}
|
}
|
||||||
|
|
||||||
int64_t oldestVersion;
|
int64_t oldestVersion;
|
||||||
|
int64_t newestVersion;
|
||||||
std::map<std::string, int64_t> writeVersionMap;
|
std::map<std::string, int64_t> writeVersionMap;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -520,41 +578,52 @@ inline const char *resultToStr(ConflictSet::Result r) {
|
|||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
template <class ConflictSetImpl> struct TestDriver {
|
template <class ConflictSetImpl, bool kEnableAssertions = true>
|
||||||
Arbitrary arbitrary;
|
struct TestDriver {
|
||||||
explicit TestDriver(const uint8_t *data, size_t size)
|
Arbitrary *arbitrary;
|
||||||
: arbitrary({data, size}) {}
|
explicit TestDriver(Arbitrary &a) : arbitrary(&a) {
|
||||||
|
#if DEBUG_VERBOSE && !defined(NDEBUG)
|
||||||
|
fprintf(stderr, "%p Initial version: {%" PRId64 "}\n", this, writeVersion);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
int64_t writeVersion = 0;
|
int64_t oldestVersion = arbitrary->next();
|
||||||
int64_t oldestVersion = 0;
|
int64_t writeVersion = oldestVersion;
|
||||||
ConflictSetImpl cs{oldestVersion};
|
ConflictSetImpl cs{oldestVersion};
|
||||||
ReferenceImpl refImpl{oldestVersion};
|
ReferenceImpl refImpl{oldestVersion};
|
||||||
|
|
||||||
constexpr static auto kMaxKeyLen = 8;
|
constexpr static auto kMaxKeySuffixLen = 8;
|
||||||
|
|
||||||
bool ok = true;
|
bool ok = true;
|
||||||
|
|
||||||
|
const int prefixLen = arbitrary->bounded(512);
|
||||||
|
const int prefixByte = arbitrary->randT<uint8_t>();
|
||||||
|
|
||||||
// Call until it returns true, for "done". Check internal invariants etc
|
// Call until it returns true, for "done". Check internal invariants etc
|
||||||
// between calls to next.
|
// between calls to next.
|
||||||
bool next() {
|
bool next() {
|
||||||
if (!arbitrary.hasEntropy()) {
|
assert(cs.getBytes() >= 0);
|
||||||
|
if (!arbitrary->hasEntropy()) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
Arena arena;
|
Arena arena;
|
||||||
{
|
{
|
||||||
int numPointWrites = arbitrary.bounded(100);
|
int numPointWrites = arbitrary->bounded(100);
|
||||||
int numRangeWrites = arbitrary.bounded(100);
|
int numRangeWrites = arbitrary->bounded(100);
|
||||||
int64_t v = ++writeVersion;
|
int64_t v =
|
||||||
|
(writeVersion +=
|
||||||
|
arbitrary->bounded(10) ? arbitrary->bounded(10) : arbitrary->next());
|
||||||
auto *writes =
|
auto *writes =
|
||||||
new (arena) ConflictSet::WriteRange[numPointWrites + numRangeWrites];
|
new (arena) ConflictSet::WriteRange[numPointWrites + numRangeWrites];
|
||||||
auto keys = set<std::string_view>(arena);
|
auto keys = set<std::string_view>(arena);
|
||||||
while (int(keys.size()) < numPointWrites + numRangeWrites * 2) {
|
while (int(keys.size()) < numPointWrites + numRangeWrites * 2) {
|
||||||
if (!arbitrary.hasEntropy()) {
|
if (!arbitrary->hasEntropy()) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
int keyLen = arbitrary.bounded(kMaxKeyLen);
|
int keyLen = prefixLen + arbitrary->bounded(kMaxKeySuffixLen);
|
||||||
auto *begin = new (arena) uint8_t[keyLen];
|
auto *begin = new (arena) uint8_t[keyLen];
|
||||||
arbitrary.randomBytes(begin, keyLen);
|
memset(begin, prefixByte, prefixLen);
|
||||||
|
arbitrary->randomBytes(begin + prefixLen, keyLen - prefixLen);
|
||||||
keys.insert(std::string_view((const char *)begin, keyLen));
|
keys.insert(std::string_view((const char *)begin, keyLen));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -564,7 +633,7 @@ template <class ConflictSetImpl> struct TestDriver {
|
|||||||
rangesRemaining = numRangeWrites;
|
rangesRemaining = numRangeWrites;
|
||||||
pointsRemaining > 0 || rangesRemaining > 0; ++i) {
|
pointsRemaining > 0 || rangesRemaining > 0; ++i) {
|
||||||
bool pointRead = pointsRemaining > 0 && rangesRemaining > 0
|
bool pointRead = pointsRemaining > 0 && rangesRemaining > 0
|
||||||
? bool(arbitrary.bounded(2))
|
? bool(arbitrary->bounded(2))
|
||||||
: pointsRemaining > 0;
|
: pointsRemaining > 0;
|
||||||
if (pointRead) {
|
if (pointRead) {
|
||||||
assert(pointsRemaining > 0);
|
assert(pointsRemaining > 0);
|
||||||
@@ -583,45 +652,120 @@ template <class ConflictSetImpl> struct TestDriver {
|
|||||||
++iter;
|
++iter;
|
||||||
--rangesRemaining;
|
--rangesRemaining;
|
||||||
}
|
}
|
||||||
#if DEBUG_VERBOSE && !defined(NDEBUG)
|
|
||||||
if (writes[i].end.len == 0) {
|
|
||||||
fprintf(stderr, "Write: {%s} -> %" PRId64 "\n",
|
|
||||||
printable(writes[i].begin).c_str(), writeVersion);
|
|
||||||
} else {
|
|
||||||
fprintf(stderr, "Write: [%s, %s) -> %" PRId64 "\n",
|
|
||||||
printable(writes[i].begin).c_str(),
|
|
||||||
printable(writes[i].end).c_str(), writeVersion);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
assert(iter == keys.end());
|
assert(iter == keys.end());
|
||||||
assert(i == numPointWrites + numRangeWrites);
|
assert(i == numPointWrites + numRangeWrites);
|
||||||
|
|
||||||
|
// Test non-canonical writes
|
||||||
|
if (numPointWrites > 0) {
|
||||||
|
int overlaps = arbitrary->bounded(numPointWrites);
|
||||||
|
for (int i = 0; i < numPointWrites + numRangeWrites && overlaps > 0;
|
||||||
|
++i) {
|
||||||
|
if (writes[i].end.len == 0) {
|
||||||
|
int keyLen = prefixLen + arbitrary->bounded(kMaxKeySuffixLen);
|
||||||
|
auto *begin = new (arena) uint8_t[keyLen];
|
||||||
|
memset(begin, prefixByte, prefixLen);
|
||||||
|
arbitrary->randomBytes(begin + prefixLen, keyLen - prefixLen);
|
||||||
|
writes[i].end.len = keyLen;
|
||||||
|
writes[i].end.p = begin;
|
||||||
|
auto c =
|
||||||
|
std::span<const uint8_t>(writes[i].begin.p,
|
||||||
|
writes[i].begin.len) <=>
|
||||||
|
std::span<const uint8_t>(writes[i].end.p, writes[i].end.len);
|
||||||
|
if (c > 0) {
|
||||||
|
using std::swap;
|
||||||
|
swap(writes[i].begin, writes[i].end);
|
||||||
|
} else if (c == 0) {
|
||||||
|
// It's a point write after all, I guess
|
||||||
|
writes[i].end.len = 0;
|
||||||
|
}
|
||||||
|
--overlaps;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (arbitrary->bounded(2)) {
|
||||||
|
// Shuffle writes
|
||||||
|
for (int i = numPointWrites + numRangeWrites - 1; i > 0; --i) {
|
||||||
|
int j = arbitrary->bounded(i + 1);
|
||||||
|
if (i != j) {
|
||||||
|
using std::swap;
|
||||||
|
swap(writes[i], writes[j]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
oldestVersion +=
|
||||||
|
arbitrary->bounded(10) ? arbitrary->bounded(10) : arbitrary->next();
|
||||||
|
oldestVersion = std::min(oldestVersion, writeVersion);
|
||||||
|
|
||||||
|
#ifdef THREAD_TEST
|
||||||
|
std::latch ready{1};
|
||||||
|
std::thread thread2{[&]() {
|
||||||
|
ready.count_down();
|
||||||
|
ConflictSet::MetricsV1 *m;
|
||||||
|
int count;
|
||||||
|
cs.getMetricsV1(&m, &count);
|
||||||
|
for (int i = 0; i < count; ++i) {
|
||||||
|
m[i].getValue();
|
||||||
|
}
|
||||||
|
}};
|
||||||
|
ready.wait();
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if DEBUG_VERBOSE && !defined(NDEBUG)
|
||||||
|
for (int i = 0; i < numPointWrites + numRangeWrites; ++i) {
|
||||||
|
if (writes[i].end.len == 0) {
|
||||||
|
fprintf(stderr, "%p Write: {%s}\n", this,
|
||||||
|
printable(writes[i].begin).c_str());
|
||||||
|
} else {
|
||||||
|
fprintf(stderr, "%p Write: [%s, %s)\n", this,
|
||||||
|
printable(writes[i].begin).c_str(),
|
||||||
|
printable(writes[i].end).c_str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fprintf(stderr, "%p Write @ %" PRId64 "\n", this, v);
|
||||||
|
#endif
|
||||||
|
|
||||||
CALLGRIND_START_INSTRUMENTATION;
|
CALLGRIND_START_INSTRUMENTATION;
|
||||||
cs.addWrites(writes, numPointWrites + numRangeWrites, v);
|
cs.addWrites(writes, numPointWrites + numRangeWrites, v);
|
||||||
CALLGRIND_STOP_INSTRUMENTATION;
|
CALLGRIND_STOP_INSTRUMENTATION;
|
||||||
|
|
||||||
refImpl.addWrites(writes, numPointWrites + numRangeWrites, v);
|
if constexpr (kEnableAssertions) {
|
||||||
|
refImpl.addWrites(writes, numPointWrites + numRangeWrites, v);
|
||||||
|
}
|
||||||
|
|
||||||
oldestVersion = std::max<int64_t>(writeVersion - arbitrary.bounded(10),
|
#if DEBUG_VERBOSE && !defined(NDEBUG)
|
||||||
oldestVersion);
|
fprintf(stderr, "%p Set oldest version: %" PRId64 "\n", this,
|
||||||
|
oldestVersion);
|
||||||
|
#endif
|
||||||
cs.setOldestVersion(oldestVersion);
|
cs.setOldestVersion(oldestVersion);
|
||||||
refImpl.setOldestVersion(oldestVersion);
|
if constexpr (kEnableAssertions) {
|
||||||
|
refImpl.setOldestVersion(oldestVersion);
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef THREAD_TEST
|
||||||
|
thread2.join();
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
int numPointReads = arbitrary.bounded(100);
|
int numPointReads = arbitrary->bounded(100);
|
||||||
int numRangeReads = arbitrary.bounded(100);
|
int numRangeReads = arbitrary->bounded(100);
|
||||||
int64_t v = std::max<int64_t>(writeVersion - arbitrary.bounded(10), 0);
|
|
||||||
|
int64_t v = std::max<int64_t>(writeVersion - (arbitrary->bounded(10)
|
||||||
|
? arbitrary->bounded(10)
|
||||||
|
: arbitrary->next()),
|
||||||
|
0);
|
||||||
auto *reads =
|
auto *reads =
|
||||||
new (arena) ConflictSet::ReadRange[numPointReads + numRangeReads];
|
new (arena) ConflictSet::ReadRange[numPointReads + numRangeReads];
|
||||||
auto keys = set<std::string_view>(arena);
|
auto keys = set<std::string_view>(arena);
|
||||||
while (int(keys.size()) < numPointReads + numRangeReads * 2) {
|
while (int(keys.size()) < numPointReads + numRangeReads * 2) {
|
||||||
if (!arbitrary.hasEntropy()) {
|
if (!arbitrary->hasEntropy()) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
int keyLen = arbitrary.bounded(kMaxKeyLen);
|
int keyLen = prefixLen + arbitrary->bounded(kMaxKeySuffixLen);
|
||||||
auto *begin = new (arena) uint8_t[keyLen];
|
auto *begin = new (arena) uint8_t[keyLen];
|
||||||
arbitrary.randomBytes(begin, keyLen);
|
memset(begin, prefixByte, prefixLen);
|
||||||
|
arbitrary->randomBytes(begin + prefixLen, keyLen - prefixLen);
|
||||||
keys.insert(std::string_view((const char *)begin, keyLen));
|
keys.insert(std::string_view((const char *)begin, keyLen));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -630,7 +774,7 @@ template <class ConflictSetImpl> struct TestDriver {
|
|||||||
for (int pointsRemaining = numPointReads, rangesRemaining = numRangeReads;
|
for (int pointsRemaining = numPointReads, rangesRemaining = numRangeReads;
|
||||||
pointsRemaining > 0 || rangesRemaining > 0; ++i) {
|
pointsRemaining > 0 || rangesRemaining > 0; ++i) {
|
||||||
bool pointRead = pointsRemaining > 0 && rangesRemaining > 0
|
bool pointRead = pointsRemaining > 0 && rangesRemaining > 0
|
||||||
? bool(arbitrary.bounded(2))
|
? bool(arbitrary->bounded(2))
|
||||||
: pointsRemaining > 0;
|
: pointsRemaining > 0;
|
||||||
if (pointRead) {
|
if (pointRead) {
|
||||||
assert(pointsRemaining > 0);
|
assert(pointsRemaining > 0);
|
||||||
@@ -652,12 +796,12 @@ template <class ConflictSetImpl> struct TestDriver {
|
|||||||
reads[i].readVersion = v;
|
reads[i].readVersion = v;
|
||||||
#if DEBUG_VERBOSE && !defined(NDEBUG)
|
#if DEBUG_VERBOSE && !defined(NDEBUG)
|
||||||
if (reads[i].end.len == 0) {
|
if (reads[i].end.len == 0) {
|
||||||
fprintf(stderr, "Read: {%s} @ %d\n",
|
fprintf(stderr, "%p Read: {%s} @ %" PRId64 "\n", this,
|
||||||
printable(reads[i].begin).c_str(), int(reads[i].readVersion));
|
printable(reads[i].begin).c_str(), reads[i].readVersion);
|
||||||
} else {
|
} else {
|
||||||
fprintf(stderr, "Read: [%s, %s) @ %d\n",
|
fprintf(stderr, "%p Read: [%s, %s) @ %" PRId64 "\n", this,
|
||||||
printable(reads[i].begin).c_str(),
|
printable(reads[i].begin).c_str(),
|
||||||
printable(reads[i].end).c_str(), int(reads[i].readVersion));
|
printable(reads[i].end).c_str(), reads[i].readVersion);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
@@ -674,7 +818,15 @@ template <class ConflictSetImpl> struct TestDriver {
|
|||||||
std::latch ready{1};
|
std::latch ready{1};
|
||||||
std::thread thread2{[&]() {
|
std::thread thread2{[&]() {
|
||||||
ready.count_down();
|
ready.count_down();
|
||||||
|
// Call all const methods
|
||||||
cs.check(reads, results3, numPointReads + numRangeReads);
|
cs.check(reads, results3, numPointReads + numRangeReads);
|
||||||
|
cs.getBytes();
|
||||||
|
ConflictSet::MetricsV1 *m;
|
||||||
|
int count;
|
||||||
|
cs.getMetricsV1(&m, &count);
|
||||||
|
for (int i = 0; i < count; ++i) {
|
||||||
|
m[i].getValue();
|
||||||
|
}
|
||||||
}};
|
}};
|
||||||
ready.wait();
|
ready.wait();
|
||||||
#endif
|
#endif
|
||||||
@@ -683,26 +835,40 @@ template <class ConflictSetImpl> struct TestDriver {
|
|||||||
cs.check(reads, results1, numPointReads + numRangeReads);
|
cs.check(reads, results1, numPointReads + numRangeReads);
|
||||||
CALLGRIND_STOP_INSTRUMENTATION;
|
CALLGRIND_STOP_INSTRUMENTATION;
|
||||||
|
|
||||||
refImpl.check(reads, results2, numPointReads + numRangeReads);
|
if constexpr (kEnableAssertions) {
|
||||||
|
// Call remaining const methods
|
||||||
|
cs.getBytes();
|
||||||
|
ConflictSet::MetricsV1 *m;
|
||||||
|
int count;
|
||||||
|
cs.getMetricsV1(&m, &count);
|
||||||
|
for (int i = 0; i < count; ++i) {
|
||||||
|
m[i].getValue();
|
||||||
|
}
|
||||||
|
|
||||||
auto compareResults = [reads](ConflictSet::Result *results1,
|
refImpl.check(reads, results2, numPointReads + numRangeReads);
|
||||||
ConflictSet::Result *results2, int count) {
|
}
|
||||||
|
|
||||||
|
auto compareResults = [reads, this](ConflictSet::Result *results1,
|
||||||
|
ConflictSet::Result *results2,
|
||||||
|
int count) {
|
||||||
for (int i = 0; i < count; ++i) {
|
for (int i = 0; i < count; ++i) {
|
||||||
if (results1[i] != results2[i]) {
|
if (results1[i] != results2[i]) {
|
||||||
if (reads[i].end.len == 0) {
|
if (reads[i].end.len == 0) {
|
||||||
fprintf(stderr,
|
|
||||||
"Expected %s, got %s for read of {%s} at version %" PRId64
|
|
||||||
"\n",
|
|
||||||
resultToStr(results2[i]), resultToStr(results1[i]),
|
|
||||||
printable(reads[i].begin).c_str(), reads[i].readVersion);
|
|
||||||
} else {
|
|
||||||
fprintf(
|
fprintf(
|
||||||
stderr,
|
stderr,
|
||||||
"Expected %s, got %s for read of [%s, %s) at version %" PRId64
|
"%p Expected %s, got %s for read of {%s} at version %" PRId64
|
||||||
"\n",
|
"\n",
|
||||||
resultToStr(results2[i]), resultToStr(results1[i]),
|
(void *)this, resultToStr(results2[i]),
|
||||||
printable(reads[i].begin).c_str(),
|
resultToStr(results1[i]), printable(reads[i].begin).c_str(),
|
||||||
printable(reads[i].end).c_str(), reads[i].readVersion);
|
reads[i].readVersion);
|
||||||
|
} else {
|
||||||
|
fprintf(stderr,
|
||||||
|
"%p Expected %s, got %s for read of [%s, %s) at version "
|
||||||
|
"%" PRId64 "\n",
|
||||||
|
(void *)this, resultToStr(results2[i]),
|
||||||
|
resultToStr(results1[i]),
|
||||||
|
printable(reads[i].begin).c_str(),
|
||||||
|
printable(reads[i].end).c_str(), reads[i].readVersion);
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -710,9 +876,12 @@ template <class ConflictSetImpl> struct TestDriver {
|
|||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!compareResults(results1, results2, numPointReads + numRangeReads)) {
|
if constexpr (kEnableAssertions) {
|
||||||
ok = false;
|
if (!compareResults(results1, results2,
|
||||||
return true;
|
numPointReads + numRangeReads)) {
|
||||||
|
ok = false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef THREAD_TEST
|
#ifdef THREAD_TEST
|
||||||
|
19
Jenkinsfile
vendored
19
Jenkinsfile
vendored
@@ -48,6 +48,17 @@ pipeline {
|
|||||||
recordIssues(tools: [clang()])
|
recordIssues(tools: [clang()])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
stage('Debug') {
|
||||||
|
agent {
|
||||||
|
dockerfile {
|
||||||
|
args '-v /home/jenkins/ccache:/ccache'
|
||||||
|
reuseNode true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
steps {
|
||||||
|
CleanBuildAndTest("-DCMAKE_BUILD_TYPE=Debug")
|
||||||
|
}
|
||||||
|
}
|
||||||
stage('SIMD fallback') {
|
stage('SIMD fallback') {
|
||||||
agent {
|
agent {
|
||||||
dockerfile {
|
dockerfile {
|
||||||
@@ -106,11 +117,15 @@ pipeline {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
steps {
|
steps {
|
||||||
CleanBuildAndTest("-DCMAKE_C_COMPILER=gcc -DCMAKE_CXX_COMPILER=g++ -DCMAKE_C_FLAGS=--coverage -DCMAKE_CXX_FLAGS=--coverage -DCMAKE_BUILD_TYPE=Debug")
|
CleanBuildAndTest("-DCMAKE_C_COMPILER=gcc -DCMAKE_CXX_COMPILER=g++ -DCMAKE_C_FLAGS=--coverage -DCMAKE_CXX_FLAGS=--coverage -DCMAKE_BUILD_TYPE=Debug -DDISABLE_TSAN=ON")
|
||||||
sh '''
|
sh '''
|
||||||
gcovr -f ConflictSet.cpp --cobertura > build/coverage.xml
|
gcovr -f ConflictSet.cpp --cobertura > build/coverage.xml
|
||||||
'''
|
'''
|
||||||
cobertura autoUpdateHealth: false, autoUpdateStability: false, coberturaReportFile: 'build/coverage.xml', conditionalCoverageTargets: '70, 0, 0', failUnhealthy: false, failUnstable: false, lineCoverageTargets: '80, 0, 0', maxNumberOfBuilds: 0, methodCoverageTargets: '80, 0, 0', onlyStable: false, sourceEncoding: 'ASCII', zoomCoverageChart: false
|
recordCoverage qualityGates: [[criticality: 'NOTE', metric: 'MODULE']], tools: [[parser: 'COBERTURA', pattern: 'build/coverage.xml']]
|
||||||
|
sh '''
|
||||||
|
# Suppress again, because we haven't dealt with function multi-versioning for x86 yet
|
||||||
|
# gcovr -f ConflictSet.cpp --fail-under-line 100 > /dev/null
|
||||||
|
'''
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
38
README.md
38
README.md
@@ -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` |
|
| 245.99 | 4,065,232.81 | 0.3% | 0.01 | `point reads`
|
||||||
| 260.16 | 3,843,784.65 | 0.1% | 0.01 | `prefix reads` |
|
| 265.93 | 3,760,430.49 | 0.2% | 0.01 | `prefix reads`
|
||||||
| 493.35 | 2,026,953.19 | 0.1% | 0.01 | `range reads` |
|
| 485.30 | 2,060,569.50 | 0.2% | 0.01 | `range reads`
|
||||||
| 462.05 | 2,164,289.23 | 0.6% | 0.01 | `point writes` |
|
| 449.60 | 2,224,195.17 | 0.4% | 0.01 | `point writes`
|
||||||
| 448.19 | 2,231,205.25 | 0.9% | 0.01 | `prefix writes` |
|
| 441.76 | 2,263,688.18 | 1.1% | 0.01 | `prefix writes`
|
||||||
| 255.83 | 3,908,845.72 | 1.5% | 0.02 | `range writes` |
|
| 245.42 | 4,074,647.54 | 2.4% | 0.02 | `range writes`
|
||||||
| 582.63 | 1,716,349.02 | 1.3% | 0.01 | `monotonic increasing point writes` |
|
| 572.80 | 1,745,810.06 | 1.3% | 0.01 | `monotonic increasing point writes`
|
||||||
|
| 154,819.33 | 6,459.14 | 0.9% | 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.17 | 52,163,930.66 | 0.1% | 0.01 | `point reads`
|
||||||
| 58.43 | 17,115,612.57 | 0.1% | 0.01 | `prefix reads` |
|
| 23.68 | 42,224,388.21 | 0.7% | 0.01 | `prefix reads`
|
||||||
| 216.09 | 4,627,766.60 | 0.2% | 0.01 | `range reads` |
|
| 63.30 | 15,797,506.06 | 0.9% | 0.01 | `range reads`
|
||||||
| 28.35 | 35,267,567.72 | 0.2% | 0.01 | `point writes` |
|
| 29.66 | 33,720,994.74 | 0.3% | 0.01 | `point writes`
|
||||||
| 43.43 | 23,026,226.17 | 0.2% | 0.01 | `prefix writes` |
|
| 43.50 | 22,987,781.25 | 1.0% | 0.01 | `prefix writes`
|
||||||
| 50.00 | 20,000,000.00 | 0.0% | 0.01 | `range writes` |
|
| 50.00 | 20,000,000.00 | 0.8% | 0.01 | `range writes`
|
||||||
| 92.38 | 10,824,863.69 | 4.1% | 0.01 | `monotonic increasing point writes` |
|
| 103.25 | 9,684,786.47 | 2.9% | 0.01 | `monotonic increasing point writes`
|
||||||
|
| 1,181,500.00 | 846.38 | 2.3% | 0.01 | `worst case for radix tree`
|
||||||
|
|
||||||
# "Real data" test
|
# "Real data" test
|
||||||
|
|
||||||
|
@@ -129,6 +129,16 @@ int main(int argc, const char **argv) {
|
|||||||
close(fd);
|
close(fd);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ConflictSet::MetricsV1 *metrics;
|
||||||
|
int metricsCount;
|
||||||
|
cs.getMetricsV1(&metrics, &metricsCount);
|
||||||
|
for (int i = 0; i < metricsCount; ++i) {
|
||||||
|
printf("# HELP %s %s\n", metrics[i].name, metrics[i].help);
|
||||||
|
printf("# TYPE %s %s\n", metrics[i].name,
|
||||||
|
metrics[i].type == metrics[i].Counter ? "counter" : "gauge");
|
||||||
|
printf("%s %g\n", metrics[i].name, metrics[i].getValue());
|
||||||
|
}
|
||||||
|
|
||||||
printf("Check: %g seconds, %g MB/s, Add: %g seconds, %g MB/s, Gc ratio: "
|
printf("Check: %g seconds, %g MB/s, Add: %g seconds, %g MB/s, Gc ratio: "
|
||||||
"%g%%, Peak idle memory: %g\n",
|
"%g%%, Peak idle memory: %g\n",
|
||||||
checkTime, checkBytes / checkTime * 1e-6, addTime,
|
checkTime, checkBytes / checkTime * 1e-6, addTime,
|
||||||
|
155
ScriptTest.cpp
155
ScriptTest.cpp
@@ -1,155 +0,0 @@
|
|||||||
#include "Internal.h"
|
|
||||||
#include <ConflictSet.h>
|
|
||||||
|
|
||||||
#include <chrono>
|
|
||||||
#include <cstring>
|
|
||||||
#include <fcntl.h>
|
|
||||||
#include <sys/mman.h>
|
|
||||||
#include <sys/stat.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
inline size_t getPageSize() {
|
|
||||||
static size_t kPageSize = sysconf(_SC_PAGESIZE);
|
|
||||||
return kPageSize;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Helper for rounding up to page size (or some other alignment)
|
|
||||||
constexpr inline size_t rightAlign(size_t offset, size_t alignment) {
|
|
||||||
return offset % alignment == 0 ? offset
|
|
||||||
: ((offset / alignment) + 1) * alignment;
|
|
||||||
}
|
|
||||||
|
|
||||||
using StringView = std::basic_string_view<uint8_t>;
|
|
||||||
|
|
||||||
inline StringView operator"" _v(const char *str, size_t size) {
|
|
||||||
return {reinterpret_cast<const uint8_t *>(str), size};
|
|
||||||
}
|
|
||||||
|
|
||||||
int main(int argc, const char **argv) {
|
|
||||||
|
|
||||||
ConflictSet cs{0};
|
|
||||||
ReferenceImpl ref{0};
|
|
||||||
|
|
||||||
for (int i = 1; i < argc; ++i) {
|
|
||||||
int fd = open(argv[i], O_RDONLY);
|
|
||||||
struct stat st;
|
|
||||||
if (fstat(fd, &st) == -1) {
|
|
||||||
int err = errno;
|
|
||||||
fprintf(stderr, "stat error %s - %s\n", argv[i], strerror(err));
|
|
||||||
fflush(stderr);
|
|
||||||
abort();
|
|
||||||
}
|
|
||||||
|
|
||||||
int64_t size = rightAlign(st.st_size, getPageSize());
|
|
||||||
const uint8_t *begin =
|
|
||||||
(uint8_t *)mmap(0, size, PROT_READ, MAP_PRIVATE, fd, 0);
|
|
||||||
madvise((void *)begin, size, MADV_SEQUENTIAL);
|
|
||||||
auto *const mapOriginal = begin;
|
|
||||||
const auto sizeOriginal = size;
|
|
||||||
|
|
||||||
StringView b;
|
|
||||||
StringView e;
|
|
||||||
int64_t v = 0;
|
|
||||||
int64_t lastWriteVersion = 0;
|
|
||||||
int64_t lastOldestVersion = 0;
|
|
||||||
std::vector<ConflictSet::WriteRange> writeRanges;
|
|
||||||
std::vector<ConflictSet::ReadRange> readRanges;
|
|
||||||
std::vector<ConflictSet::Result> results;
|
|
||||||
|
|
||||||
for (uint8_t *end = (uint8_t *)memchr(begin, '\n', size); end != nullptr;) {
|
|
||||||
StringView line{begin, static_cast<size_t>(end - begin)};
|
|
||||||
size -= end - begin + 1;
|
|
||||||
begin = end + 1;
|
|
||||||
end = (uint8_t *)memchr(begin, '\n', size);
|
|
||||||
|
|
||||||
if (line.starts_with("begin"_v)) {
|
|
||||||
b = line.substr("begin "_v.size(), line.size());
|
|
||||||
printf("b <- %.*s\n", int(b.size()), b.data());
|
|
||||||
} else if (line.starts_with("end"_v)) {
|
|
||||||
e = line.substr("end "_v.size(), line.size());
|
|
||||||
printf("e <- %.*s\n", int(e.size()), e.data());
|
|
||||||
} else if (line.starts_with("version"_v)) {
|
|
||||||
line = line.substr("version "_v.size(), line.size());
|
|
||||||
v = 0;
|
|
||||||
for (auto c : line) {
|
|
||||||
v = v * 10 + int(c) - int('0');
|
|
||||||
}
|
|
||||||
printf("v <- %" PRId64 "\n", v);
|
|
||||||
} else if (line.starts_with("pointread"_v)) {
|
|
||||||
printf("pointread\n");
|
|
||||||
ConflictSet::ReadRange r;
|
|
||||||
r.begin.p = b.data();
|
|
||||||
r.begin.len = b.size();
|
|
||||||
r.end.len = 0;
|
|
||||||
r.readVersion = v;
|
|
||||||
readRanges.push_back(r);
|
|
||||||
} else if (line.starts_with("pointwrite"_v)) {
|
|
||||||
printf("pointwrite\n");
|
|
||||||
assert(writeRanges.empty() ||
|
|
||||||
(writeRanges.back().end.len == 0 ? writeRanges.back().begin
|
|
||||||
: writeRanges.back().end) < b);
|
|
||||||
ConflictSet::WriteRange w;
|
|
||||||
w.begin.p = b.data();
|
|
||||||
w.begin.len = b.size();
|
|
||||||
w.end.len = 0;
|
|
||||||
writeRanges.push_back(w);
|
|
||||||
} else if (line.starts_with("rangeread"_v)) {
|
|
||||||
printf("rangeread\n");
|
|
||||||
ConflictSet::ReadRange r;
|
|
||||||
r.begin.p = b.data();
|
|
||||||
r.begin.len = b.size();
|
|
||||||
r.end.p = e.data();
|
|
||||||
r.end.len = e.size();
|
|
||||||
r.readVersion = v;
|
|
||||||
readRanges.push_back(r);
|
|
||||||
} else if (line.starts_with("rangewrite"_v)) {
|
|
||||||
printf("rangewrite\n");
|
|
||||||
assert(b < e);
|
|
||||||
assert(writeRanges.empty() ||
|
|
||||||
(writeRanges.back().end.len == 0 ? writeRanges.back().begin
|
|
||||||
: writeRanges.back().end) < b);
|
|
||||||
ConflictSet::WriteRange w;
|
|
||||||
w.begin.p = b.data();
|
|
||||||
w.begin.len = b.size();
|
|
||||||
w.end.p = e.data();
|
|
||||||
w.end.len = e.size();
|
|
||||||
writeRanges.push_back(w);
|
|
||||||
} else if (line.starts_with("check"_v)) {
|
|
||||||
printf("check\n");
|
|
||||||
Arena arena;
|
|
||||||
auto *expected = new (arena) ConflictSet::Result[readRanges.size()];
|
|
||||||
auto *actual = new (arena) ConflictSet::Result[readRanges.size()];
|
|
||||||
ref.check(readRanges.data(), expected, readRanges.size());
|
|
||||||
cs.check(readRanges.data(), actual, readRanges.size());
|
|
||||||
for (int i = 0; i < int(readRanges.size()); ++i) {
|
|
||||||
if (expected[i] != actual[i]) {
|
|
||||||
fprintf(stderr, "Expected %s, got %s at index %d\n",
|
|
||||||
resultToStr(expected[i]), resultToStr(actual[i]), i);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
readRanges = {};
|
|
||||||
} else if (line.starts_with("addwrites"_v)) {
|
|
||||||
printf("addwrites\n");
|
|
||||||
assert(v > lastWriteVersion);
|
|
||||||
lastWriteVersion = v;
|
|
||||||
cs.addWrites(writeRanges.data(), writeRanges.size(), v);
|
|
||||||
ref.addWrites(writeRanges.data(), writeRanges.size(), v);
|
|
||||||
writeRanges = {};
|
|
||||||
} else if (line.starts_with("setoldest"_v)) {
|
|
||||||
printf("setoldest\n");
|
|
||||||
assert(v > lastOldestVersion);
|
|
||||||
lastOldestVersion = v;
|
|
||||||
cs.setOldestVersion(v);
|
|
||||||
ref.setOldestVersion(v);
|
|
||||||
} else if (line.empty() || line.starts_with(";"_v)) {
|
|
||||||
// skip
|
|
||||||
} else {
|
|
||||||
printf("Unrecognized line: %.*s\n", int(line.size()), line.data());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
munmap((void *)mapOriginal, sizeOriginal);
|
|
||||||
close(fd);
|
|
||||||
}
|
|
||||||
}
|
|
283
SkipList.cpp
283
SkipList.cpp
@@ -16,10 +16,14 @@
|
|||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
|
*
|
||||||
|
* This source code is modified to compile outside of FoundationDB
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "ConflictSet.h"
|
#include "ConflictSet.h"
|
||||||
#include "Internal.h"
|
#include "Internal.h"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
#include <span>
|
#include <span>
|
||||||
|
|
||||||
std::span<const uint8_t> keyAfter(Arena &arena, std::span<const uint8_t> key) {
|
std::span<const uint8_t> keyAfter(Arena &arena, std::span<const uint8_t> key) {
|
||||||
@@ -38,7 +42,7 @@ std::span<const uint8_t> copyToArena(Arena &arena,
|
|||||||
}
|
}
|
||||||
|
|
||||||
using Version = int64_t;
|
using Version = int64_t;
|
||||||
#define force_inline __attribute__((always_inline))
|
#define force_inline inline __attribute__((always_inline))
|
||||||
using StringRef = std::span<const uint8_t>;
|
using StringRef = std::span<const uint8_t>;
|
||||||
|
|
||||||
struct KeyRangeRef {
|
struct KeyRangeRef {
|
||||||
@@ -50,6 +54,135 @@ struct KeyRangeRef {
|
|||||||
: begin(begin), end(keyAfter(arena, begin)) {}
|
: begin(begin), end(keyAfter(arena, begin)) {}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct KeyInfo {
|
||||||
|
StringRef key;
|
||||||
|
bool begin;
|
||||||
|
bool write;
|
||||||
|
|
||||||
|
KeyInfo() = default;
|
||||||
|
KeyInfo(StringRef key, bool begin, bool write)
|
||||||
|
: key(key), begin(begin), write(write) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
force_inline int extra_ordering(const KeyInfo &ki) {
|
||||||
|
return ki.begin * 2 + (ki.write ^ ki.begin);
|
||||||
|
}
|
||||||
|
|
||||||
|
// returns true if done with string
|
||||||
|
force_inline bool getCharacter(const KeyInfo &ki, int character,
|
||||||
|
int &outputCharacter) {
|
||||||
|
// normal case
|
||||||
|
if (character < ki.key.size()) {
|
||||||
|
outputCharacter = 5 + ki.key.begin()[character];
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// termination
|
||||||
|
if (character == ki.key.size()) {
|
||||||
|
outputCharacter = 0;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (character == ki.key.size() + 1) {
|
||||||
|
// end/begin+read/write relative sorting
|
||||||
|
outputCharacter = extra_ordering(ki);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
outputCharacter = 0;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator<(const KeyInfo &lhs, const KeyInfo &rhs) {
|
||||||
|
int i = std::min(lhs.key.size(), rhs.key.size());
|
||||||
|
int c = memcmp(lhs.key.data(), rhs.key.data(), i);
|
||||||
|
if (c != 0)
|
||||||
|
return c < 0;
|
||||||
|
|
||||||
|
// Always sort shorter keys before longer keys.
|
||||||
|
if (lhs.key.size() < rhs.key.size()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (lhs.key.size() > rhs.key.size()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// When the keys are the same length, use the extra ordering constraint.
|
||||||
|
return extra_ordering(lhs) < extra_ordering(rhs);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator==(const KeyInfo &lhs, const KeyInfo &rhs) {
|
||||||
|
return !(lhs < rhs || rhs < lhs);
|
||||||
|
}
|
||||||
|
|
||||||
|
void swapSort(std::vector<KeyInfo> &points, int a, int b) {
|
||||||
|
if (points[b] < points[a]) {
|
||||||
|
KeyInfo temp;
|
||||||
|
temp = points[a];
|
||||||
|
points[a] = points[b];
|
||||||
|
points[b] = temp;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct SortTask {
|
||||||
|
int begin;
|
||||||
|
int size;
|
||||||
|
int character;
|
||||||
|
SortTask(int begin, int size, int character)
|
||||||
|
: begin(begin), size(size), character(character) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
void sortPoints(std::vector<KeyInfo> &points) {
|
||||||
|
std::vector<SortTask> tasks;
|
||||||
|
std::vector<KeyInfo> newPoints;
|
||||||
|
std::vector<int> counts;
|
||||||
|
|
||||||
|
tasks.emplace_back(0, points.size(), 0);
|
||||||
|
|
||||||
|
while (tasks.size()) {
|
||||||
|
SortTask st = tasks.back();
|
||||||
|
tasks.pop_back();
|
||||||
|
|
||||||
|
if (st.size < 10) {
|
||||||
|
std::sort(points.begin() + st.begin, points.begin() + st.begin + st.size);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
newPoints.resize(st.size);
|
||||||
|
counts.assign(256 + 5, 0);
|
||||||
|
|
||||||
|
// get counts
|
||||||
|
int c;
|
||||||
|
bool allDone = true;
|
||||||
|
for (int i = st.begin; i < st.begin + st.size; i++) {
|
||||||
|
allDone &= getCharacter(points[i], st.character, c);
|
||||||
|
counts[c]++;
|
||||||
|
}
|
||||||
|
if (allDone)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// calculate offsets from counts and build next level of tasks
|
||||||
|
int total = 0;
|
||||||
|
for (int i = 0; i < counts.size(); i++) {
|
||||||
|
int temp = counts[i];
|
||||||
|
if (temp > 1)
|
||||||
|
tasks.emplace_back(st.begin + total, temp, st.character + 1);
|
||||||
|
counts[i] = total;
|
||||||
|
total += temp;
|
||||||
|
}
|
||||||
|
|
||||||
|
// put in their places
|
||||||
|
for (int i = st.begin; i < st.begin + st.size; i++) {
|
||||||
|
getCharacter(points[i], st.character, c);
|
||||||
|
newPoints[counts[c]++] = points[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
// copy back into original points array
|
||||||
|
for (int i = 0; i < st.size; i++)
|
||||||
|
points[st.begin + i] = newPoints[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static thread_local uint32_t g_seed = 0;
|
static thread_local uint32_t g_seed = 0;
|
||||||
|
|
||||||
static inline int skfastrand() {
|
static inline int skfastrand() {
|
||||||
@@ -268,13 +401,21 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
explicit SkipList(Version version = 0) {
|
explicit SkipList(Version version = 0) {
|
||||||
|
#if DEBUG_VERBOSE
|
||||||
|
fprintf(stderr, "skip_list: create\n");
|
||||||
|
#endif
|
||||||
header = Node::create(StringRef(), MaxLevels - 1);
|
header = Node::create(StringRef(), MaxLevels - 1);
|
||||||
for (int l = 0; l < MaxLevels; l++) {
|
for (int l = 0; l < MaxLevels; l++) {
|
||||||
header->setNext(l, nullptr);
|
header->setNext(l, nullptr);
|
||||||
header->setMaxVersion(l, version);
|
header->setMaxVersion(l, version);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
~SkipList() { destroy(); }
|
~SkipList() {
|
||||||
|
#if DEBUG_VERBOSE
|
||||||
|
fprintf(stderr, "skip_list: destroy\n");
|
||||||
|
#endif
|
||||||
|
destroy();
|
||||||
|
}
|
||||||
SkipList(SkipList &&other) noexcept : header(other.header) {
|
SkipList(SkipList &&other) noexcept : header(other.header) {
|
||||||
other.header = nullptr;
|
other.header = nullptr;
|
||||||
}
|
}
|
||||||
@@ -567,7 +708,8 @@ struct SkipListConflictSet {};
|
|||||||
|
|
||||||
struct __attribute__((visibility("hidden"))) ConflictSet::Impl {
|
struct __attribute__((visibility("hidden"))) ConflictSet::Impl {
|
||||||
Impl(int64_t oldestVersion)
|
Impl(int64_t oldestVersion)
|
||||||
: oldestVersion(oldestVersion), skipList(oldestVersion) {}
|
: oldestVersion(oldestVersion), newestVersion(oldestVersion),
|
||||||
|
skipList(oldestVersion) {}
|
||||||
void check(const ConflictSet::ReadRange *reads, ConflictSet::Result *results,
|
void check(const ConflictSet::ReadRange *reads, ConflictSet::Result *results,
|
||||||
int count) const {
|
int count) const {
|
||||||
Arena arena;
|
Arena arena;
|
||||||
@@ -582,7 +724,8 @@ struct __attribute__((visibility("hidden"))) ConflictSet::Impl {
|
|||||||
}
|
}
|
||||||
skipList.detectConflicts(ranges, count, results);
|
skipList.detectConflicts(ranges, count, results);
|
||||||
for (int i = 0; i < count; ++i) {
|
for (int i = 0; i < count; ++i) {
|
||||||
if (reads[i].readVersion < oldestVersion) {
|
if (reads[i].readVersion < oldestVersion ||
|
||||||
|
reads[i].readVersion < newestVersion - 2e9) {
|
||||||
results[i] = TooOld;
|
results[i] = TooOld;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -590,8 +733,40 @@ struct __attribute__((visibility("hidden"))) ConflictSet::Impl {
|
|||||||
|
|
||||||
void addWrites(const ConflictSet::WriteRange *writes, int count,
|
void addWrites(const ConflictSet::WriteRange *writes, int count,
|
||||||
int64_t writeVersion) {
|
int64_t writeVersion) {
|
||||||
|
auto points = std::vector<KeyInfo>(count * 2);
|
||||||
Arena arena;
|
Arena arena;
|
||||||
const int stringCount = count * 2;
|
|
||||||
|
for (int r = 0; r < count; r++) {
|
||||||
|
points.emplace_back(StringRef(writes[r].begin.p, writes[r].begin.len),
|
||||||
|
true, true);
|
||||||
|
points.emplace_back(
|
||||||
|
writes[r].end.len > 0
|
||||||
|
? StringRef{writes[r].end.p, size_t(writes[r].end.len)}
|
||||||
|
: keyAfter(arena, points.back().key),
|
||||||
|
false, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
sortPoints(points);
|
||||||
|
|
||||||
|
int activeWriteCount = 0;
|
||||||
|
std::vector<std::pair<StringRef, StringRef>> combinedWriteConflictRanges;
|
||||||
|
for (const KeyInfo &point : points) {
|
||||||
|
if (point.write) {
|
||||||
|
if (point.begin) {
|
||||||
|
activeWriteCount++;
|
||||||
|
if (activeWriteCount == 1)
|
||||||
|
combinedWriteConflictRanges.emplace_back(point.key, StringRef());
|
||||||
|
} else /*if (point.end)*/ {
|
||||||
|
activeWriteCount--;
|
||||||
|
if (activeWriteCount == 0)
|
||||||
|
combinedWriteConflictRanges.back().second = point.key;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
assert(writeVersion >= newestVersion);
|
||||||
|
newestVersion = writeVersion;
|
||||||
|
const int stringCount = combinedWriteConflictRanges.size() * 2;
|
||||||
|
|
||||||
const int stripeSize = 16;
|
const int stripeSize = 16;
|
||||||
SkipList::Finger fingers[stripeSize];
|
SkipList::Finger fingers[stripeSize];
|
||||||
@@ -602,11 +777,9 @@ struct __attribute__((visibility("hidden"))) ConflictSet::Impl {
|
|||||||
int ss = stringCount - (stripes - 1) * stripeSize;
|
int ss = stringCount - (stripes - 1) * stripeSize;
|
||||||
for (int s = stripes - 1; s >= 0; s--) {
|
for (int s = stripes - 1; s >= 0; s--) {
|
||||||
for (int i = 0; i * 2 < ss; ++i) {
|
for (int i = 0; i * 2 < ss; ++i) {
|
||||||
const auto &w = writes[s * stripeSize / 2 + i];
|
const auto &w = combinedWriteConflictRanges[s * stripeSize / 2 + i];
|
||||||
values[i * 2] = {w.begin.p, size_t(w.begin.len)};
|
values[i * 2] = w.first;
|
||||||
values[i * 2 + 1] = w.end.len > 0
|
values[i * 2 + 1] = w.second;
|
||||||
? StringRef{w.end.p, size_t(w.end.len)}
|
|
||||||
: keyAfter(arena, values[i * 2]);
|
|
||||||
keyUpdates += 3;
|
keyUpdates += 3;
|
||||||
}
|
}
|
||||||
skipList.find(values, fingers, temp, ss);
|
skipList.find(values, fingers, temp, ss);
|
||||||
@@ -616,6 +789,7 @@ struct __attribute__((visibility("hidden"))) ConflictSet::Impl {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void setOldestVersion(int64_t oldestVersion) {
|
void setOldestVersion(int64_t oldestVersion) {
|
||||||
|
assert(oldestVersion >= this->oldestVersion);
|
||||||
this->oldestVersion = oldestVersion;
|
this->oldestVersion = oldestVersion;
|
||||||
SkipList::Finger finger;
|
SkipList::Finger finger;
|
||||||
int temp;
|
int temp;
|
||||||
@@ -634,16 +808,20 @@ private:
|
|||||||
Arena removalArena;
|
Arena removalArena;
|
||||||
std::span<const uint8_t> removalKey;
|
std::span<const uint8_t> removalKey;
|
||||||
int64_t oldestVersion;
|
int64_t oldestVersion;
|
||||||
|
int64_t newestVersion;
|
||||||
SkipList skipList;
|
SkipList skipList;
|
||||||
};
|
};
|
||||||
|
|
||||||
void ConflictSet::check(const ReadRange *reads, Result *results,
|
// Internal entry points. Public entry points should just delegate to these
|
||||||
int count) const {
|
|
||||||
|
void internal_check(ConflictSet::Impl *impl,
|
||||||
|
const ConflictSet::ReadRange *reads,
|
||||||
|
ConflictSet::Result *results, int count) {
|
||||||
impl->check(reads, results, count);
|
impl->check(reads, results, count);
|
||||||
}
|
}
|
||||||
|
void internal_addWrites(ConflictSet::Impl *impl,
|
||||||
void ConflictSet::addWrites(const WriteRange *writes, int count,
|
const ConflictSet::WriteRange *writes, int count,
|
||||||
int64_t writeVersion) {
|
int64_t writeVersion) {
|
||||||
mallocBytesDelta = 0;
|
mallocBytesDelta = 0;
|
||||||
impl->addWrites(writes, count, writeVersion);
|
impl->addWrites(writes, count, writeVersion);
|
||||||
impl->totalBytes += mallocBytesDelta;
|
impl->totalBytes += mallocBytesDelta;
|
||||||
@@ -654,7 +832,7 @@ void ConflictSet::addWrites(const WriteRange *writes, int count,
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
void ConflictSet::setOldestVersion(int64_t oldestVersion) {
|
void internal_setOldestVersion(ConflictSet::Impl *impl, int64_t oldestVersion) {
|
||||||
mallocBytesDelta = 0;
|
mallocBytesDelta = 0;
|
||||||
impl->setOldestVersion(oldestVersion);
|
impl->setOldestVersion(oldestVersion);
|
||||||
impl->totalBytes += mallocBytesDelta;
|
impl->totalBytes += mallocBytesDelta;
|
||||||
@@ -664,19 +842,61 @@ void ConflictSet::setOldestVersion(int64_t oldestVersion) {
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
ConflictSet::Impl *internal_create(int64_t oldestVersion) {
|
||||||
|
mallocBytesDelta = 0;
|
||||||
|
auto *result = new (safe_malloc(sizeof(ConflictSet::Impl)))
|
||||||
|
ConflictSet::Impl{oldestVersion};
|
||||||
|
result->totalBytes += mallocBytesDelta;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
int64_t ConflictSet::getBytes() const { return impl->totalBytes; }
|
void internal_destroy(ConflictSet::Impl *impl) {
|
||||||
|
impl->~Impl();
|
||||||
|
safe_free(impl, sizeof(ConflictSet::Impl));
|
||||||
|
}
|
||||||
|
|
||||||
|
int64_t internal_getBytes(ConflictSet::Impl *impl) { return impl->totalBytes; }
|
||||||
|
|
||||||
|
void internal_getMetricsV1(ConflictSet::Impl *impl,
|
||||||
|
ConflictSet::MetricsV1 **metrics, int *count) {
|
||||||
|
*metrics = nullptr;
|
||||||
|
*count = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
double internal_getMetricValue(const ConflictSet::MetricsV1 *metric) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ConflictSet::check(const ReadRange *reads, Result *results,
|
||||||
|
int count) const {
|
||||||
|
internal_check(impl, reads, results, count);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ConflictSet::addWrites(const WriteRange *writes, int count,
|
||||||
|
int64_t writeVersion) {
|
||||||
|
internal_addWrites(impl, writes, count, writeVersion);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ConflictSet::setOldestVersion(int64_t oldestVersion) {
|
||||||
|
internal_setOldestVersion(impl, oldestVersion);
|
||||||
|
}
|
||||||
|
|
||||||
|
int64_t ConflictSet::getBytes() const { return internal_getBytes(impl); }
|
||||||
|
|
||||||
|
void ConflictSet::getMetricsV1(MetricsV1 **metrics, int *count) const {
|
||||||
|
return internal_getMetricsV1(impl, metrics, count);
|
||||||
|
}
|
||||||
|
|
||||||
|
double ConflictSet::MetricsV1::getValue() const {
|
||||||
|
return internal_getMetricValue(this);
|
||||||
|
}
|
||||||
|
|
||||||
ConflictSet::ConflictSet(int64_t oldestVersion)
|
ConflictSet::ConflictSet(int64_t oldestVersion)
|
||||||
: impl((mallocBytesDelta = 0,
|
: impl(internal_create(oldestVersion)) {}
|
||||||
new(safe_malloc(sizeof(Impl))) Impl{oldestVersion})) {
|
|
||||||
impl->totalBytes += mallocBytesDelta;
|
|
||||||
}
|
|
||||||
|
|
||||||
ConflictSet::~ConflictSet() {
|
ConflictSet::~ConflictSet() {
|
||||||
if (impl) {
|
if (impl) {
|
||||||
impl->~Impl();
|
internal_destroy(impl);
|
||||||
safe_free(impl, sizeof(Impl));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -697,37 +917,34 @@ extern "C" {
|
|||||||
__attribute__((__visibility__("default"))) void
|
__attribute__((__visibility__("default"))) void
|
||||||
ConflictSet_check(void *cs, const ConflictSet_ReadRange *reads,
|
ConflictSet_check(void *cs, const ConflictSet_ReadRange *reads,
|
||||||
ConflictSet_Result *results, int count) {
|
ConflictSet_Result *results, int count) {
|
||||||
((ConflictSet::Impl *)cs)->check(reads, results, count);
|
internal_check((ConflictSet::Impl *)cs, reads, results, count);
|
||||||
}
|
}
|
||||||
__attribute__((__visibility__("default"))) void
|
__attribute__((__visibility__("default"))) void
|
||||||
ConflictSet_addWrites(void *cs, const ConflictSet_WriteRange *writes, int count,
|
ConflictSet_addWrites(void *cs, const ConflictSet_WriteRange *writes, int count,
|
||||||
int64_t writeVersion) {
|
int64_t writeVersion) {
|
||||||
((ConflictSet::Impl *)cs)->addWrites(writes, count, writeVersion);
|
internal_addWrites((ConflictSet::Impl *)cs, writes, count, writeVersion);
|
||||||
}
|
}
|
||||||
__attribute__((__visibility__("default"))) void
|
__attribute__((__visibility__("default"))) void
|
||||||
ConflictSet_setOldestVersion(void *cs, int64_t oldestVersion) {
|
ConflictSet_setOldestVersion(void *cs, int64_t oldestVersion) {
|
||||||
((ConflictSet::Impl *)cs)->setOldestVersion(oldestVersion);
|
internal_setOldestVersion((ConflictSet::Impl *)cs, oldestVersion);
|
||||||
}
|
}
|
||||||
__attribute__((__visibility__("default"))) void *
|
__attribute__((__visibility__("default"))) void *
|
||||||
ConflictSet_create(int64_t oldestVersion) {
|
ConflictSet_create(int64_t oldestVersion) {
|
||||||
return new (safe_malloc(sizeof(ConflictSet::Impl)))
|
return internal_create(oldestVersion);
|
||||||
ConflictSet::Impl{oldestVersion};
|
|
||||||
}
|
}
|
||||||
__attribute__((__visibility__("default"))) void ConflictSet_destroy(void *cs) {
|
__attribute__((__visibility__("default"))) void ConflictSet_destroy(void *cs) {
|
||||||
using Impl = ConflictSet::Impl;
|
internal_destroy((ConflictSet::Impl *)cs);
|
||||||
((Impl *)cs)->~Impl();
|
|
||||||
safe_free(cs, sizeof(Impl));
|
|
||||||
}
|
}
|
||||||
__attribute__((__visibility__("default"))) int64_t
|
__attribute__((__visibility__("default"))) int64_t
|
||||||
ConflictSet_getBytes(void *cs) {
|
ConflictSet_getBytes(void *cs) {
|
||||||
using Impl = ConflictSet::Impl;
|
return internal_getBytes((ConflictSet::Impl *)cs);
|
||||||
return ((Impl *)cs)->totalBytes;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#if SHOW_MEMORY
|
#if SHOW_MEMORY
|
||||||
struct __attribute__((visibility("default"))) PeakPrinter {
|
struct __attribute__((visibility("default"))) PeakPrinter {
|
||||||
~PeakPrinter() {
|
~PeakPrinter() {
|
||||||
|
printf("--- skip_list ---\n");
|
||||||
printf("malloc bytes: %g\n", double(mallocBytes));
|
printf("malloc bytes: %g\n", double(mallocBytes));
|
||||||
printf("Peak malloc bytes: %g\n", double(peakMallocBytes));
|
printf("Peak malloc bytes: %g\n", double(peakMallocBytes));
|
||||||
}
|
}
|
||||||
|
@@ -3,17 +3,68 @@
|
|||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
|
|
||||||
|
#ifndef PERF_TEST
|
||||||
|
#define PERF_TEST 0
|
||||||
|
#endif
|
||||||
|
|
||||||
int main(int argc, char **argv) {
|
int main(int argc, char **argv) {
|
||||||
for (int i = 1; i < argc; ++i) {
|
for (int i = 1; i < argc; ++i) {
|
||||||
std::ifstream t(argv[i], std::ios::binary);
|
std::ifstream t(argv[i], std::ios::binary);
|
||||||
std::stringstream buffer;
|
std::stringstream buffer;
|
||||||
buffer << t.rdbuf();
|
buffer << t.rdbuf();
|
||||||
auto str = buffer.str();
|
auto str = buffer.str();
|
||||||
TestDriver<ConflictSet> driver{(const uint8_t *)str.data(), str.size()};
|
Arbitrary arbitrary({(const uint8_t *)str.data(), str.size()});
|
||||||
while (!driver.next())
|
TestDriver<ConflictSet, !PERF_TEST> driver1{arbitrary};
|
||||||
;
|
TestDriver<ConflictSet, !PERF_TEST> driver2{arbitrary};
|
||||||
if (!driver.ok) {
|
bool done1 = false;
|
||||||
abort();
|
bool done2 = false;
|
||||||
|
for (;;) {
|
||||||
|
if (!done1) {
|
||||||
|
done1 = driver1.next();
|
||||||
|
if (!driver1.ok) {
|
||||||
|
abort();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!done2) {
|
||||||
|
done2 = driver2.next();
|
||||||
|
if (!driver2.ok) {
|
||||||
|
abort();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (done1 && done2) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
ConflictSet::MetricsV1 *metrics;
|
||||||
|
int metricsCount;
|
||||||
|
driver1.cs.getMetricsV1(&metrics, &metricsCount);
|
||||||
|
printf("#################### METRICS for ConflictSet 1 for %s "
|
||||||
|
"####################\n",
|
||||||
|
argv[i]);
|
||||||
|
for (int i = 0; i < metricsCount; ++i) {
|
||||||
|
printf("# HELP %s %s\n", metrics[i].name, metrics[i].help);
|
||||||
|
printf("# TYPE %s %s\n", metrics[i].name,
|
||||||
|
metrics[i].type == metrics[i].Counter ? "counter" : "gauge");
|
||||||
|
printf("%s %g\n", metrics[i].name, metrics[i].getValue());
|
||||||
|
}
|
||||||
|
puts("");
|
||||||
|
}
|
||||||
|
{
|
||||||
|
ConflictSet::MetricsV1 *metrics;
|
||||||
|
int metricsCount;
|
||||||
|
driver2.cs.getMetricsV1(&metrics, &metricsCount);
|
||||||
|
printf("#################### METRICS for ConflictSet 2 for %s "
|
||||||
|
"####################\n",
|
||||||
|
argv[i]);
|
||||||
|
for (int i = 0; i < metricsCount; ++i) {
|
||||||
|
printf("# HELP %s %s\n", metrics[i].name, metrics[i].help);
|
||||||
|
printf("# TYPE %s %s\n", metrics[i].name,
|
||||||
|
metrics[i].type == metrics[i].Counter ? "counter" : "gauge");
|
||||||
|
printf("%s %g\n", metrics[i].name, metrics[i].getValue());
|
||||||
|
}
|
||||||
|
puts("");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,3 +1,5 @@
|
|||||||
|
__aarch64_cas8_relax
|
||||||
|
__getauxval@GLIBC_2.17
|
||||||
__stack_chk_fail@GLIBC_2.17
|
__stack_chk_fail@GLIBC_2.17
|
||||||
__stack_chk_guard@GLIBC_2.17
|
__stack_chk_guard@GLIBC_2.17
|
||||||
abort@GLIBC_2.17
|
abort@GLIBC_2.17
|
||||||
|
@@ -13,5 +13,7 @@ __ZN8weaselab11ConflictSetC2Ex
|
|||||||
__ZN8weaselab11ConflictSetD1Ev
|
__ZN8weaselab11ConflictSetD1Ev
|
||||||
__ZN8weaselab11ConflictSetD2Ev
|
__ZN8weaselab11ConflictSetD2Ev
|
||||||
__ZN8weaselab11ConflictSetaSEOS0_
|
__ZN8weaselab11ConflictSetaSEOS0_
|
||||||
|
__ZNK8weaselab11ConflictSet12getMetricsV1EPPNS0_9MetricsV1EPi
|
||||||
__ZNK8weaselab11ConflictSet5checkEPKNS0_9ReadRangeEPNS0_6ResultEi
|
__ZNK8weaselab11ConflictSet5checkEPKNS0_9ReadRangeEPNS0_6ResultEi
|
||||||
__ZNK8weaselab11ConflictSet8getBytesEv
|
__ZNK8weaselab11ConflictSet8getBytesEv
|
||||||
|
__ZNK8weaselab11ConflictSet9MetricsV18getValueEv
|
@@ -1,7 +1,10 @@
|
|||||||
|
___stack_chk_fail
|
||||||
|
___stack_chk_guard
|
||||||
__tlv_bootstrap
|
__tlv_bootstrap
|
||||||
_abort
|
_abort
|
||||||
_bzero
|
_bzero
|
||||||
_free
|
_free
|
||||||
_malloc
|
_malloc
|
||||||
_memcpy
|
_memcpy
|
||||||
_memmove
|
_memmove
|
||||||
|
dyld_stub_binder
|
132
conflict_set.py
132
conflict_set.py
@@ -4,22 +4,6 @@ import os
|
|||||||
|
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
_lib = None
|
|
||||||
for f in (
|
|
||||||
os.path.dirname(__file__) + "/build/radix_tree/libconflict-set.so.0",
|
|
||||||
os.path.dirname(__file__) + "/build/radix_tree/libconflict-set.0.dylib",
|
|
||||||
):
|
|
||||||
try:
|
|
||||||
_lib = ctypes.cdll.LoadLibrary(f)
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
|
|
||||||
if _lib is None:
|
|
||||||
import sys
|
|
||||||
|
|
||||||
print("Could not find libconflict-set", file=sys.stderr)
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
|
|
||||||
class _Key(ctypes.Structure):
|
class _Key(ctypes.Structure):
|
||||||
_fields_ = [("p", ctypes.POINTER(ctypes.c_ubyte)), ("len", ctypes.c_int)]
|
_fields_ = [("p", ctypes.POINTER(ctypes.c_ubyte)), ("len", ctypes.c_int)]
|
||||||
@@ -37,28 +21,6 @@ class WriteRange(ctypes.Structure):
|
|||||||
_fields_ = [("begin", _Key), ("end", _Key)]
|
_fields_ = [("begin", _Key), ("end", _Key)]
|
||||||
|
|
||||||
|
|
||||||
_lib.ConflictSet_create.argtypes = (ctypes.c_int64,)
|
|
||||||
_lib.ConflictSet_create.restype = ctypes.c_void_p
|
|
||||||
|
|
||||||
_lib.ConflictSet_check.argtypes = (
|
|
||||||
ctypes.c_void_p,
|
|
||||||
ctypes.POINTER(ReadRange),
|
|
||||||
ctypes.POINTER(ctypes.c_int),
|
|
||||||
ctypes.c_int,
|
|
||||||
)
|
|
||||||
|
|
||||||
_lib.ConflictSet_addWrites.argtypes = (
|
|
||||||
ctypes.c_void_p,
|
|
||||||
ctypes.POINTER(WriteRange),
|
|
||||||
ctypes.c_int,
|
|
||||||
ctypes.c_int64,
|
|
||||||
)
|
|
||||||
|
|
||||||
_lib.ConflictSet_setOldestVersion.argtypes = (ctypes.c_void_p, ctypes.c_int64)
|
|
||||||
|
|
||||||
_lib.ConflictSet_destroy.argtypes = (ctypes.c_void_p,)
|
|
||||||
|
|
||||||
|
|
||||||
class Result(enum.Enum):
|
class Result(enum.Enum):
|
||||||
COMMIT = 0
|
COMMIT = 0
|
||||||
CONFLICT = 1
|
CONFLICT = 1
|
||||||
@@ -66,55 +28,111 @@ class Result(enum.Enum):
|
|||||||
|
|
||||||
|
|
||||||
def write(begin: bytes, end: Optional[bytes] = None) -> WriteRange:
|
def write(begin: bytes, end: Optional[bytes] = None) -> WriteRange:
|
||||||
b = (ctypes.c_ubyte * len(begin))()
|
b = (ctypes.c_ubyte * len(begin)).from_buffer(bytearray(begin))
|
||||||
b.value = begin
|
|
||||||
if end is None:
|
if end is None:
|
||||||
e = (ctypes.c_ubyte * 0)()
|
e = (ctypes.c_ubyte * 0)()
|
||||||
e.value = b""
|
|
||||||
else:
|
else:
|
||||||
e = (ctypes.c_ubyte * len(end))()
|
e = (ctypes.c_ubyte * len(end)).from_buffer(bytearray(end))
|
||||||
e.value = end
|
|
||||||
return WriteRange(_Key(b, len(b)), _Key(e, len(e)))
|
return WriteRange(_Key(b, len(b)), _Key(e, len(e)))
|
||||||
|
|
||||||
|
|
||||||
def read(version: int, begin: bytes, end: Optional[bytes] = None) -> ReadRange:
|
def read(version: int, begin: bytes, end: Optional[bytes] = None) -> ReadRange:
|
||||||
b = (ctypes.c_ubyte * len(begin))()
|
b = (ctypes.c_ubyte * len(begin)).from_buffer(bytearray(begin))
|
||||||
b.value = begin
|
|
||||||
if end is None:
|
if end is None:
|
||||||
e = (ctypes.c_ubyte * 0)()
|
e = (ctypes.c_ubyte * 0)()
|
||||||
e.value = b""
|
|
||||||
else:
|
else:
|
||||||
e = (ctypes.c_ubyte * len(end))()
|
e = (ctypes.c_ubyte * len(end)).from_buffer(bytearray(end))
|
||||||
e.value = end
|
|
||||||
return ReadRange(_Key(b, len(b)), _Key(e, len(e)), version)
|
return ReadRange(_Key(b, len(b)), _Key(e, len(e)), version)
|
||||||
|
|
||||||
|
|
||||||
class ConflictSet:
|
class ConflictSet:
|
||||||
def __init__(self, version: int = 0) -> None:
|
def __init__(
|
||||||
self.p = _lib.ConflictSet_create(version)
|
self,
|
||||||
|
version: int = 0,
|
||||||
|
build_dir: Optional[str] = None,
|
||||||
|
implementation: Optional[str] = None,
|
||||||
|
) -> None:
|
||||||
|
self._lib = None
|
||||||
|
if build_dir is None:
|
||||||
|
build_dir = os.path.dirname(__file__) + "/build"
|
||||||
|
if implementation is None:
|
||||||
|
implementation = "radix_tree"
|
||||||
|
for f in (
|
||||||
|
build_dir + "/" + implementation + "/libconflict-set.so.0",
|
||||||
|
os.path.dirname(__file__)
|
||||||
|
+ "/build/"
|
||||||
|
+ implementation
|
||||||
|
+ "/libconflict-set.0.dylib",
|
||||||
|
):
|
||||||
|
try:
|
||||||
|
self._lib = ctypes.cdll.LoadLibrary(f)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if self._lib is None:
|
||||||
|
import sys
|
||||||
|
|
||||||
|
print(
|
||||||
|
"Could not find libconflict-set implementation " + implementation,
|
||||||
|
file=sys.stderr,
|
||||||
|
)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
self._lib.ConflictSet_create.argtypes = (ctypes.c_int64,)
|
||||||
|
self._lib.ConflictSet_create.restype = ctypes.c_void_p
|
||||||
|
|
||||||
|
self._lib.ConflictSet_check.argtypes = (
|
||||||
|
ctypes.c_void_p,
|
||||||
|
ctypes.POINTER(ReadRange),
|
||||||
|
ctypes.POINTER(ctypes.c_int),
|
||||||
|
ctypes.c_int,
|
||||||
|
)
|
||||||
|
|
||||||
|
self._lib.ConflictSet_addWrites.argtypes = (
|
||||||
|
ctypes.c_void_p,
|
||||||
|
ctypes.POINTER(WriteRange),
|
||||||
|
ctypes.c_int,
|
||||||
|
ctypes.c_int64,
|
||||||
|
)
|
||||||
|
|
||||||
|
self._lib.ConflictSet_setOldestVersion.argtypes = (
|
||||||
|
ctypes.c_void_p,
|
||||||
|
ctypes.c_int64,
|
||||||
|
)
|
||||||
|
|
||||||
|
self._lib.ConflictSet_destroy.argtypes = (ctypes.c_void_p,)
|
||||||
|
|
||||||
|
self._lib.ConflictSet_getBytes.argtypes = (ctypes.c_void_p,)
|
||||||
|
self._lib.ConflictSet_getBytes.restype = ctypes.c_int64
|
||||||
|
|
||||||
|
self.p = self._lib.ConflictSet_create(version)
|
||||||
|
|
||||||
def addWrites(self, version: int, *writes: WriteRange):
|
def addWrites(self, version: int, *writes: WriteRange):
|
||||||
_lib.ConflictSet_addWrites(
|
self._lib.ConflictSet_addWrites(
|
||||||
self.p, (WriteRange * len(writes))(*writes), len(writes), version
|
self.p, (WriteRange * len(writes))(*writes), len(writes), version
|
||||||
)
|
)
|
||||||
|
|
||||||
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))()
|
||||||
_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:
|
||||||
_lib.ConflictSet_setOldestVersion(self.p, version)
|
self._lib.ConflictSet_setOldestVersion(self.p, version)
|
||||||
|
|
||||||
|
def getBytes(self) -> int:
|
||||||
|
return self._lib.ConflictSet_getBytes(self.p)
|
||||||
|
|
||||||
def __enter__(self):
|
def __enter__(self):
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def close(self) -> None:
|
def close(self) -> None:
|
||||||
if self.p is not None:
|
if self.p is not None:
|
||||||
_lib.ConflictSet_destroy(self.p)
|
self._lib.ConflictSet_destroy(self.p)
|
||||||
self.p = None
|
self.p = None
|
||||||
|
|
||||||
def __exit__(self, exception_type, exception_value, exception_traceback):
|
def __exit__(self, exception_type, exception_value, exception_traceback):
|
||||||
if self.p is not None:
|
self.close()
|
||||||
_lib.ConflictSet_destroy(self.p)
|
|
||||||
self.p = None
|
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
#include "ConflictSet.h"
|
#include "ConflictSet.h"
|
||||||
|
|
||||||
#include <cassert>
|
#include <cassert>
|
||||||
|
#include <cstdio>
|
||||||
|
|
||||||
using namespace weaselab;
|
using namespace weaselab;
|
||||||
|
|
||||||
@@ -21,4 +22,14 @@ int main(void) {
|
|||||||
assert(result == ConflictSet::Conflict);
|
assert(result == ConflictSet::Conflict);
|
||||||
int64_t bytes = cs.getBytes();
|
int64_t bytes = cs.getBytes();
|
||||||
assert(bytes > 0);
|
assert(bytes > 0);
|
||||||
|
|
||||||
|
ConflictSet::MetricsV1 *metrics;
|
||||||
|
int metricsCount;
|
||||||
|
cs.getMetricsV1(&metrics, &metricsCount);
|
||||||
|
for (int i = 0; i < metricsCount; ++i) {
|
||||||
|
printf("# HELP %s %s\n", metrics[i].name, metrics[i].help);
|
||||||
|
printf("# TYPE %s %s\n", metrics[i].name,
|
||||||
|
metrics[i].type == metrics[i].Counter ? "counter" : "gauge");
|
||||||
|
printf("%s %g\n", metrics[i].name, metrics[i].getValue());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
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.
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.
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.
Binary file not shown.
Binary file not shown.
Before Width: | Height: | Size: 20 B |
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user