Compare commits
6 Commits
v0.0.7
...
fac7968405
Author | SHA1 | Date | |
---|---|---|---|
fac7968405 | |||
e3f6fbe955 | |||
219af68745 | |||
52db15d8bd | |||
b3b91ef860 | |||
13ee3c3a12 |
2
.clangd
2
.clangd
@@ -1,2 +1,2 @@
|
||||
CompileFlags:
|
||||
Add: [-DENABLE_MAIN, -UNDEBUG, -DENABLE_FUZZ, -DTHREAD_TEST, -fexceptions]
|
||||
Add: [-DENABLE_MAIN, -UNDEBUG, -DENABLE_FUZZ, -fexceptions]
|
||||
|
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,3 +1,2 @@
|
||||
.cache
|
||||
__pycache__
|
||||
build
|
||||
|
@@ -1,11 +1,11 @@
|
||||
repos:
|
||||
- repo: https://github.com/pre-commit/mirrors-clang-format
|
||||
rev: 6d365699efc33b1b432eab5b4ae331a19e1857de # frozen: v18.1.2
|
||||
rev: b111689e7b5cba60be3c62d5db2bd1357f4d36ca
|
||||
hooks:
|
||||
- id: clang-format
|
||||
exclude: ".*third_party/.*"
|
||||
- repo: https://github.com/cheshirekow/cmake-format-precommit
|
||||
rev: e2c2116d86a80e72e7146a06e68b7c228afc6319 # frozen: v0.6.13
|
||||
rev: e2c2116d86a80e72e7146a06e68b7c228afc6319
|
||||
hooks:
|
||||
- id: cmake-format
|
||||
- repo: local
|
||||
@@ -13,7 +13,7 @@ repos:
|
||||
- id: debug verbose check
|
||||
name: disallow checking in DEBUG_VERBOSE=1
|
||||
description: disallow checking in DEBUG_VERBOSE=1
|
||||
entry: "^#define DEBUG_VERBOSE 1$"
|
||||
entry: '^#define DEBUG_VERBOSE 1$'
|
||||
language: pygrep
|
||||
types: [c++]
|
||||
- repo: local
|
||||
@@ -21,14 +21,6 @@ repos:
|
||||
- id: debug verbose check
|
||||
name: disallow checking in SHOW_MEMORY=1
|
||||
description: disallow checking in SHOW_MEMORY=1
|
||||
entry: "^#define SHOW_MEMORY 1$"
|
||||
entry: '^#define SHOW_MEMORY 1$'
|
||||
language: pygrep
|
||||
types: [c++]
|
||||
- repo: https://github.com/shellcheck-py/shellcheck-py
|
||||
rev: a23f6b85d0fdd5bb9d564e2579e678033debbdff # frozen: v0.10.0.1
|
||||
hooks:
|
||||
- id: shellcheck
|
||||
- repo: https://github.com/psf/black
|
||||
rev: 552baf822992936134cbd31a38f69c8cfe7c0f05 # frozen: 24.3.0
|
||||
hooks:
|
||||
- id: black
|
||||
types: [c++]
|
109
Bench.cpp
109
Bench.cpp
@@ -34,7 +34,7 @@ ConflictSet::ReadRange singleton(Arena &arena, std::span<const uint8_t> key) {
|
||||
std::span<uint8_t>(new (arena) uint8_t[key.size() + 1], key.size() + 1);
|
||||
memcpy(r.data(), key.data(), key.size());
|
||||
r[key.size()] = 0;
|
||||
return {{key.data(), int(key.size())}, {r.data(), int(r.size())}, 0};
|
||||
return {key.data(), int(key.size()), r.data(), int(r.size())};
|
||||
}
|
||||
|
||||
ConflictSet::ReadRange prefixRange(Arena &arena, std::span<const uint8_t> key) {
|
||||
@@ -52,7 +52,7 @@ ConflictSet::ReadRange prefixRange(Arena &arena, std::span<const uint8_t> key) {
|
||||
auto r = std::span<uint8_t>(new (arena) uint8_t[index + 1], index + 1);
|
||||
memcpy(r.data(), key.data(), index + 1);
|
||||
r[r.size() - 1]++;
|
||||
return {{key.data(), int(key.size())}, {r.data(), int(r.size())}, 0};
|
||||
return {key.data(), int(key.size()), r.data(), int(r.size())};
|
||||
}
|
||||
|
||||
void benchConflictSet() {
|
||||
@@ -258,107 +258,4 @@ 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();
|
||||
}
|
||||
int main(void) { benchConflictSet(); }
|
330
CMakeLists.txt
330
CMakeLists.txt
@@ -1,30 +1,15 @@
|
||||
cmake_minimum_required(VERSION 3.18)
|
||||
project(
|
||||
conflict-set
|
||||
VERSION 0.0.7
|
||||
conflict_set
|
||||
VERSION 0.0.1
|
||||
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_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)
|
||||
include(CheckIncludeFileCXX)
|
||||
include(CheckCXXSourceCompiles)
|
||||
|
||||
set(DEFAULT_BUILD_TYPE "Release")
|
||||
|
||||
if(EMSCRIPTEN OR CMAKE_SYSTEM_NAME STREQUAL WASI)
|
||||
set(WASM ON)
|
||||
else()
|
||||
set(WASM OFF)
|
||||
endif()
|
||||
|
||||
if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
|
||||
message(
|
||||
STATUS
|
||||
@@ -37,34 +22,16 @@ if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
|
||||
"MinSizeRel" "RelWithDebInfo")
|
||||
endif()
|
||||
|
||||
add_compile_options(-fdata-sections -ffunction-sections -Wswitch-enum
|
||||
-Werror=switch-enum -fPIC)
|
||||
|
||||
set(full_relro_flags "-pie;LINKER:-z,relro,-z,now,-z,noexecstack")
|
||||
cmake_push_check_state()
|
||||
list(APPEND CMAKE_REQUIRED_LINK_OPTIONS ${full_relro_flags})
|
||||
check_cxx_source_compiles("int main(){}" HAS_FULL_RELRO FAIL_REGEX "warning:")
|
||||
if(HAS_FULL_RELRO)
|
||||
add_link_options(${full_relro_flags})
|
||||
endif()
|
||||
cmake_pop_check_state()
|
||||
|
||||
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
|
||||
"warning:")
|
||||
cmake_pop_check_state()
|
||||
|
||||
option(USE_SIMD_FALLBACK
|
||||
"Use fallback implementations of functions that use SIMD" OFF)
|
||||
add_compile_options(-fdata-sections -ffunction-sections)
|
||||
|
||||
# This is encouraged according to
|
||||
# https://valgrind.org/docs/manual/manual-core-adv.html#manual-core-adv.clientreq
|
||||
include_directories(SYSTEM ${CMAKE_CURRENT_SOURCE_DIR}/third_party/valgrind)
|
||||
include_directories(SYSTEM ${CMAKE_SOURCE_DIR}/third_party/valgrind)
|
||||
|
||||
add_compile_options($<$<COMPILE_LANGUAGE:CXX>:-Wno-invalid-offsetof>)
|
||||
if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
|
||||
add_compile_options(-Wno-maybe-uninitialized
|
||||
$<$<COMPILE_LANGUAGE:CXX>:-Wno-invalid-offsetof>)
|
||||
endif()
|
||||
|
||||
if(APPLE)
|
||||
add_link_options(-Wl,-dead_strip)
|
||||
@@ -72,137 +39,89 @@ else()
|
||||
add_link_options(-Wl,--gc-sections)
|
||||
endif()
|
||||
|
||||
if(EMSCRIPTEN)
|
||||
# https://github.com/emscripten-core/emscripten/issues/15377#issuecomment-1285167486
|
||||
add_link_options(-lnodefs.js -lnoderawfs.js)
|
||||
add_link_options(-s ALLOW_MEMORY_GROWTH)
|
||||
include(CheckIncludeFileCXX)
|
||||
include(CMakePushCheckState)
|
||||
|
||||
cmake_push_check_state()
|
||||
list(APPEND CMAKE_REQUIRED_FLAGS -mavx)
|
||||
check_include_file_cxx("immintrin.h" HAS_AVX)
|
||||
if(HAS_AVX)
|
||||
add_compile_options(-mavx)
|
||||
add_compile_definitions(HAS_AVX)
|
||||
endif()
|
||||
cmake_pop_check_state()
|
||||
|
||||
if(NOT USE_SIMD_FALLBACK)
|
||||
cmake_push_check_state()
|
||||
list(APPEND CMAKE_REQUIRED_FLAGS -msimd128)
|
||||
check_include_file_cxx("wasm_simd128.h" HAS_WASM_SIMD)
|
||||
if(HAS_WASM_SIMD)
|
||||
add_compile_options(-msimd128)
|
||||
add_compile_definitions(HAS_WASM_SIMD)
|
||||
endif()
|
||||
cmake_pop_check_state()
|
||||
|
||||
cmake_push_check_state()
|
||||
list(APPEND CMAKE_REQUIRED_FLAGS -mavx)
|
||||
check_include_file_cxx("immintrin.h" HAS_AVX)
|
||||
if(HAS_AVX)
|
||||
add_compile_options(-mavx)
|
||||
add_compile_definitions(HAS_AVX)
|
||||
endif()
|
||||
cmake_pop_check_state()
|
||||
|
||||
check_include_file_cxx("arm_neon.h" HAS_ARM_NEON)
|
||||
if(HAS_ARM_NEON)
|
||||
add_compile_definitions(HAS_ARM_NEON)
|
||||
endif()
|
||||
check_include_file_cxx("arm_neon.h" HAS_ARM_NEON)
|
||||
if(HAS_ARM_NEON)
|
||||
add_compile_definitions(HAS_ARM_NEON)
|
||||
endif()
|
||||
|
||||
set(CMAKE_CXX_IMPLICIT_LINK_LIBRARIES "")
|
||||
|
||||
add_library(${PROJECT_NAME}-object OBJECT ConflictSet.cpp)
|
||||
target_compile_options(${PROJECT_NAME}-object PRIVATE -fno-exceptions
|
||||
add_library(${PROJECT_NAME}_object OBJECT ConflictSet.cpp)
|
||||
target_compile_options(${PROJECT_NAME}_object PRIVATE -fPIC -fno-exceptions
|
||||
-fvisibility=hidden)
|
||||
target_include_directories(${PROJECT_NAME}-object
|
||||
PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include)
|
||||
target_include_directories(${PROJECT_NAME}_object
|
||||
PRIVATE ${CMAKE_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(
|
||||
${PROJECT_NAME} PROPERTIES LIBRARY_OUTPUT_DIRECTORY
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/radix_tree")
|
||||
"${CMAKE_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_CURRENT_SOURCE_DIR}/linker.map)
|
||||
if(NOT APPLE)
|
||||
target_link_options(${PROJECT_NAME} PRIVATE
|
||||
LINKER:--version-script=${CMAKE_SOURCE_DIR}/linker.map)
|
||||
endif()
|
||||
|
||||
add_library(${PROJECT_NAME}-static STATIC
|
||||
$<TARGET_OBJECTS:${PROJECT_NAME}-object>)
|
||||
add_library(${PROJECT_NAME}_static STATIC
|
||||
$<TARGET_OBJECTS:${PROJECT_NAME}_object>)
|
||||
if(NOT CMAKE_BUILD_TYPE STREQUAL Debug)
|
||||
set_target_properties(${PROJECT_NAME}-static PROPERTIES LINKER_LANGUAGE C)
|
||||
set_target_properties(${PROJECT_NAME}_static PROPERTIES LINKER_LANGUAGE C)
|
||||
endif()
|
||||
|
||||
if(APPLE)
|
||||
if(NOT APPLE AND CMAKE_OBJCOPY)
|
||||
add_custom_command(
|
||||
TARGET ${PROJECT_NAME}-static
|
||||
PRE_LINK
|
||||
COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/privatize_symbols_macos.sh
|
||||
$<TARGET_OBJECTS:${PROJECT_NAME}-object>)
|
||||
else()
|
||||
add_custom_command(
|
||||
TARGET ${PROJECT_NAME}-static
|
||||
TARGET conflict_set_static
|
||||
POST_BUILD
|
||||
COMMAND
|
||||
${CMAKE_OBJCOPY}
|
||||
--keep-global-symbols=${CMAKE_CURRENT_SOURCE_DIR}/symbol-exports.txt
|
||||
$<TARGET_FILE:${PROJECT_NAME}-static> || echo
|
||||
"Proceeding with all symbols global in static library")
|
||||
${CMAKE_OBJCOPY} --keep-global-symbols=${CMAKE_SOURCE_DIR}/symbols.txt
|
||||
$<TARGET_FILE:${PROJECT_NAME}_static>)
|
||||
endif()
|
||||
|
||||
set(TEST_FLAGS -Wall -Wextra -Wunreachable-code -Wpedantic -UNDEBUG)
|
||||
|
||||
include(CTest)
|
||||
|
||||
# disable tests if this is being used through e.g. FetchContent
|
||||
if(CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR AND BUILD_TESTING)
|
||||
if(BUILD_TESTING)
|
||||
|
||||
set(TEST_FLAGS -Wall -Wextra -Wunreachable-code -Wpedantic -UNDEBUG)
|
||||
# Shared library version of FoundationDB's skip list implementation
|
||||
add_library(skip_list SHARED SkipList.cpp)
|
||||
target_compile_options(skip_list PRIVATE -fPIC -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")
|
||||
set_target_properties(skip_list PROPERTIES OUTPUT_NAME ${PROJECT_NAME})
|
||||
set_target_properties(skip_list PROPERTIES VERSION ${PROJECT_VERSION}
|
||||
SOVERSION ${PROJECT_VERSION_MAJOR})
|
||||
|
||||
# corpus tests, which are tests curated by libfuzzer. The goal is to get broad
|
||||
# coverage with a small number of tests.
|
||||
# Shared library version of a std::unordered_map-based conflict set (point
|
||||
# queries only)
|
||||
add_library(hash_table SHARED HashTable.cpp)
|
||||
target_compile_options(hash_table PRIVATE -fPIC -fno-exceptions
|
||||
-fvisibility=hidden)
|
||||
target_include_directories(hash_table PUBLIC ${CMAKE_SOURCE_DIR}/include)
|
||||
set_target_properties(hash_table PROPERTIES LIBRARY_OUTPUT_DIRECTORY
|
||||
"${CMAKE_BINARY_DIR}/hash_table")
|
||||
set_target_properties(hash_table PROPERTIES OUTPUT_NAME ${PROJECT_NAME})
|
||||
set_target_properties(
|
||||
hash_table PROPERTIES VERSION ${PROJECT_VERSION} SOVERSION
|
||||
${PROJECT_VERSION_MAJOR})
|
||||
|
||||
file(GLOB CORPUS_TESTS ${CMAKE_CURRENT_SOURCE_DIR}/corpus/*)
|
||||
|
||||
# extra testing that relies on shared libraries, which aren't available with
|
||||
# wasm
|
||||
if(NOT WASM)
|
||||
# Shared library version of FoundationDB's skip list implementation
|
||||
add_library(skip_list SHARED SkipList.cpp)
|
||||
target_compile_options(skip_list PRIVATE -fno-exceptions
|
||||
-fvisibility=hidden)
|
||||
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
|
||||
${PROJECT_VERSION_MAJOR})
|
||||
|
||||
# Shared library version of a std::unordered_map-based conflict set (point
|
||||
# queries only)
|
||||
add_library(hash_table SHARED HashTable.cpp)
|
||||
target_compile_options(hash_table PRIVATE -fno-exceptions
|
||||
-fvisibility=hidden)
|
||||
target_include_directories(hash_table
|
||||
PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include)
|
||||
set_target_properties(
|
||||
hash_table PROPERTIES LIBRARY_OUTPUT_DIRECTORY
|
||||
"${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
|
||||
${PROJECT_VERSION_MAJOR})
|
||||
|
||||
add_executable(driver_skip_list TestDriver.cpp)
|
||||
target_compile_options(driver_skip_list PRIVATE ${TEST_FLAGS})
|
||||
target_link_libraries(driver_skip_list PRIVATE skip_list)
|
||||
|
||||
foreach(TEST ${CORPUS_TESTS})
|
||||
get_filename_component(hash ${TEST} NAME)
|
||||
add_test(NAME skip_list_${hash} COMMAND driver_skip_list ${TEST})
|
||||
endforeach()
|
||||
endif()
|
||||
|
||||
# ad hoc testing
|
||||
add_executable(conflict_set_main ConflictSet.cpp)
|
||||
target_include_directories(conflict_set_main
|
||||
PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include)
|
||||
@@ -229,13 +148,12 @@ if(CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR AND BUILD_TESTING)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# whitebox tests
|
||||
# corpus tests
|
||||
|
||||
file(GLOB CORPUS_TESTS ${CMAKE_SOURCE_DIR}/corpus/*)
|
||||
|
||||
add_executable(fuzz_driver ConflictSet.cpp FuzzTestDriver.cpp)
|
||||
target_compile_options(fuzz_driver PRIVATE ${TEST_FLAGS})
|
||||
if(NOT CMAKE_CROSSCOMPILING)
|
||||
target_compile_options(fuzz_driver PRIVATE -fsanitize=address,undefined)
|
||||
target_link_options(fuzz_driver PRIVATE -fsanitize=address,undefined)
|
||||
endif()
|
||||
target_compile_definitions(fuzz_driver PRIVATE ENABLE_FUZZ)
|
||||
target_include_directories(fuzz_driver
|
||||
PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include)
|
||||
@@ -244,51 +162,13 @@ if(CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR AND BUILD_TESTING)
|
||||
add_test(NAME conflict_set_fuzz_${hash} COMMAND fuzz_driver ${TEST})
|
||||
endforeach()
|
||||
|
||||
# tsan tests
|
||||
if(NOT CMAKE_CROSSCOMPILING AND NOT CMAKE_BUILD_TYPE STREQUAL Debug)
|
||||
add_executable(tsan_driver ConflictSet.cpp FuzzTestDriver.cpp)
|
||||
target_compile_options(tsan_driver PRIVATE ${TEST_FLAGS} -fsanitize=thread)
|
||||
target_link_options(tsan_driver PRIVATE -fsanitize=thread)
|
||||
target_compile_definitions(tsan_driver PRIVATE ENABLE_FUZZ THREAD_TEST)
|
||||
target_include_directories(tsan_driver
|
||||
PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include)
|
||||
foreach(TEST ${CORPUS_TESTS})
|
||||
get_filename_component(hash ${TEST} NAME)
|
||||
add_test(NAME conflict_set_tsan_${hash} COMMAND tsan_driver ${TEST})
|
||||
endforeach()
|
||||
endif()
|
||||
|
||||
# blackbox tests
|
||||
add_executable(driver TestDriver.cpp)
|
||||
target_compile_options(driver PRIVATE ${TEST_FLAGS})
|
||||
target_link_libraries(driver PRIVATE ${PROJECT_NAME})
|
||||
foreach(TEST ${CORPUS_TESTS})
|
||||
get_filename_component(hash ${TEST} NAME)
|
||||
add_test(NAME conflict_set_blackbox_${hash} COMMAND driver ${TEST})
|
||||
endforeach()
|
||||
|
||||
# scripted tests. Written manually to fill in anything libfuzzer couldn't
|
||||
# find.
|
||||
if(NOT CMAKE_CROSSCOMPILING)
|
||||
find_package(Python3 REQUIRED COMPONENTS Interpreter)
|
||||
set_property(
|
||||
DIRECTORY
|
||||
APPEND
|
||||
PROPERTY CMAKE_CONFIGURE_DEPENDS
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/test_conflict_set.py)
|
||||
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()
|
||||
add_executable(driver_skip_list TestDriver.cpp)
|
||||
target_compile_options(driver_skip_list PRIVATE ${TEST_FLAGS})
|
||||
target_link_libraries(driver_skip_list PRIVATE skip_list)
|
||||
|
||||
find_program(VALGRIND_EXE valgrind)
|
||||
if(VALGRIND_EXE AND NOT CMAKE_CROSSCOMPILING)
|
||||
@@ -297,6 +177,12 @@ if(CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR AND BUILD_TESTING)
|
||||
$<TARGET_FILE:driver> ${CORPUS_TESTS})
|
||||
endif()
|
||||
|
||||
foreach(TEST ${CORPUS_TESTS})
|
||||
get_filename_component(hash ${TEST} NAME)
|
||||
add_test(NAME conflict_set_blackbox_${hash} COMMAND driver ${TEST})
|
||||
add_test(NAME skip_list_${hash} COMMAND driver_skip_list ${TEST})
|
||||
endforeach()
|
||||
|
||||
# api smoke tests
|
||||
|
||||
# c90
|
||||
@@ -317,46 +203,31 @@ if(CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR AND BUILD_TESTING)
|
||||
PROPERTIES CXX_STANDARD_REQUIRED ON)
|
||||
add_test(NAME conflict_set_cxx_api_test COMMAND conflict_set_cxx_api_test)
|
||||
|
||||
# symbol visibility tests
|
||||
if(NOT WASM AND NOT CMAKE_BUILD_TYPE STREQUAL Debug)
|
||||
if(APPLE)
|
||||
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_CURRENT_SOURCE_DIR}/symbol-exports.txt)
|
||||
if(CMAKE_SYSTEM_PROCESSOR STREQUAL aarch64)
|
||||
set(symbol_imports
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/aarch64-symbol-imports.txt)
|
||||
else()
|
||||
set(symbol_imports ${CMAKE_CURRENT_SOURCE_DIR}/symbol-imports.txt)
|
||||
endif()
|
||||
endif()
|
||||
if(NOT APPLE AND NOT CMAKE_BUILD_TYPE STREQUAL Debug)
|
||||
add_test(
|
||||
NAME conflict_set_shared_symbols
|
||||
COMMAND
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/test_symbols.sh
|
||||
$<TARGET_FILE:${PROJECT_NAME}> ${symbol_exports} ${symbol_imports})
|
||||
COMMAND ${CMAKE_SOURCE_DIR}/test_symbols.sh
|
||||
$<TARGET_FILE:${PROJECT_NAME}> ${CMAKE_SOURCE_DIR}/symbols.txt)
|
||||
add_test(
|
||||
NAME conflict_set_static_symbols
|
||||
COMMAND
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/test_symbols.sh
|
||||
$<TARGET_FILE:${PROJECT_NAME}-static> ${symbol_exports}
|
||||
${symbol_imports})
|
||||
${CMAKE_SOURCE_DIR}/test_symbols.sh
|
||||
$<TARGET_FILE:${PROJECT_NAME}_static> ${CMAKE_SOURCE_DIR}/symbols.txt)
|
||||
endif()
|
||||
|
||||
# bench
|
||||
|
||||
add_executable(conflict_set_bench Bench.cpp)
|
||||
target_link_libraries(conflict_set_bench PRIVATE ${PROJECT_NAME})
|
||||
set_target_properties(conflict_set_bench PROPERTIES SKIP_BUILD_RPATH ON)
|
||||
|
||||
add_executable(real_data_bench RealDataBench.cpp)
|
||||
target_link_libraries(real_data_bench PRIVATE ${PROJECT_NAME})
|
||||
set_target_properties(real_data_bench PROPERTIES SKIP_BUILD_RPATH ON)
|
||||
endif()
|
||||
|
||||
# packaging
|
||||
|
||||
set(CPACK_PACKAGE_CONTACT andrew@weaselab.dev)
|
||||
set(CMAKE_INSTALL_DEFAULT_COMPONENT_NAME all)
|
||||
|
||||
set(CPACK_PACKAGE_VENDOR "Weaselab")
|
||||
set(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_CURRENT_SOURCE_DIR}/LICENSE")
|
||||
@@ -366,30 +237,6 @@ set(CPACK_RESOURCE_FILE_README "${CMAKE_CURRENT_SOURCE_DIR}/README.md")
|
||||
set(CPACK_RPM_PACKAGE_ARCHITECTURE ${CMAKE_SYSTEM_PROCESSOR})
|
||||
set(CPACK_RPM_SPEC_INSTALL_POST "/bin/true") # avoid stripping
|
||||
set(CPACK_RPM_PACKAGE_LICENSE "Apache 2.0")
|
||||
set(CPACK_RPM_FILE_NAME RPM-DEFAULT)
|
||||
|
||||
# deb
|
||||
set(CPACK_DEBIAN_FILE_NAME DEB-DEFAULT)
|
||||
# see *-imports.txt - dependency versions need to be synced with symbol versions
|
||||
if(CMAKE_SYSTEM_PROCESSOR STREQUAL aarch64)
|
||||
set(CPACK_DEBIAN_PACKAGE_DEPENDS "libc6 (>= 2.17)")
|
||||
else()
|
||||
set(CPACK_DEBIAN_PACKAGE_DEPENDS "libc6 (>= 2.14)")
|
||||
endif()
|
||||
|
||||
# macos
|
||||
set(CMAKE_OSX_DEPLOYMENT_TARGET 11.0)
|
||||
if(APPLE)
|
||||
find_program(PANDOC_EXE pandoc)
|
||||
if(PANDOC_EXE)
|
||||
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_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)
|
||||
|
||||
@@ -401,7 +248,7 @@ target_include_directories(
|
||||
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_NAME}>)
|
||||
|
||||
target_include_directories(
|
||||
${PROJECT_NAME}-static
|
||||
${PROJECT_NAME}_static
|
||||
PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
|
||||
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_NAME}>)
|
||||
|
||||
@@ -410,16 +257,13 @@ set_target_properties(
|
||||
SOVERSION ${PROJECT_VERSION_MAJOR})
|
||||
|
||||
install(
|
||||
TARGETS ${PROJECT_NAME} ${PROJECT_NAME}-static
|
||||
EXPORT ${PROJECT_NAME}Config
|
||||
TARGETS ${PROJECT_NAME} ${PROJECT_NAME}_static
|
||||
EXPORT ConflictSetConfig
|
||||
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
|
||||
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
|
||||
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
|
||||
|
||||
install(DIRECTORY include/
|
||||
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_NAME})
|
||||
|
||||
install(EXPORT ${PROJECT_NAME}Config
|
||||
DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/${PROJECT_NAME}/cmake)
|
||||
|
||||
cpack_add_component(all)
|
||||
install(EXPORT ConflictSetConfig
|
||||
DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/ConflictSet/cmake)
|
||||
|
3667
ConflictSet.cpp
3667
ConflictSet.cpp
File diff suppressed because it is too large
Load Diff
@@ -2,25 +2,15 @@
|
||||
#include <cstdint>
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
#include <thread>
|
||||
|
||||
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size);
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
auto doTest = [&]() {
|
||||
for (int i = 1; i < argc; ++i) {
|
||||
std::ifstream t(argv[i], std::ios::binary);
|
||||
std::stringstream buffer;
|
||||
buffer << t.rdbuf();
|
||||
auto str = buffer.str();
|
||||
LLVMFuzzerTestOneInput((const uint8_t *)str.data(), str.size());
|
||||
}
|
||||
};
|
||||
#ifdef THREAD_TEST
|
||||
std::thread thread2{doTest};
|
||||
#endif
|
||||
doTest();
|
||||
#ifdef THREAD_TEST
|
||||
thread2.join();
|
||||
#endif
|
||||
for (int i = 1; i < argc; ++i) {
|
||||
std::ifstream t(argv[i], std::ios::binary);
|
||||
std::stringstream buffer;
|
||||
buffer << t.rdbuf();
|
||||
auto str = buffer.str();
|
||||
LLVMFuzzerTestOneInput((const uint8_t *)str.data(), str.size());
|
||||
}
|
||||
}
|
||||
|
@@ -96,15 +96,13 @@ void ConflictSet::setOldestVersion(int64_t oldestVersion) {
|
||||
return impl->setOldestVersion(oldestVersion);
|
||||
}
|
||||
|
||||
int64_t ConflictSet::getBytes() const { return -1; }
|
||||
|
||||
ConflictSet::ConflictSet(int64_t oldestVersion)
|
||||
: impl(new(safe_malloc(sizeof(Impl))) Impl{oldestVersion}) {}
|
||||
: impl(new (safe_malloc(sizeof(Impl))) Impl{oldestVersion}) {}
|
||||
|
||||
ConflictSet::~ConflictSet() {
|
||||
if (impl) {
|
||||
impl->~Impl();
|
||||
safe_free(impl, sizeof(Impl));
|
||||
free(impl);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -144,11 +142,6 @@ ConflictSet_create(int64_t oldestVersion) {
|
||||
__attribute__((__visibility__("default"))) void ConflictSet_destroy(void *cs) {
|
||||
using Impl = ConflictSet::Impl;
|
||||
((Impl *)cs)->~Impl();
|
||||
safe_free(cs, sizeof(Impl));
|
||||
}
|
||||
__attribute__((__visibility__("default"))) int64_t
|
||||
ConflictSet_getBytes(void *cs) {
|
||||
using Impl = ConflictSet::Impl;
|
||||
return -1;
|
||||
free(cs);
|
||||
}
|
||||
}
|
||||
|
321
Internal.h
321
Internal.h
@@ -2,8 +2,6 @@
|
||||
|
||||
#include "ConflictSet.h"
|
||||
|
||||
using namespace weaselab;
|
||||
|
||||
#include <bit>
|
||||
#include <cassert>
|
||||
#include <compare>
|
||||
@@ -12,12 +10,10 @@ using namespace weaselab;
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <inttypes.h>
|
||||
#include <latch>
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <span>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <unordered_set>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
@@ -39,74 +35,16 @@ operator<=>(const std::span<const uint8_t> &lhs,
|
||||
return lhs.size() <=> rhs.size();
|
||||
}
|
||||
|
||||
[[nodiscard]] inline auto operator<=>(const std::span<const uint8_t> &lhs,
|
||||
const ConflictSet::Key &rhs) noexcept {
|
||||
int cl = std::min<int>(lhs.size(), rhs.len);
|
||||
if (cl > 0) {
|
||||
if (auto c = memcmp(lhs.data(), rhs.p, cl) <=> 0; c != 0) {
|
||||
return c;
|
||||
}
|
||||
}
|
||||
return lhs.size() <=> size_t(rhs.len);
|
||||
}
|
||||
|
||||
// This header contains code that we want to reuse outside of ConflictSet.cpp or
|
||||
// want to exclude from coverage since it's only testing related.
|
||||
|
||||
// GCOVR_EXCL_START
|
||||
|
||||
#if SHOW_MEMORY
|
||||
inline int64_t mallocBytes = 0;
|
||||
inline int64_t peakMallocBytes = 0;
|
||||
#endif
|
||||
|
||||
inline thread_local int64_t mallocBytesDelta = 0;
|
||||
|
||||
#ifndef NDEBUG
|
||||
constexpr auto kMallocHeaderSize = 16;
|
||||
#endif
|
||||
|
||||
// malloc that aborts on OOM and thus always returns a non-null pointer. Must be
|
||||
// paired with `safe_free`.
|
||||
__attribute__((always_inline)) inline void *safe_malloc(size_t s) {
|
||||
mallocBytesDelta += s;
|
||||
#if SHOW_MEMORY
|
||||
mallocBytes += s;
|
||||
if (mallocBytes > peakMallocBytes) {
|
||||
peakMallocBytes = mallocBytes;
|
||||
if (void *p = malloc(s)) {
|
||||
return p;
|
||||
}
|
||||
#endif
|
||||
void *p = malloc(s
|
||||
#ifndef NDEBUG
|
||||
+ kMallocHeaderSize
|
||||
#endif
|
||||
);
|
||||
if (p == nullptr) {
|
||||
abort();
|
||||
}
|
||||
#ifndef NDEBUG
|
||||
memcpy(p, &s, sizeof(s));
|
||||
(char *&)p += kMallocHeaderSize;
|
||||
#endif
|
||||
return p;
|
||||
}
|
||||
|
||||
// Must be paired with `safe_malloc`.
|
||||
//
|
||||
// There's nothing safer about this than free. Only called safe_free for
|
||||
// symmetry with safe_malloc.
|
||||
__attribute__((always_inline)) inline void safe_free(void *p, size_t s) {
|
||||
mallocBytesDelta -= s;
|
||||
#if SHOW_MEMORY
|
||||
mallocBytes -= s;
|
||||
#endif
|
||||
#ifndef NDEBUG
|
||||
(char *&)p -= kMallocHeaderSize;
|
||||
size_t expected;
|
||||
memcpy(&expected, p, sizeof(expected));
|
||||
assert(s == expected);
|
||||
#endif
|
||||
free(p);
|
||||
abort();
|
||||
}
|
||||
|
||||
// ==================== BEGIN ARENA IMPL ====================
|
||||
@@ -152,7 +90,6 @@ inline void *operator new[](size_t size, std::align_val_t align,
|
||||
|
||||
/// align must be a power of two
|
||||
template <class T> T *align_up(T *t, size_t align) {
|
||||
assert(std::popcount(align) == 1);
|
||||
auto unaligned = uintptr_t(t);
|
||||
auto aligned = (unaligned + align - 1) & ~(align - 1);
|
||||
return reinterpret_cast<T *>(reinterpret_cast<char *>(t) + aligned -
|
||||
@@ -161,7 +98,6 @@ template <class T> T *align_up(T *t, size_t align) {
|
||||
|
||||
/// align must be a power of two
|
||||
constexpr inline int align_up(uint32_t unaligned, uint32_t align) {
|
||||
assert(std::popcount(align) == 1);
|
||||
return (unaligned + align - 1) & ~(align - 1);
|
||||
}
|
||||
|
||||
@@ -177,10 +113,12 @@ struct Arena::ArenaImpl {
|
||||
uint8_t *begin() { return reinterpret_cast<uint8_t *>(this + 1); }
|
||||
};
|
||||
|
||||
static_assert(sizeof(Arena::ArenaImpl) == 16);
|
||||
static_assert(alignof(Arena::ArenaImpl) == 8);
|
||||
|
||||
inline Arena::Arena(int initialSize) : impl(nullptr) {
|
||||
if (initialSize > 0) {
|
||||
auto allocationSize =
|
||||
align_up(initialSize + sizeof(ArenaImpl), alignof(ArenaImpl));
|
||||
auto allocationSize = align_up(initialSize + sizeof(ArenaImpl), 16);
|
||||
impl = (Arena::ArenaImpl *)safe_malloc(allocationSize);
|
||||
impl->prev = nullptr;
|
||||
impl->capacity = allocationSize - sizeof(ArenaImpl);
|
||||
@@ -191,7 +129,7 @@ inline Arena::Arena(int initialSize) : impl(nullptr) {
|
||||
inline void onDestroy(Arena::ArenaImpl *impl) {
|
||||
while (impl) {
|
||||
auto *prev = impl->prev;
|
||||
safe_free(impl, sizeof(Arena::ArenaImpl) + impl->capacity);
|
||||
free(impl);
|
||||
impl = prev;
|
||||
}
|
||||
}
|
||||
@@ -216,7 +154,7 @@ inline void *operator new(size_t size, std::align_val_t align, Arena &arena) {
|
||||
(arena.impl ? std::max<int>(sizeof(Arena::ArenaImpl),
|
||||
arena.impl->capacity * 2)
|
||||
: 0)),
|
||||
alignof(Arena::ArenaImpl));
|
||||
16);
|
||||
auto *impl = (Arena::ArenaImpl *)safe_malloc(allocationSize);
|
||||
impl->prev = arena.impl;
|
||||
impl->capacity = allocationSize - sizeof(Arena::ArenaImpl);
|
||||
@@ -255,65 +193,10 @@ template <class T> struct ArenaAlloc {
|
||||
void deallocate(T *, size_t) noexcept {}
|
||||
};
|
||||
|
||||
template <class T> struct Vector {
|
||||
static_assert(std::is_trivially_destructible_v<T>);
|
||||
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> using Vector = std::vector<T, ArenaAlloc<T>>;
|
||||
template <class T> auto vector(Arena &arena) {
|
||||
return Vector<T>(ArenaAlloc<T>(&arena));
|
||||
}
|
||||
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) {
|
||||
return Set<T, C>(ArenaAlloc<T>(&arena));
|
||||
@@ -466,16 +349,42 @@ inline uint32_t Arbitrary::bounded(uint32_t s) {
|
||||
|
||||
// ==================== END ARBITRARY IMPL ====================
|
||||
|
||||
// ==================== BEGIN UTILITIES IMPL ====================
|
||||
|
||||
// Call Stepwise::step for each element of remaining until it returns true.
|
||||
// Applies a permutation to `remaining` as a side effect.
|
||||
template <class Stepwise> void runInterleaved(std::span<Stepwise> remaining) {
|
||||
while (remaining.size() > 0) {
|
||||
for (int i = 0; i < int(remaining.size());) {
|
||||
bool done = remaining[i].step();
|
||||
if (done) {
|
||||
if (i != int(remaining.size()) - 1) {
|
||||
using std::swap;
|
||||
swap(remaining[i], remaining.back());
|
||||
}
|
||||
remaining = remaining.subspan(0, remaining.size() - 1);
|
||||
} else {
|
||||
++i;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template <class Stepwise> void runSequential(std::span<Stepwise> remaining) {
|
||||
for (auto &r : remaining) {
|
||||
while (!r.step()) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct ReferenceImpl {
|
||||
explicit ReferenceImpl(int64_t oldestVersion)
|
||||
: oldestVersion(oldestVersion), newestVersion(oldestVersion) {
|
||||
explicit ReferenceImpl(int64_t oldestVersion) : oldestVersion(oldestVersion) {
|
||||
writeVersionMap[""] = oldestVersion;
|
||||
}
|
||||
void check(const ConflictSet::ReadRange *reads, ConflictSet::Result *results,
|
||||
int count) const {
|
||||
for (int i = 0; i < count; ++i) {
|
||||
if (reads[i].readVersion < oldestVersion ||
|
||||
reads[i].readVersion < newestVersion - 2e9) {
|
||||
if (reads[i].readVersion < oldestVersion) {
|
||||
results[i] = ConflictSet::TooOld;
|
||||
continue;
|
||||
}
|
||||
@@ -497,8 +406,6 @@ struct ReferenceImpl {
|
||||
}
|
||||
void addWrites(const ConflictSet::WriteRange *writes, int count,
|
||||
int64_t writeVersion) {
|
||||
assert(writeVersion >= newestVersion);
|
||||
newestVersion = writeVersion;
|
||||
for (int i = 0; i < count; ++i) {
|
||||
auto begin =
|
||||
std::string((const char *)writes[i].begin.p, writes[i].begin.len);
|
||||
@@ -518,12 +425,11 @@ struct ReferenceImpl {
|
||||
}
|
||||
|
||||
void setOldestVersion(int64_t oldestVersion) {
|
||||
assert(oldestVersion >= this->oldestVersion);
|
||||
assert(oldestVersion >= oldestVersion);
|
||||
this->oldestVersion = oldestVersion;
|
||||
}
|
||||
|
||||
int64_t oldestVersion;
|
||||
int64_t newestVersion;
|
||||
std::map<std::string, int64_t> writeVersionMap;
|
||||
};
|
||||
|
||||
@@ -564,18 +470,6 @@ inline std::string printable(std::span<const uint8_t> key) {
|
||||
return printable(std::string_view((const char *)key.data(), key.size()));
|
||||
}
|
||||
|
||||
inline const char *resultToStr(ConflictSet::Result r) {
|
||||
switch (r) {
|
||||
case ConflictSet::Commit:
|
||||
return "commit";
|
||||
case ConflictSet::Conflict:
|
||||
return "conflict";
|
||||
case ConflictSet::TooOld:
|
||||
return "too old";
|
||||
}
|
||||
abort();
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
template <class ConflictSetImpl> struct TestDriver {
|
||||
@@ -583,22 +477,30 @@ template <class ConflictSetImpl> struct TestDriver {
|
||||
explicit TestDriver(const uint8_t *data, size_t size)
|
||||
: arbitrary({data, size}) {}
|
||||
|
||||
int64_t oldestVersion = arbitrary.next();
|
||||
int64_t writeVersion = oldestVersion;
|
||||
int64_t writeVersion = 0;
|
||||
int64_t oldestVersion = 0;
|
||||
ConflictSetImpl cs{oldestVersion};
|
||||
ReferenceImpl refImpl{oldestVersion};
|
||||
|
||||
constexpr static auto kMaxKeySuffixLen = 8;
|
||||
constexpr static auto kMaxKeyLen = 64;
|
||||
|
||||
bool ok = true;
|
||||
|
||||
const int prefixLen = arbitrary.bounded(512);
|
||||
const int prefixByte = arbitrary.randT<uint8_t>();
|
||||
static const char *resultToStr(ConflictSet::Result r) {
|
||||
switch (r) {
|
||||
case ConflictSet::Commit:
|
||||
return "commit";
|
||||
case ConflictSet::Conflict:
|
||||
return "conflict";
|
||||
case ConflictSet::TooOld:
|
||||
return "too old";
|
||||
}
|
||||
abort();
|
||||
}
|
||||
|
||||
// Call until it returns true, for "done". Check internal invariants etc
|
||||
// between calls to next.
|
||||
bool next() {
|
||||
assert(cs.getBytes() >= 0);
|
||||
if (!arbitrary.hasEntropy()) {
|
||||
return true;
|
||||
}
|
||||
@@ -606,8 +508,7 @@ template <class ConflictSetImpl> struct TestDriver {
|
||||
{
|
||||
int numPointWrites = arbitrary.bounded(100);
|
||||
int numRangeWrites = arbitrary.bounded(100);
|
||||
int64_t v = (writeVersion += arbitrary.bounded(10) ? arbitrary.bounded(10)
|
||||
: arbitrary.next());
|
||||
int64_t v = ++writeVersion;
|
||||
auto *writes =
|
||||
new (arena) ConflictSet::WriteRange[numPointWrites + numRangeWrites];
|
||||
auto keys = set<std::string_view>(arena);
|
||||
@@ -615,10 +516,9 @@ template <class ConflictSetImpl> struct TestDriver {
|
||||
if (!arbitrary.hasEntropy()) {
|
||||
return true;
|
||||
}
|
||||
int keyLen = prefixLen + arbitrary.bounded(kMaxKeySuffixLen);
|
||||
int keyLen = arbitrary.bounded(kMaxKeyLen);
|
||||
auto *begin = new (arena) uint8_t[keyLen];
|
||||
memset(begin, prefixByte, prefixLen);
|
||||
arbitrary.randomBytes(begin + prefixLen, keyLen - prefixLen);
|
||||
arbitrary.randomBytes(begin, keyLen);
|
||||
keys.insert(std::string_view((const char *)begin, keyLen));
|
||||
}
|
||||
|
||||
@@ -649,41 +549,33 @@ template <class ConflictSetImpl> struct TestDriver {
|
||||
}
|
||||
#if DEBUG_VERBOSE && !defined(NDEBUG)
|
||||
if (writes[i].end.len == 0) {
|
||||
fprintf(stderr, "Write: {%s}\n", printable(writes[i].begin).c_str());
|
||||
fprintf(stderr, "Write: {%s} -> %" PRId64 "\n",
|
||||
printable(writes[i].begin).c_str(), writeVersion);
|
||||
} else {
|
||||
fprintf(stderr, "Write: [%s, %s)\n",
|
||||
fprintf(stderr, "Write: [%s, %s) -> %" PRId64 "\n",
|
||||
printable(writes[i].begin).c_str(),
|
||||
printable(writes[i].end).c_str());
|
||||
printable(writes[i].end).c_str(), writeVersion);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
assert(iter == keys.end());
|
||||
assert(i == numPointWrites + numRangeWrites);
|
||||
|
||||
#if DEBUG_VERBOSE && !defined(NDEBUG)
|
||||
fprintf(stderr, "Write @ %" PRId64 "\n", v);
|
||||
#endif
|
||||
|
||||
CALLGRIND_START_INSTRUMENTATION;
|
||||
cs.addWrites(writes, numPointWrites + numRangeWrites, v);
|
||||
CALLGRIND_STOP_INSTRUMENTATION;
|
||||
|
||||
refImpl.addWrites(writes, numPointWrites + numRangeWrites, v);
|
||||
|
||||
oldestVersion +=
|
||||
arbitrary.bounded(10) ? arbitrary.bounded(10) : arbitrary.next();
|
||||
oldestVersion = std::min(oldestVersion, writeVersion);
|
||||
oldestVersion = std::max<int64_t>(writeVersion - arbitrary.bounded(10),
|
||||
oldestVersion);
|
||||
cs.setOldestVersion(oldestVersion);
|
||||
refImpl.setOldestVersion(oldestVersion);
|
||||
}
|
||||
{
|
||||
int numPointReads = arbitrary.bounded(100);
|
||||
int numRangeReads = arbitrary.bounded(100);
|
||||
|
||||
int64_t v = std::max<int64_t>(writeVersion - (arbitrary.bounded(10)
|
||||
? arbitrary.bounded(10)
|
||||
: arbitrary.next()),
|
||||
0);
|
||||
int64_t v = std::max<int64_t>(writeVersion - arbitrary.bounded(10), 0);
|
||||
auto *reads =
|
||||
new (arena) ConflictSet::ReadRange[numPointReads + numRangeReads];
|
||||
auto keys = set<std::string_view>(arena);
|
||||
@@ -691,10 +583,9 @@ template <class ConflictSetImpl> struct TestDriver {
|
||||
if (!arbitrary.hasEntropy()) {
|
||||
return true;
|
||||
}
|
||||
int keyLen = prefixLen + arbitrary.bounded(kMaxKeySuffixLen);
|
||||
int keyLen = arbitrary.bounded(kMaxKeyLen);
|
||||
auto *begin = new (arena) uint8_t[keyLen];
|
||||
memset(begin, prefixByte, prefixLen);
|
||||
arbitrary.randomBytes(begin + prefixLen, keyLen - prefixLen);
|
||||
arbitrary.randomBytes(begin, keyLen);
|
||||
keys.insert(std::string_view((const char *)begin, keyLen));
|
||||
}
|
||||
|
||||
@@ -725,12 +616,12 @@ template <class ConflictSetImpl> struct TestDriver {
|
||||
reads[i].readVersion = v;
|
||||
#if DEBUG_VERBOSE && !defined(NDEBUG)
|
||||
if (reads[i].end.len == 0) {
|
||||
fprintf(stderr, "Read: {%s} @ %" PRId64 "\n",
|
||||
printable(reads[i].begin).c_str(), reads[i].readVersion);
|
||||
fprintf(stderr, "Read: {%s} @ %d\n",
|
||||
printable(reads[i].begin).c_str(), int(reads[i].readVersion));
|
||||
} else {
|
||||
fprintf(stderr, "Read: [%s, %s) @ %" PRId64 "\n",
|
||||
fprintf(stderr, "Read: [%s, %s) @ %d\n",
|
||||
printable(reads[i].begin).c_str(),
|
||||
printable(reads[i].end).c_str(), reads[i].readVersion);
|
||||
printable(reads[i].end).c_str(), int(reads[i].readVersion));
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@@ -741,60 +632,32 @@ template <class ConflictSetImpl> struct TestDriver {
|
||||
auto *results2 =
|
||||
new (arena) ConflictSet::Result[numPointReads + numRangeReads];
|
||||
|
||||
#ifdef THREAD_TEST
|
||||
auto *results3 =
|
||||
new (arena) ConflictSet::Result[numPointReads + numRangeReads];
|
||||
std::latch ready{1};
|
||||
std::thread thread2{[&]() {
|
||||
ready.count_down();
|
||||
cs.check(reads, results3, numPointReads + numRangeReads);
|
||||
}};
|
||||
ready.wait();
|
||||
#endif
|
||||
|
||||
CALLGRIND_START_INSTRUMENTATION;
|
||||
cs.check(reads, results1, numPointReads + numRangeReads);
|
||||
CALLGRIND_STOP_INSTRUMENTATION;
|
||||
|
||||
refImpl.check(reads, results2, numPointReads + numRangeReads);
|
||||
|
||||
auto compareResults = [reads](ConflictSet::Result *results1,
|
||||
ConflictSet::Result *results2, int count) {
|
||||
for (int i = 0; i < count; ++i) {
|
||||
if (results1[i] != results2[i]) {
|
||||
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(
|
||||
stderr,
|
||||
"Expected %s, got %s for read of [%s, %s) at version %" PRId64
|
||||
"\n",
|
||||
resultToStr(results2[i]), resultToStr(results1[i]),
|
||||
printable(reads[i].begin).c_str(),
|
||||
printable(reads[i].end).c_str(), reads[i].readVersion);
|
||||
}
|
||||
return false;
|
||||
for (int i = 0; i < numPointReads + numRangeReads; ++i) {
|
||||
if (results1[i] != results2[i]) {
|
||||
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(
|
||||
stderr,
|
||||
"Expected %s, got %s for read of [%s, %s) at version %" PRId64
|
||||
"\n",
|
||||
resultToStr(results2[i]), resultToStr(results1[i]),
|
||||
printable(reads[i].begin).c_str(),
|
||||
printable(reads[i].end).c_str(), reads[i].readVersion);
|
||||
}
|
||||
ok = false;
|
||||
return true;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
if (!compareResults(results1, results2, numPointReads + numRangeReads)) {
|
||||
ok = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
#ifdef THREAD_TEST
|
||||
thread2.join();
|
||||
if (!compareResults(results3, results2, numPointReads + numRangeReads)) {
|
||||
ok = false;
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
46
Jenkinsfile
vendored
46
Jenkinsfile
vendored
@@ -36,40 +36,6 @@ pipeline {
|
||||
sh 'pre-commit run --all-files --show-diff-on-failure'
|
||||
}
|
||||
}
|
||||
stage('Clang') {
|
||||
agent {
|
||||
dockerfile {
|
||||
args '-v /home/jenkins/ccache:/ccache'
|
||||
reuseNode true
|
||||
}
|
||||
}
|
||||
steps {
|
||||
CleanBuildAndTest("")
|
||||
recordIssues(tools: [clang()])
|
||||
}
|
||||
}
|
||||
stage('SIMD fallback') {
|
||||
agent {
|
||||
dockerfile {
|
||||
args '-v /home/jenkins/ccache:/ccache'
|
||||
reuseNode true
|
||||
}
|
||||
}
|
||||
steps {
|
||||
CleanBuildAndTest("-DUSE_SIMD_FALLBACK=ON")
|
||||
}
|
||||
}
|
||||
stage('32-bit versions') {
|
||||
agent {
|
||||
dockerfile {
|
||||
args '-v /home/jenkins/ccache:/ccache'
|
||||
reuseNode true
|
||||
}
|
||||
}
|
||||
steps {
|
||||
CleanBuildAndTest("-DUSE_32_BIT_VERSIONS=ON")
|
||||
}
|
||||
}
|
||||
stage('Release [gcc]') {
|
||||
agent {
|
||||
dockerfile {
|
||||
@@ -78,7 +44,7 @@ pipeline {
|
||||
}
|
||||
}
|
||||
steps {
|
||||
CleanBuildAndTest("-DCMAKE_C_COMPILER=gcc -DCMAKE_CXX_COMPILER=g++ -DCMAKE_CXX_FLAGS=-DNVALGRIND")
|
||||
CleanBuildAndTest("-DCMAKE_C_COMPILER=gcc -DCMAKE_CXX_COMPILER=g++ -DCMAKE_BUILD_TYPE=Release")
|
||||
recordIssues(tools: [gcc()])
|
||||
sh '''
|
||||
cd build
|
||||
@@ -100,7 +66,7 @@ pipeline {
|
||||
}
|
||||
}
|
||||
steps {
|
||||
CleanBuildAndTest("-DCMAKE_TOOLCHAIN_FILE=../aarch64-toolchain.cmake -DCMAKE_CXX_FLAGS=-DNVALGRIND")
|
||||
CleanBuildAndTest("-DCMAKE_TOOLCHAIN_FILE=../aarch64-toolchain.cmake")
|
||||
sh '''
|
||||
cd build
|
||||
cpack -G DEB
|
||||
@@ -119,13 +85,9 @@ pipeline {
|
||||
steps {
|
||||
CleanBuildAndTest("-DCMAKE_C_COMPILER=gcc -DCMAKE_CXX_COMPILER=g++ -DCMAKE_C_FLAGS=--coverage -DCMAKE_CXX_FLAGS=--coverage -DCMAKE_BUILD_TYPE=Debug")
|
||||
sh '''
|
||||
gcovr -f ConflictSet.cpp --cobertura > build/coverage.xml
|
||||
'''
|
||||
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
|
||||
gcovr --exclude '.*third_party.*' --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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
112
README.md
112
README.md
@@ -2,56 +2,54 @@ A data structure for optimistic concurrency control on ranges of bitwise-lexicog
|
||||
|
||||
Intended to replace FoundationDB's skip list.
|
||||
|
||||
Hardware for all benchmarks is a mac m1 2020.
|
||||
|
||||
# FoundationDB's benchmark
|
||||
|
||||
## Skip list
|
||||
|
||||
```
|
||||
New conflict set: 1.957 sec
|
||||
0.639 Mtransactions/sec
|
||||
2.555 Mkeys/sec
|
||||
Detect only: 1.845 sec
|
||||
0.678 Mtransactions/sec
|
||||
2.710 Mkeys/sec
|
||||
Skiplist only: 1.263 sec
|
||||
0.990 Mtransactions/sec
|
||||
3.960 Mkeys/sec
|
||||
New conflict set: 2.404 sec
|
||||
0.520 Mtransactions/sec
|
||||
2.080 Mkeys/sec
|
||||
Detect only: 2.266 sec
|
||||
0.552 Mtransactions/sec
|
||||
2.207 Mkeys/sec
|
||||
Skiplist only: 1.594 sec
|
||||
0.784 Mtransactions/sec
|
||||
3.137 Mkeys/sec
|
||||
Performance counters:
|
||||
Build: 0.0546
|
||||
Add: 0.0563
|
||||
Detect: 1.84
|
||||
D.Sort: 0.412
|
||||
D.Combine: 0.0141
|
||||
D.CheckRead: 0.671
|
||||
D.CheckIntraBatch: 0.0068
|
||||
D.MergeWrite: 0.592
|
||||
D.RemoveBefore: 0.146
|
||||
Build: 0.071
|
||||
Add: 0.0641
|
||||
Detect: 2.27
|
||||
D.Sort: 0.44
|
||||
D.Combine: 0.018
|
||||
D.CheckRead: 0.855
|
||||
D.CheckIntraBatch: 0.00903
|
||||
D.MergeWrite: 0.739
|
||||
D.RemoveBefore: 0.201
|
||||
```
|
||||
|
||||
## Radix tree (this implementation)
|
||||
|
||||
```
|
||||
New conflict set: 1.366 sec
|
||||
0.915 Mtransactions/sec
|
||||
3.660 Mkeys/sec
|
||||
Detect only: 1.248 sec
|
||||
1.002 Mtransactions/sec
|
||||
4.007 Mkeys/sec
|
||||
Skiplist only: 0.573 sec
|
||||
2.182 Mtransactions/sec
|
||||
8.730 Mkeys/sec
|
||||
New conflict set: 1.743 sec
|
||||
0.717 Mtransactions/sec
|
||||
2.869 Mkeys/sec
|
||||
Detect only: 1.611 sec
|
||||
0.776 Mtransactions/sec
|
||||
3.103 Mkeys/sec
|
||||
Skiplist only: 0.919 sec
|
||||
1.360 Mtransactions/sec
|
||||
5.440 Mkeys/sec
|
||||
Performance counters:
|
||||
Build: 0.0594
|
||||
Add: 0.0572
|
||||
Detect: 1.25
|
||||
D.Sort: 0.418
|
||||
D.Combine: 0.0149
|
||||
D.CheckRead: 0.232
|
||||
D.CheckIntraBatch: 0.0067
|
||||
D.MergeWrite: 0.341
|
||||
D.RemoveBefore: 0.232
|
||||
Build: 0.0657
|
||||
Add: 0.0628
|
||||
Detect: 1.61
|
||||
D.Sort: 0.442
|
||||
D.Combine: 0.0178
|
||||
D.CheckRead: 0.395
|
||||
D.CheckIntraBatch: 0.00776
|
||||
D.MergeWrite: 0.524
|
||||
D.RemoveBefore: 0.221
|
||||
```
|
||||
|
||||
# Our benchmark
|
||||
@@ -60,27 +58,25 @@ Performance counters:
|
||||
|
||||
| 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`
|
||||
| 270.07 | 3,702,706.03 | 0.4% | 0.01 | `point reads`
|
||||
| 285.76 | 3,499,437.03 | 1.5% | 0.01 | `prefix reads`
|
||||
| 532.54 | 1,877,794.90 | 0.7% | 0.01 | `range reads`
|
||||
| 528.50 | 1,892,132.94 | 0.7% | 0.01 | `point writes`
|
||||
| 516.53 | 1,935,978.22 | 0.9% | 0.01 | `prefix writes`
|
||||
| 303.34 | 3,296,630.84 | 3.6% | 0.05 | `range writes`
|
||||
| 502.88 | 1,988,553.24 | 2.0% | 0.01 | `monotonic increasing point writes`
|
||||
|
||||
## Radix tree (this implementation)
|
||||
|
||||
| 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`
|
||||
| 14.52 | 68,850,842.99 | 1.2% | 0.01 | `point reads`
|
||||
| 60.89 | 16,422,538.22 | 1.5% | 0.01 | `prefix reads`
|
||||
| 226.89 | 4,407,362.98 | 0.5% | 0.01 | `range reads`
|
||||
| 22.99 | 43,498,198.49 | 0.2% | 0.01 | `point writes`
|
||||
| 50.51 | 19,799,864.54 | 1.0% | 0.01 | `prefix writes`
|
||||
| 82.50 | 12,121,212.12 | 2.6% | 0.03 | `range writes`
|
||||
| 119.94 | 8,337,354.54 | 2.1% | 0.01 | `monotonic increasing point writes`
|
||||
|
||||
# "Real data" test
|
||||
|
||||
@@ -89,13 +85,13 @@ Point queries only, best of three runs. Gc ratio is the ratio of time spent doin
|
||||
## skip list
|
||||
|
||||
```
|
||||
Check: 11.3385 seconds, 329.718 MB/s, Add: 5.35612 seconds, 131.072 MB/s, Gc ratio: 45.7173%
|
||||
Check: 12.7863 seconds, 292.384 MB/s, Add: 19.8276 seconds, 35.4071 MB/s, Gc ratio: 23.5314%
|
||||
```
|
||||
|
||||
## radix tree
|
||||
|
||||
```
|
||||
Check: 2.48583 seconds, 1503.93 MB/s, Add: 2.12768 seconds, 329.954 MB/s, Gc ratio: 41.7943%
|
||||
Check: 3.60187 seconds, 1037.94 MB/s, Add: 3.03958 seconds, 230.966 MB/s, Gc ratio: 52.3876%
|
||||
```
|
||||
|
||||
## hash table
|
||||
@@ -103,5 +99,5 @@ Check: 2.48583 seconds, 1503.93 MB/s, Add: 2.12768 seconds, 329.954 MB/s, Gc rat
|
||||
(The hash table implementation doesn't work on range queries, and its purpose is to provide an idea of how fast point queries can be)
|
||||
|
||||
```
|
||||
Check: 1.83386 seconds, 2038.6 MB/s, Add: 0.601411 seconds, 1167.32 MB/s, Gc ratio: 48.9776%
|
||||
```
|
||||
Check: 2.15925 seconds, 1731.4 MB/s, Add: 1.08519 seconds, 646.926 MB/s, Gc ratio: 52.1526%
|
||||
```
|
@@ -1,18 +1,13 @@
|
||||
#include <ConflictSet.h>
|
||||
|
||||
#include <cerrno>
|
||||
#include <chrono>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <fcntl.h>
|
||||
#include <string_view>
|
||||
#include <sys/mman.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
#include <vector>
|
||||
|
||||
using namespace weaselab;
|
||||
|
||||
double now() {
|
||||
return std::chrono::duration_cast<std::chrono::nanoseconds>(
|
||||
std::chrono::steady_clock::now().time_since_epoch())
|
||||
@@ -33,7 +28,7 @@ constexpr inline size_t rightAlign(size_t offset, size_t alignment) {
|
||||
|
||||
int main(int argc, const char **argv) {
|
||||
// Use with this dataset https://snap.stanford.edu/data/memetracker9.html
|
||||
// Preprocess the files with `sed -i'' '/^Q/d'`
|
||||
// Preprocess the files with `sed -i '' '/^Q/d'`
|
||||
|
||||
double checkTime = 0;
|
||||
double addTime = 0;
|
||||
@@ -45,8 +40,6 @@ int main(int argc, const char **argv) {
|
||||
int64_t version = 0;
|
||||
double timer = 0;
|
||||
|
||||
int64_t peakMemory = 0;
|
||||
|
||||
for (int i = 1; i < argc; ++i) {
|
||||
int fd = open(argv[i], O_RDONLY);
|
||||
struct stat st;
|
||||
@@ -116,10 +109,6 @@ int main(int argc, const char **argv) {
|
||||
write = {};
|
||||
reads.clear();
|
||||
|
||||
if (cs.getBytes() > peakMemory) {
|
||||
peakMemory = cs.getBytes();
|
||||
}
|
||||
|
||||
timer = now();
|
||||
cs.setOldestVersion(version - 10000);
|
||||
gcTime += now() - timer;
|
||||
@@ -129,9 +118,8 @@ int main(int argc, const char **argv) {
|
||||
close(fd);
|
||||
}
|
||||
|
||||
printf("Check: %g seconds, %g MB/s, Add: %g seconds, %g MB/s, Gc ratio: "
|
||||
"%g%%, Peak idle memory: %g\n",
|
||||
checkTime, checkBytes / checkTime * 1e-6, addTime,
|
||||
addBytes / addTime * 1e-6, gcTime / (gcTime + addTime) * 1e2,
|
||||
double(peakMemory));
|
||||
}
|
||||
printf(
|
||||
"Check: %g seconds, %g MB/s, Add: %g seconds, %g MB/s, Gc ratio: %g%%\n",
|
||||
checkTime, checkBytes / checkTime * 1e-6, addTime,
|
||||
addBytes / addTime * 1e-6, gcTime / (gcTime + addTime) * 1e2);
|
||||
}
|
121
SkipList.cpp
121
SkipList.cpp
@@ -16,8 +16,6 @@
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
* This source code is modified to compile outside of FoundationDB
|
||||
*/
|
||||
|
||||
#include "ConflictSet.h"
|
||||
@@ -151,7 +149,7 @@ private:
|
||||
setMaxVersion(level, v);
|
||||
}
|
||||
|
||||
void destroy() { safe_free(this, getNodeSize()); }
|
||||
void destroy() { free(this); }
|
||||
|
||||
private:
|
||||
int getNodeSize() const {
|
||||
@@ -270,21 +268,13 @@ public:
|
||||
}
|
||||
|
||||
explicit SkipList(Version version = 0) {
|
||||
#if DEBUG_VERBOSE
|
||||
fprintf(stderr, "skip_list: create\n");
|
||||
#endif
|
||||
header = Node::create(StringRef(), MaxLevels - 1);
|
||||
for (int l = 0; l < MaxLevels; l++) {
|
||||
header->setNext(l, nullptr);
|
||||
header->setMaxVersion(l, version);
|
||||
}
|
||||
}
|
||||
~SkipList() {
|
||||
#if DEBUG_VERBOSE
|
||||
fprintf(stderr, "skip_list: destroy\n");
|
||||
#endif
|
||||
destroy();
|
||||
}
|
||||
~SkipList() { destroy(); }
|
||||
SkipList(SkipList &&other) noexcept : header(other.header) {
|
||||
other.header = nullptr;
|
||||
}
|
||||
@@ -577,8 +567,7 @@ struct SkipListConflictSet {};
|
||||
|
||||
struct __attribute__((visibility("hidden"))) ConflictSet::Impl {
|
||||
Impl(int64_t oldestVersion)
|
||||
: oldestVersion(oldestVersion), newestVersion(oldestVersion),
|
||||
skipList(oldestVersion) {}
|
||||
: oldestVersion(oldestVersion), skipList(oldestVersion) {}
|
||||
void check(const ConflictSet::ReadRange *reads, ConflictSet::Result *results,
|
||||
int count) const {
|
||||
Arena arena;
|
||||
@@ -593,8 +582,7 @@ struct __attribute__((visibility("hidden"))) ConflictSet::Impl {
|
||||
}
|
||||
skipList.detectConflicts(ranges, count, results);
|
||||
for (int i = 0; i < count; ++i) {
|
||||
if (reads[i].readVersion < oldestVersion ||
|
||||
reads[i].readVersion < newestVersion - 2e9) {
|
||||
if (reads[i].readVersion < oldestVersion) {
|
||||
results[i] = TooOld;
|
||||
}
|
||||
}
|
||||
@@ -602,8 +590,6 @@ struct __attribute__((visibility("hidden"))) ConflictSet::Impl {
|
||||
|
||||
void addWrites(const ConflictSet::WriteRange *writes, int count,
|
||||
int64_t writeVersion) {
|
||||
assert(writeVersion >= newestVersion);
|
||||
newestVersion = writeVersion;
|
||||
Arena arena;
|
||||
const int stringCount = count * 2;
|
||||
|
||||
@@ -617,15 +603,11 @@ struct __attribute__((visibility("hidden"))) ConflictSet::Impl {
|
||||
for (int s = stripes - 1; s >= 0; s--) {
|
||||
for (int i = 0; i * 2 < ss; ++i) {
|
||||
const auto &w = writes[s * stripeSize / 2 + i];
|
||||
#if DEBUG_VERBOSE
|
||||
printf("Write begin: %s\n", printable(w.begin).c_str());
|
||||
fflush(stdout);
|
||||
#endif
|
||||
values[i * 2] = {w.begin.p, size_t(w.begin.len)};
|
||||
values[i * 2 + 1] = w.end.len > 0
|
||||
? StringRef{w.end.p, size_t(w.end.len)}
|
||||
: keyAfter(arena, values[i * 2]);
|
||||
keyUpdates += 3;
|
||||
keyUpdates += 2;
|
||||
}
|
||||
skipList.find(values, fingers, temp, ss);
|
||||
skipList.addConflictRanges(fingers, ss / 2, writeVersion);
|
||||
@@ -634,96 +616,46 @@ struct __attribute__((visibility("hidden"))) ConflictSet::Impl {
|
||||
}
|
||||
|
||||
void setOldestVersion(int64_t oldestVersion) {
|
||||
assert(oldestVersion >= this->oldestVersion);
|
||||
this->oldestVersion = oldestVersion;
|
||||
SkipList::Finger finger;
|
||||
int temp;
|
||||
std::span<const uint8_t> key = removalKey;
|
||||
skipList.find(&key, &finger, &temp, 1);
|
||||
skipList.removeBefore(oldestVersion, finger, std::exchange(keyUpdates, 10));
|
||||
skipList.removeBefore(oldestVersion, finger, std::exchange(keyUpdates, 0));
|
||||
removalArena = Arena();
|
||||
removalKey = copyToArena(
|
||||
removalArena, {finger.getValue().data(), finger.getValue().size()});
|
||||
}
|
||||
|
||||
int64_t totalBytes = 0;
|
||||
|
||||
private:
|
||||
int64_t keyUpdates = 10;
|
||||
int64_t keyUpdates = 0;
|
||||
Arena removalArena;
|
||||
std::span<const uint8_t> removalKey;
|
||||
int64_t oldestVersion;
|
||||
int64_t newestVersion;
|
||||
SkipList skipList;
|
||||
};
|
||||
|
||||
// Internal entry points. Public entry points should just delegate to these
|
||||
|
||||
void internal_check(ConflictSet::Impl *impl,
|
||||
const ConflictSet::ReadRange *reads,
|
||||
ConflictSet::Result *results, int count) {
|
||||
impl->check(reads, results, count);
|
||||
}
|
||||
void internal_addWrites(ConflictSet::Impl *impl,
|
||||
const ConflictSet::WriteRange *writes, int count,
|
||||
int64_t writeVersion) {
|
||||
mallocBytesDelta = 0;
|
||||
impl->addWrites(writes, count, writeVersion);
|
||||
impl->totalBytes += mallocBytesDelta;
|
||||
#if SHOW_MEMORY
|
||||
if (impl->totalBytes != mallocBytes) {
|
||||
abort();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void internal_setOldestVersion(ConflictSet::Impl *impl, int64_t oldestVersion) {
|
||||
mallocBytesDelta = 0;
|
||||
impl->setOldestVersion(oldestVersion);
|
||||
impl->totalBytes += mallocBytesDelta;
|
||||
#if SHOW_MEMORY
|
||||
if (impl->totalBytes != mallocBytes) {
|
||||
abort();
|
||||
}
|
||||
#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;
|
||||
}
|
||||
|
||||
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 ConflictSet::check(const ReadRange *reads, Result *results,
|
||||
int count) const {
|
||||
internal_check(impl, reads, results, count);
|
||||
return impl->check(reads, results, count);
|
||||
}
|
||||
|
||||
void ConflictSet::addWrites(const WriteRange *writes, int count,
|
||||
int64_t writeVersion) {
|
||||
internal_addWrites(impl, writes, count, writeVersion);
|
||||
return impl->addWrites(writes, count, writeVersion);
|
||||
}
|
||||
|
||||
void ConflictSet::setOldestVersion(int64_t oldestVersion) {
|
||||
internal_setOldestVersion(impl, oldestVersion);
|
||||
return impl->setOldestVersion(oldestVersion);
|
||||
}
|
||||
|
||||
int64_t ConflictSet::getBytes() const { return internal_getBytes(impl); }
|
||||
|
||||
ConflictSet::ConflictSet(int64_t oldestVersion)
|
||||
: impl(internal_create(oldestVersion)) {}
|
||||
: impl(new (safe_malloc(sizeof(Impl))) Impl{oldestVersion}) {}
|
||||
|
||||
ConflictSet::~ConflictSet() {
|
||||
if (impl) {
|
||||
internal_destroy(impl);
|
||||
impl->~Impl();
|
||||
free(impl);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -744,36 +676,25 @@ extern "C" {
|
||||
__attribute__((__visibility__("default"))) void
|
||||
ConflictSet_check(void *cs, const ConflictSet_ReadRange *reads,
|
||||
ConflictSet_Result *results, int count) {
|
||||
internal_check((ConflictSet::Impl *)cs, reads, results, count);
|
||||
((ConflictSet::Impl *)cs)->check(reads, results, count);
|
||||
}
|
||||
__attribute__((__visibility__("default"))) void
|
||||
ConflictSet_addWrites(void *cs, const ConflictSet_WriteRange *writes, int count,
|
||||
int64_t writeVersion) {
|
||||
internal_addWrites((ConflictSet::Impl *)cs, writes, count, writeVersion);
|
||||
((ConflictSet::Impl *)cs)->addWrites(writes, count, writeVersion);
|
||||
}
|
||||
__attribute__((__visibility__("default"))) void
|
||||
ConflictSet_setOldestVersion(void *cs, int64_t oldestVersion) {
|
||||
internal_setOldestVersion((ConflictSet::Impl *)cs, oldestVersion);
|
||||
((ConflictSet::Impl *)cs)->setOldestVersion(oldestVersion);
|
||||
}
|
||||
__attribute__((__visibility__("default"))) void *
|
||||
ConflictSet_create(int64_t oldestVersion) {
|
||||
return internal_create(oldestVersion);
|
||||
return new (safe_malloc(sizeof(ConflictSet::Impl)))
|
||||
ConflictSet::Impl{oldestVersion};
|
||||
}
|
||||
__attribute__((__visibility__("default"))) void ConflictSet_destroy(void *cs) {
|
||||
internal_destroy((ConflictSet::Impl *)cs);
|
||||
}
|
||||
__attribute__((__visibility__("default"))) int64_t
|
||||
ConflictSet_getBytes(void *cs) {
|
||||
return internal_getBytes((ConflictSet::Impl *)cs);
|
||||
using Impl = ConflictSet::Impl;
|
||||
((Impl *)cs)->~Impl();
|
||||
free(cs);
|
||||
}
|
||||
}
|
||||
|
||||
#if SHOW_MEMORY
|
||||
struct __attribute__((visibility("default"))) PeakPrinter {
|
||||
~PeakPrinter() {
|
||||
printf("--- skip_list ---\n");
|
||||
printf("malloc bytes: %g\n", double(mallocBytes));
|
||||
printf("Peak malloc bytes: %g\n", double(peakMallocBytes));
|
||||
}
|
||||
} peakPrinter;
|
||||
#endif
|
@@ -5,6 +5,7 @@
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
for (int i = 1; i < argc; ++i) {
|
||||
printf("Running: %s\n", argv[i]);
|
||||
std::ifstream t(argv[i], std::ios::binary);
|
||||
std::stringstream buffer;
|
||||
buffer << t.rdbuf();
|
||||
|
@@ -1,8 +0,0 @@
|
||||
__stack_chk_fail@GLIBC_2.17
|
||||
__stack_chk_guard@GLIBC_2.17
|
||||
abort@GLIBC_2.17
|
||||
free@GLIBC_2.17
|
||||
malloc@GLIBC_2.17
|
||||
memcpy@GLIBC_2.17
|
||||
memmove@GLIBC_2.17
|
||||
memset@GLIBC_2.17
|
@@ -1,17 +0,0 @@
|
||||
_ConflictSet_addWrites
|
||||
_ConflictSet_check
|
||||
_ConflictSet_create
|
||||
_ConflictSet_destroy
|
||||
_ConflictSet_getBytes
|
||||
_ConflictSet_setOldestVersion
|
||||
__ZN8weaselab11ConflictSet16setOldestVersionEx
|
||||
__ZN8weaselab11ConflictSet9addWritesEPKNS0_10WriteRangeEix
|
||||
__ZN8weaselab11ConflictSetC1EOS0_
|
||||
__ZN8weaselab11ConflictSetC1Ex
|
||||
__ZN8weaselab11ConflictSetC2EOS0_
|
||||
__ZN8weaselab11ConflictSetC2Ex
|
||||
__ZN8weaselab11ConflictSetD1Ev
|
||||
__ZN8weaselab11ConflictSetD2Ev
|
||||
__ZN8weaselab11ConflictSetaSEOS0_
|
||||
__ZNK8weaselab11ConflictSet5checkEPKNS0_9ReadRangeEPNS0_6ResultEi
|
||||
__ZNK8weaselab11ConflictSet8getBytesEv
|
@@ -1,10 +0,0 @@
|
||||
___stack_chk_fail
|
||||
___stack_chk_guard
|
||||
__tlv_bootstrap
|
||||
_abort
|
||||
_bzero
|
||||
_free
|
||||
_malloc
|
||||
_memcpy
|
||||
_memmove
|
||||
dyld_stub_binder
|
138
conflict_set.py
138
conflict_set.py
@@ -1,138 +0,0 @@
|
||||
import ctypes
|
||||
import enum
|
||||
import os
|
||||
|
||||
from typing import Optional
|
||||
|
||||
|
||||
class _Key(ctypes.Structure):
|
||||
_fields_ = [("p", ctypes.POINTER(ctypes.c_ubyte)), ("len", ctypes.c_int)]
|
||||
|
||||
|
||||
class ReadRange(ctypes.Structure):
|
||||
_fields_ = [
|
||||
("begin", _Key),
|
||||
("end", _Key),
|
||||
("readVersion", ctypes.c_int64),
|
||||
]
|
||||
|
||||
|
||||
class WriteRange(ctypes.Structure):
|
||||
_fields_ = [("begin", _Key), ("end", _Key)]
|
||||
|
||||
|
||||
class Result(enum.Enum):
|
||||
COMMIT = 0
|
||||
CONFLICT = 1
|
||||
TOO_OLD = 2
|
||||
|
||||
|
||||
def write(begin: bytes, end: Optional[bytes] = None) -> WriteRange:
|
||||
b = (ctypes.c_ubyte * len(begin)).from_buffer(bytearray(begin))
|
||||
|
||||
if end is None:
|
||||
e = (ctypes.c_ubyte * 0)()
|
||||
else:
|
||||
e = (ctypes.c_ubyte * len(end)).from_buffer(bytearray(end))
|
||||
return WriteRange(_Key(b, len(b)), _Key(e, len(e)))
|
||||
|
||||
|
||||
def read(version: int, begin: bytes, end: Optional[bytes] = None) -> ReadRange:
|
||||
b = (ctypes.c_ubyte * len(begin)).from_buffer(bytearray(begin))
|
||||
if end is None:
|
||||
e = (ctypes.c_ubyte * 0)()
|
||||
else:
|
||||
e = (ctypes.c_ubyte * len(end)).from_buffer(bytearray(end))
|
||||
return ReadRange(_Key(b, len(b)), _Key(e, len(e)), version)
|
||||
|
||||
|
||||
class ConflictSet:
|
||||
def __init__(
|
||||
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):
|
||||
self._lib.ConflictSet_addWrites(
|
||||
self.p, (WriteRange * len(writes))(*writes), len(writes), version
|
||||
)
|
||||
|
||||
def check(self, *reads: ReadRange) -> list[Result]:
|
||||
r = (ctypes.c_int * len(reads))()
|
||||
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:
|
||||
self._lib.ConflictSet_setOldestVersion(self.p, version)
|
||||
|
||||
def getBytes(self) -> int:
|
||||
return self._lib.ConflictSet_getBytes(self.p)
|
||||
|
||||
def __enter__(self):
|
||||
return self
|
||||
|
||||
def close(self) -> None:
|
||||
if self.p is not None:
|
||||
self._lib.ConflictSet_destroy(self.p)
|
||||
self.p = None
|
||||
|
||||
def __exit__(self, exception_type, exception_value, exception_traceback):
|
||||
self.close()
|
@@ -7,7 +7,6 @@ int main(void) {
|
||||
ConflictSet_WriteRange w;
|
||||
ConflictSet_Result result;
|
||||
ConflictSet_ReadRange r;
|
||||
int64_t bytes;
|
||||
w.begin.p = (const uint8_t *)"0000";
|
||||
w.begin.len = 4;
|
||||
w.end.len = 0;
|
||||
@@ -18,8 +17,6 @@ int main(void) {
|
||||
r.readVersion = 0;
|
||||
ConflictSet_check(cs, &r, &result, 1);
|
||||
assert(result == ConflictSet_Conflict);
|
||||
bytes = ConflictSet_getBytes(cs);
|
||||
assert(bytes > 0);
|
||||
ConflictSet_destroy(cs);
|
||||
return 0;
|
||||
}
|
||||
|
@@ -2,8 +2,6 @@
|
||||
|
||||
#include <cassert>
|
||||
|
||||
using namespace weaselab;
|
||||
|
||||
int main(void) {
|
||||
ConflictSet cs(0);
|
||||
ConflictSet::WriteRange w;
|
||||
@@ -19,6 +17,4 @@ int main(void) {
|
||||
r.readVersion = 0;
|
||||
cs.check(&r, &result, 1);
|
||||
assert(result == ConflictSet::Conflict);
|
||||
int64_t bytes = cs.getBytes();
|
||||
assert(bytes > 0);
|
||||
}
|
||||
|
Binary file not shown.
Binary file not shown.
BIN
corpus/00b4e0cdd5068101b8f48c1b36231c17f3676ac5
Normal file
BIN
corpus/00b4e0cdd5068101b8f48c1b36231c17f3676ac5
Normal file
Binary file not shown.
BIN
corpus/01150f441c877c707fe702462898362cceae6460
Normal file
BIN
corpus/01150f441c877c707fe702462898362cceae6460
Normal file
Binary file not shown.
Binary file not shown.
BIN
corpus/0138ac02488d80160576017569441a18117ead68
Normal file
BIN
corpus/0138ac02488d80160576017569441a18117ead68
Normal file
Binary file not shown.
Binary file not shown.
BIN
corpus/025ef475f367232e50ed8e8fdb7281d020c52acb
Normal file
BIN
corpus/025ef475f367232e50ed8e8fdb7281d020c52acb
Normal file
Binary file not shown.
Binary file not shown.
BIN
corpus/029f2e02c9fe87087698366d5261a67d7937da09
Normal file
BIN
corpus/029f2e02c9fe87087698366d5261a67d7937da09
Normal file
Binary file not shown.
Binary file not shown.
BIN
corpus/040003b4346a59a841eaf37b535b5923131df006
Normal file
BIN
corpus/040003b4346a59a841eaf37b535b5923131df006
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
corpus/06dc924ad9a3c75e8f1875fedccb05c427288e18
Normal file
BIN
corpus/06dc924ad9a3c75e8f1875fedccb05c427288e18
Normal file
Binary file not shown.
BIN
corpus/0701128041c1d79cf9444469b650ef38e06ab789
Normal file
BIN
corpus/0701128041c1d79cf9444469b650ef38e06ab789
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
corpus/07b9ae1792ae558cacc33266ef7fa049a0a8e622
Normal file
BIN
corpus/07b9ae1792ae558cacc33266ef7fa049a0a8e622
Normal file
Binary file not shown.
BIN
corpus/07c2bdbb7e3a47259cdcad87b87afc3255dc5401
Normal file
BIN
corpus/07c2bdbb7e3a47259cdcad87b87afc3255dc5401
Normal file
Binary file not shown.
BIN
corpus/08ab604dc84dacdc600dce29fc7c50c6917e4cd1
Normal file
BIN
corpus/08ab604dc84dacdc600dce29fc7c50c6917e4cd1
Normal file
Binary file not shown.
BIN
corpus/08eaebb53504575dab864e549526fa0d8570464f
Normal file
BIN
corpus/08eaebb53504575dab864e549526fa0d8570464f
Normal file
Binary file not shown.
Binary file not shown.
BIN
corpus/09cb9651d7411650c187fa0b55023ae8cce88b7a
Normal file
BIN
corpus/09cb9651d7411650c187fa0b55023ae8cce88b7a
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
corpus/0a739c08bf30e991da30660e77f4d848d54359c3
Normal file
BIN
corpus/0a739c08bf30e991da30660e77f4d848d54359c3
Normal file
Binary file not shown.
BIN
corpus/0ad1eeedfbc3d0a68f9388da658d26930fe78d36
Normal file
BIN
corpus/0ad1eeedfbc3d0a68f9388da658d26930fe78d36
Normal file
Binary file not shown.
Binary file not shown.
BIN
corpus/0beee6c2311ec2195fd0bc4d6fe0590bc07aa4ca
Normal file
BIN
corpus/0beee6c2311ec2195fd0bc4d6fe0590bc07aa4ca
Normal file
Binary file not shown.
BIN
corpus/0c67b0a645896ab95dc84b221a9446e988c68a5a
Normal file
BIN
corpus/0c67b0a645896ab95dc84b221a9446e988c68a5a
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,4 +0,0 @@
|
||||
*
|
||||
|
||||
|
||||
|
BIN
corpus/0f0a60e83062d65d2b87f91e03e12b29293fe780
Normal file
BIN
corpus/0f0a60e83062d65d2b87f91e03e12b29293fe780
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
corpus/1098f0fac9f0fa6a7620aa87240d055d86b67b93
Normal file
BIN
corpus/1098f0fac9f0fa6a7620aa87240d055d86b67b93
Normal file
Binary file not shown.
BIN
corpus/10e43abe8d8b969b8f49d552d208e2910475bc61
Normal file
BIN
corpus/10e43abe8d8b969b8f49d552d208e2910475bc61
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
corpus/12464d960b5478378c27b628d3b992a2a989eb79
Normal file
BIN
corpus/12464d960b5478378c27b628d3b992a2a989eb79
Normal file
Binary file not shown.
BIN
corpus/125c7247bd125131489683fa2dfb24ebf426c812
Normal file
BIN
corpus/125c7247bd125131489683fa2dfb24ebf426c812
Normal file
Binary file not shown.
Binary file not shown.
BIN
corpus/158951a44e359d1c3cd54670bc047ce6f93c6a4c
Normal file
BIN
corpus/158951a44e359d1c3cd54670bc047ce6f93c6a4c
Normal file
Binary file not shown.
BIN
corpus/15b3ada9dee0cd4b716b3ab4001f527a806d1a79
Normal file
BIN
corpus/15b3ada9dee0cd4b716b3ab4001f527a806d1a79
Normal file
Binary file not shown.
BIN
corpus/15e5a21d0f2e8bef963ea5b2a2bec2e3da35b4f8
Normal file
BIN
corpus/15e5a21d0f2e8bef963ea5b2a2bec2e3da35b4f8
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
corpus/168f93a0b8cd5f12cc99f38e7771b35e33c3a275
Normal file
BIN
corpus/168f93a0b8cd5f12cc99f38e7771b35e33c3a275
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
corpus/18783fa17e9d0846aac7eb948d46d5d40f74abf1
Normal file
BIN
corpus/18783fa17e9d0846aac7eb948d46d5d40f74abf1
Normal file
Binary file not shown.
Binary file not shown.
BIN
corpus/19d1aa334c6d6aa7636cc21643f070b653cf560d
Normal file
BIN
corpus/19d1aa334c6d6aa7636cc21643f070b653cf560d
Normal file
Binary file not shown.
BIN
corpus/1aef7988d42ddb277f54ca784affb1bd8518ffac
Normal file
BIN
corpus/1aef7988d42ddb277f54ca784affb1bd8518ffac
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
corpus/1f9ddfb127c67562e2c36e1129c42d53dcd95e86
Normal file
BIN
corpus/1f9ddfb127c67562e2c36e1129c42d53dcd95e86
Normal file
Binary file not shown.
BIN
corpus/1fb4d7136e53dd7458e2a63e910527458619b9de
Normal file
BIN
corpus/1fb4d7136e53dd7458e2a63e910527458619b9de
Normal file
Binary file not shown.
Binary file not shown.
@@ -1,2 +0,0 @@
|
||||
*
|
||||
|
BIN
corpus/23dd777a66376e6d174517a59bdb6286b5c3f1e5
Normal file
BIN
corpus/23dd777a66376e6d174517a59bdb6286b5c3f1e5
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
corpus/270cfa1be8fa2cfae12ee3aac31cfebeda8ba166
Normal file
BIN
corpus/270cfa1be8fa2cfae12ee3aac31cfebeda8ba166
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
corpus/29a0944e2a9b6f60270a178405809a0b8809d183
Normal file
BIN
corpus/29a0944e2a9b6f60270a178405809a0b8809d183
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
corpus/2a280463b90a5d751988ad136b3db466155ca196
Normal file
BIN
corpus/2a280463b90a5d751988ad136b3db466155ca196
Normal file
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