6 Commits

Author SHA1 Message Date
fac7968405 Bound memory, and disable free list for now
All checks were successful
Tests / Release [gcc] total: 827, passed: 827
GNU C Compiler (gcc) |Total|New|Outstanding|Fixed|Trend |:-:|:-:|:-:|:-:|:-: |1|0|1|0|:zzz:
Tests / Release [gcc,aarch64] total: 826, passed: 826
Tests / Coverage total: 825, passed: 825
weaselab/conflict-set/pipeline/head This commit looks good
CC #9
2024-03-11 17:11:37 -07:00
e3f6fbe955 Create a Node0 when splitting existing partial key 2024-03-11 16:22:14 -07:00
219af68745 sizeof(Node0) also needs to be < kBytesPerKey
Also remove vestigial comment.

CC #9
2024-03-11 16:15:09 -07:00
52db15d8bd Check min number of children invariant
CC #9
2024-03-11 16:15:07 -07:00
b3b91ef860 Merge Node4 with one child into child if favorable
CC #9
2024-03-11 16:15:05 -07:00
13ee3c3a12 WIP downsize for Node{16,48,256} on erase
CC #9
2024-03-11 16:15:01 -07:00
593 changed files with 1164 additions and 2418 deletions

View File

@@ -1,2 +1,2 @@
CompileFlags: CompileFlags:
Add: [-DENABLE_MAIN, -UNDEBUG, -DENABLE_FUZZ, -DTHREAD_TEST, -fexceptions] Add: [-DENABLE_MAIN, -UNDEBUG, -DENABLE_FUZZ, -fexceptions]

1
.gitignore vendored
View File

@@ -1,3 +1,2 @@
.cache .cache
__pycache__
build build

View File

@@ -1,11 +1,11 @@
repos: repos:
- repo: https://github.com/pre-commit/mirrors-clang-format - repo: https://github.com/pre-commit/mirrors-clang-format
rev: 6d365699efc33b1b432eab5b4ae331a19e1857de # frozen: v18.1.2 rev: b111689e7b5cba60be3c62d5db2bd1357f4d36ca
hooks: hooks:
- id: clang-format - id: clang-format
exclude: ".*third_party/.*" exclude: ".*third_party/.*"
- repo: https://github.com/cheshirekow/cmake-format-precommit - repo: https://github.com/cheshirekow/cmake-format-precommit
rev: e2c2116d86a80e72e7146a06e68b7c228afc6319 # frozen: v0.6.13 rev: e2c2116d86a80e72e7146a06e68b7c228afc6319
hooks: hooks:
- id: cmake-format - id: cmake-format
- repo: local - repo: local
@@ -13,7 +13,7 @@ repos:
- id: debug verbose check - id: debug verbose check
name: disallow checking in DEBUG_VERBOSE=1 name: disallow checking in DEBUG_VERBOSE=1
description: 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 language: pygrep
types: [c++] types: [c++]
- repo: local - repo: local
@@ -21,14 +21,6 @@ repos:
- id: debug verbose check - id: debug verbose check
name: disallow checking in SHOW_MEMORY=1 name: disallow checking in SHOW_MEMORY=1
description: 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 language: pygrep
types: [c++] 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

View File

@@ -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); std::span<uint8_t>(new (arena) uint8_t[key.size() + 1], key.size() + 1);
memcpy(r.data(), key.data(), key.size()); memcpy(r.data(), key.data(), key.size());
r[key.size()] = 0; 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) { 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); auto r = std::span<uint8_t>(new (arena) uint8_t[index + 1], index + 1);
memcpy(r.data(), key.data(), index + 1); memcpy(r.data(), key.data(), index + 1);
r[r.size() - 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() { void benchConflictSet() {

View File

@@ -1,28 +1,15 @@
cmake_minimum_required(VERSION 3.18) cmake_minimum_required(VERSION 3.18)
project( project(
conflict-set conflict_set
VERSION 0.0.4 VERSION 0.0.1
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})
include(CMakePushCheckState)
include(CheckCXXCompilerFlag)
include(CheckIncludeFileCXX)
include(CheckCXXSourceCompiles)
set(DEFAULT_BUILD_TYPE "Release") 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) if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
message( message(
STATUS STATUS
@@ -35,33 +22,16 @@ if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
"MinSizeRel" "RelWithDebInfo") "MinSizeRel" "RelWithDebInfo")
endif() endif()
add_compile_options(-fdata-sections -ffunction-sections -Wswitch-enum add_compile_options(-fdata-sections -ffunction-sections)
-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_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)
# 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_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) if(APPLE)
add_link_options(-Wl,-dead_strip) add_link_options(-Wl,-dead_strip)
@@ -69,46 +39,32 @@ else()
add_link_options(-Wl,--gc-sections) add_link_options(-Wl,--gc-sections)
endif() endif()
if(EMSCRIPTEN) include(CheckIncludeFileCXX)
# https://github.com/emscripten-core/emscripten/issues/15377#issuecomment-1285167486 include(CMakePushCheckState)
add_link_options(-lnodefs.js -lnoderawfs.js)
add_link_options(-s ALLOW_MEMORY_GROWTH) 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() endif()
cmake_pop_check_state()
if(NOT USE_SIMD_FALLBACK) check_include_file_cxx("arm_neon.h" HAS_ARM_NEON)
cmake_push_check_state() if(HAS_ARM_NEON)
list(APPEND CMAKE_REQUIRED_FLAGS -msimd128) add_compile_definitions(HAS_ARM_NEON)
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()
endif() endif()
set(CMAKE_CXX_IMPLICIT_LINK_LIBRARIES "") set(CMAKE_CXX_IMPLICIT_LINK_LIBRARIES "")
add_library(${PROJECT_NAME}-object OBJECT ConflictSet.cpp) add_library(${PROJECT_NAME}_object OBJECT ConflictSet.cpp)
target_compile_options(${PROJECT_NAME}-object PRIVATE -fno-exceptions target_compile_options(${PROJECT_NAME}_object PRIVATE -fPIC -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_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_BINARY_DIR}/radix_tree")
@@ -116,32 +72,24 @@ 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(NOT APPLE)
target_link_options(${PROJECT_NAME} PRIVATE target_link_options(${PROJECT_NAME} PRIVATE
LINKER:--version-script=${CMAKE_SOURCE_DIR}/linker.map) LINKER:--version-script=${CMAKE_SOURCE_DIR}/linker.map)
endif() endif()
add_library(${PROJECT_NAME}-static STATIC add_library(${PROJECT_NAME}_static STATIC
$<TARGET_OBJECTS:${PROJECT_NAME}-object>) $<TARGET_OBJECTS:${PROJECT_NAME}_object>)
if(NOT CMAKE_BUILD_TYPE STREQUAL Debug) 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() endif()
if(APPLE) if(NOT APPLE AND CMAKE_OBJCOPY)
add_custom_command( add_custom_command(
TARGET ${PROJECT_NAME}-static TARGET conflict_set_static
PRE_LINK
COMMAND ${CMAKE_SOURCE_DIR}/privatize_symbols_macos.sh
$<TARGET_OBJECTS:${PROJECT_NAME}-object>)
else()
add_custom_command(
TARGET ${PROJECT_NAME}-static
POST_BUILD POST_BUILD
COMMAND COMMAND
${CMAKE_OBJCOPY} ${CMAKE_OBJCOPY} --keep-global-symbols=${CMAKE_SOURCE_DIR}/symbols.txt
--keep-global-symbols=${CMAKE_SOURCE_DIR}/symbol-exports.txt $<TARGET_FILE:${PROJECT_NAME}_static>)
$<TARGET_FILE:${PROJECT_NAME}-static> || echo
"Proceeding with all symbols global in static library")
endif() endif()
set(TEST_FLAGS -Wall -Wextra -Wunreachable-code -Wpedantic -UNDEBUG) set(TEST_FLAGS -Wall -Wextra -Wunreachable-code -Wpedantic -UNDEBUG)
@@ -150,51 +98,30 @@ include(CTest)
if(BUILD_TESTING) if(BUILD_TESTING)
# corpus tests, which are tests curated by libfuzzer. The goal is to get broad # Shared library version of FoundationDB's skip list implementation
# coverage with a small number of tests. 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})
file(GLOB CORPUS_TESTS ${CMAKE_SOURCE_DIR}/corpus/*) # 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})
# 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_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})
# 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_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})
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) add_executable(conflict_set_main ConflictSet.cpp)
target_include_directories(conflict_set_main target_include_directories(conflict_set_main
PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include) PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include)
@@ -221,13 +148,12 @@ if(BUILD_TESTING)
endif() endif()
endif() endif()
# whitebox tests # corpus tests
file(GLOB CORPUS_TESTS ${CMAKE_SOURCE_DIR}/corpus/*)
add_executable(fuzz_driver ConflictSet.cpp FuzzTestDriver.cpp) add_executable(fuzz_driver ConflictSet.cpp FuzzTestDriver.cpp)
target_compile_options(fuzz_driver PRIVATE ${TEST_FLAGS}) 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_compile_definitions(fuzz_driver PRIVATE ENABLE_FUZZ)
target_include_directories(fuzz_driver target_include_directories(fuzz_driver
PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include) PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include)
@@ -236,47 +162,13 @@ if(BUILD_TESTING)
add_test(NAME conflict_set_fuzz_${hash} COMMAND fuzz_driver ${TEST}) add_test(NAME conflict_set_fuzz_${hash} COMMAND fuzz_driver ${TEST})
endforeach() 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) add_executable(driver TestDriver.cpp)
target_compile_options(driver PRIVATE ${TEST_FLAGS}) target_compile_options(driver PRIVATE ${TEST_FLAGS})
target_link_libraries(driver PRIVATE ${PROJECT_NAME}) 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 add_executable(driver_skip_list TestDriver.cpp)
# find. target_compile_options(driver_skip_list PRIVATE ${TEST_FLAGS})
if(NOT CMAKE_CROSSCOMPILING) target_link_libraries(driver_skip_list PRIVATE skip_list)
find_package(Python3 REQUIRED COMPONENTS Interpreter)
set_property(
DIRECTORY
APPEND
PROPERTY CMAKE_CONFIGURE_DEPENDS ${CMAKE_SOURCE_DIR}/test_conflict_set.py)
execute_process(
COMMAND ${Python3_EXECUTABLE} ${CMAKE_SOURCE_DIR}/test_conflict_set.py
list OUTPUT_VARIABLE SCRIPT_TESTS)
foreach(TEST ${SCRIPT_TESTS})
add_test(
NAME script_test_${TEST}
COMMAND ${Python3_EXECUTABLE} ${CMAKE_SOURCE_DIR}/test_conflict_set.py
test ${TEST} --build-dir ${CMAKE_BINARY_DIR})
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)
@@ -285,6 +177,12 @@ if(BUILD_TESTING)
$<TARGET_FILE:driver> ${CORPUS_TESTS}) $<TARGET_FILE:driver> ${CORPUS_TESTS})
endif() 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 # api smoke tests
# c90 # c90
@@ -305,45 +203,31 @@ if(BUILD_TESTING)
PROPERTIES CXX_STANDARD_REQUIRED ON) PROPERTIES CXX_STANDARD_REQUIRED ON)
add_test(NAME conflict_set_cxx_api_test COMMAND conflict_set_cxx_api_test) add_test(NAME conflict_set_cxx_api_test COMMAND conflict_set_cxx_api_test)
# symbol visibility tests if(NOT APPLE AND NOT CMAKE_BUILD_TYPE STREQUAL Debug)
if(NOT WASM AND NOT CMAKE_BUILD_TYPE STREQUAL Debug)
if(APPLE)
set(symbol_exports ${CMAKE_SOURCE_DIR}/apple-symbol-exports.txt)
set(symbol_imports ${CMAKE_SOURCE_DIR}/apple-symbol-imports.txt)
else()
set(symbol_exports ${CMAKE_SOURCE_DIR}/symbol-exports.txt)
if(CMAKE_SYSTEM_PROCESSOR STREQUAL aarch64)
set(symbol_imports ${CMAKE_SOURCE_DIR}/aarch64-symbol-imports.txt)
else()
set(symbol_imports ${CMAKE_SOURCE_DIR}/symbol-imports.txt)
endif()
endif()
add_test( add_test(
NAME conflict_set_shared_symbols NAME conflict_set_shared_symbols
COMMAND COMMAND ${CMAKE_SOURCE_DIR}/test_symbols.sh
${CMAKE_SOURCE_DIR}/test_symbols.sh $<TARGET_FILE:${PROJECT_NAME}> $<TARGET_FILE:${PROJECT_NAME}> ${CMAKE_SOURCE_DIR}/symbols.txt)
${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_SOURCE_DIR}/test_symbols.sh
$<TARGET_FILE:${PROJECT_NAME}-static> ${symbol_exports} $<TARGET_FILE:${PROJECT_NAME}_static> ${CMAKE_SOURCE_DIR}/symbols.txt)
${symbol_imports})
endif() endif()
# bench # bench
add_executable(conflict_set_bench Bench.cpp) add_executable(conflict_set_bench Bench.cpp)
target_link_libraries(conflict_set_bench PRIVATE ${PROJECT_NAME}) target_link_libraries(conflict_set_bench PRIVATE ${PROJECT_NAME})
set_target_properties(conflict_set_bench PROPERTIES SKIP_BUILD_RPATH ON) set_target_properties(conflict_set_bench PROPERTIES SKIP_BUILD_RPATH ON)
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)
endif() endif()
# packaging # packaging
set(CPACK_PACKAGE_CONTACT andrew@weaselab.dev) set(CPACK_PACKAGE_CONTACT andrew@weaselab.dev)
set(CMAKE_INSTALL_DEFAULT_COMPONENT_NAME all)
set(CPACK_PACKAGE_VENDOR "Weaselab") set(CPACK_PACKAGE_VENDOR "Weaselab")
set(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_CURRENT_SOURCE_DIR}/LICENSE") set(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_CURRENT_SOURCE_DIR}/LICENSE")
@@ -353,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_PACKAGE_ARCHITECTURE ${CMAKE_SYSTEM_PROCESSOR})
set(CPACK_RPM_SPEC_INSTALL_POST "/bin/true") # avoid stripping set(CPACK_RPM_SPEC_INSTALL_POST "/bin/true") # avoid stripping
set(CPACK_RPM_PACKAGE_LICENSE "Apache 2.0") 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_SOURCE_DIR}/README.md -o
${CMAKE_BINARY_DIR}/README.txt)
set(CPACK_RESOURCE_FILE_README ${CMAKE_BINARY_DIR}/README.txt)
endif()
configure_file(${CMAKE_SOURCE_DIR}/LICENSE ${CMAKE_BINARY_DIR}/LICENSE.txt
COPYONLY)
set(CPACK_RESOURCE_FILE_LICENSE ${CMAKE_BINARY_DIR}/LICENSE.txt)
endif()
include(CPack) include(CPack)
@@ -388,7 +248,7 @@ target_include_directories(
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_NAME}>) $<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_NAME}>)
target_include_directories( target_include_directories(
${PROJECT_NAME}-static ${PROJECT_NAME}_static
PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include> PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_NAME}>) $<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_NAME}>)
@@ -397,16 +257,13 @@ set_target_properties(
SOVERSION ${PROJECT_VERSION_MAJOR}) SOVERSION ${PROJECT_VERSION_MAJOR})
install( install(
TARGETS ${PROJECT_NAME} ${PROJECT_NAME}-static TARGETS ${PROJECT_NAME} ${PROJECT_NAME}_static
EXPORT ${PROJECT_NAME}Config EXPORT ConflictSetConfig
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
install(DIRECTORY include/ install(DIRECTORY include/
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_NAME}) DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_NAME})
install(EXPORT ${PROJECT_NAME}Config install(EXPORT ConflictSetConfig
DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/${PROJECT_NAME}/cmake) DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/ConflictSet/cmake)
cpack_add_component(all)

File diff suppressed because it is too large Load Diff

View File

@@ -2,25 +2,15 @@
#include <cstdint> #include <cstdint>
#include <fstream> #include <fstream>
#include <sstream> #include <sstream>
#include <thread>
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size); extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size);
int main(int argc, char **argv) { int main(int argc, char **argv) {
auto doTest = [&]() { 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(); LLVMFuzzerTestOneInput((const uint8_t *)str.data(), str.size());
LLVMFuzzerTestOneInput((const uint8_t *)str.data(), str.size()); }
}
};
#ifdef THREAD_TEST
std::thread thread2{doTest};
#endif
doTest();
#ifdef THREAD_TEST
thread2.join();
#endif
} }

View File

@@ -96,15 +96,13 @@ void ConflictSet::setOldestVersion(int64_t oldestVersion) {
return impl->setOldestVersion(oldestVersion); return impl->setOldestVersion(oldestVersion);
} }
int64_t ConflictSet::getBytes() const { return -1; }
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}) {}
ConflictSet::~ConflictSet() { ConflictSet::~ConflictSet() {
if (impl) { if (impl) {
impl->~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) { __attribute__((__visibility__("default"))) void ConflictSet_destroy(void *cs) {
using Impl = ConflictSet::Impl; using Impl = ConflictSet::Impl;
((Impl *)cs)->~Impl(); ((Impl *)cs)->~Impl();
safe_free(cs, sizeof(Impl)); free(cs);
}
__attribute__((__visibility__("default"))) int64_t
ConflictSet_getBytes(void *cs) {
using Impl = ConflictSet::Impl;
return -1;
} }
} }

View File

@@ -2,8 +2,6 @@
#include "ConflictSet.h" #include "ConflictSet.h"
using namespace weaselab;
#include <bit> #include <bit>
#include <cassert> #include <cassert>
#include <compare> #include <compare>
@@ -12,12 +10,10 @@ using namespace weaselab;
#include <cstdlib> #include <cstdlib>
#include <cstring> #include <cstring>
#include <inttypes.h> #include <inttypes.h>
#include <latch>
#include <map> #include <map>
#include <set> #include <set>
#include <span> #include <span>
#include <string> #include <string>
#include <thread>
#include <unordered_set> #include <unordered_set>
#include <utility> #include <utility>
#include <vector> #include <vector>
@@ -39,76 +35,16 @@ operator<=>(const std::span<const uint8_t> &lhs,
return lhs.size() <=> rhs.size(); 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 // 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. // want to exclude from coverage since it's only testing related.
// GCOVR_EXCL_START // 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) { __attribute__((always_inline)) inline void *safe_malloc(size_t s) {
mallocBytesDelta += s; if (void *p = malloc(s)) {
#if SHOW_MEMORY return p;
mallocBytes += s;
if (mallocBytes > peakMallocBytes) {
peakMallocBytes = mallocBytes;
} }
#endif abort();
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;
free(p);
#else
#ifndef NDEBUG
(char *&)p -= kMallocHeaderSize;
size_t expected;
memcpy(&expected, p, sizeof(expected));
assert(s == expected);
#endif
free(p);
#endif
} }
// ==================== BEGIN ARENA IMPL ==================== // ==================== BEGIN ARENA IMPL ====================
@@ -154,7 +90,6 @@ inline void *operator new[](size_t size, std::align_val_t align,
/// align must be a power of two /// align must be a power of two
template <class T> T *align_up(T *t, size_t align) { template <class T> T *align_up(T *t, size_t align) {
assert(std::popcount(align) == 1);
auto unaligned = uintptr_t(t); auto unaligned = uintptr_t(t);
auto aligned = (unaligned + align - 1) & ~(align - 1); auto aligned = (unaligned + align - 1) & ~(align - 1);
return reinterpret_cast<T *>(reinterpret_cast<char *>(t) + aligned - return reinterpret_cast<T *>(reinterpret_cast<char *>(t) + aligned -
@@ -163,7 +98,6 @@ template <class T> T *align_up(T *t, size_t align) {
/// align must be a power of two /// align must be a power of two
constexpr inline int align_up(uint32_t unaligned, uint32_t align) { constexpr inline int align_up(uint32_t unaligned, uint32_t align) {
assert(std::popcount(align) == 1);
return (unaligned + align - 1) & ~(align - 1); return (unaligned + align - 1) & ~(align - 1);
} }
@@ -179,10 +113,12 @@ struct Arena::ArenaImpl {
uint8_t *begin() { return reinterpret_cast<uint8_t *>(this + 1); } 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) { inline Arena::Arena(int initialSize) : impl(nullptr) {
if (initialSize > 0) { if (initialSize > 0) {
auto allocationSize = auto allocationSize = align_up(initialSize + sizeof(ArenaImpl), 16);
align_up(initialSize + sizeof(ArenaImpl), alignof(ArenaImpl));
impl = (Arena::ArenaImpl *)safe_malloc(allocationSize); impl = (Arena::ArenaImpl *)safe_malloc(allocationSize);
impl->prev = nullptr; impl->prev = nullptr;
impl->capacity = allocationSize - sizeof(ArenaImpl); impl->capacity = allocationSize - sizeof(ArenaImpl);
@@ -193,7 +129,7 @@ inline Arena::Arena(int initialSize) : impl(nullptr) {
inline void onDestroy(Arena::ArenaImpl *impl) { inline void onDestroy(Arena::ArenaImpl *impl) {
while (impl) { while (impl) {
auto *prev = impl->prev; auto *prev = impl->prev;
safe_free(impl, sizeof(Arena::ArenaImpl) + impl->capacity); free(impl);
impl = prev; impl = prev;
} }
} }
@@ -218,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 ? std::max<int>(sizeof(Arena::ArenaImpl),
arena.impl->capacity * 2) arena.impl->capacity * 2)
: 0)), : 0)),
alignof(Arena::ArenaImpl)); 16);
auto *impl = (Arena::ArenaImpl *)safe_malloc(allocationSize); auto *impl = (Arena::ArenaImpl *)safe_malloc(allocationSize);
impl->prev = arena.impl; impl->prev = arena.impl;
impl->capacity = allocationSize - sizeof(Arena::ArenaImpl); impl->capacity = allocationSize - sizeof(Arena::ArenaImpl);
@@ -413,6 +349,34 @@ inline uint32_t Arbitrary::bounded(uint32_t s) {
// ==================== END ARBITRARY IMPL ==================== // ==================== 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 { struct ReferenceImpl {
explicit ReferenceImpl(int64_t oldestVersion) : oldestVersion(oldestVersion) { explicit ReferenceImpl(int64_t oldestVersion) : oldestVersion(oldestVersion) {
writeVersionMap[""] = oldestVersion; writeVersionMap[""] = oldestVersion;
@@ -506,18 +470,6 @@ inline std::string printable(std::span<const uint8_t> key) {
return printable(std::string_view((const char *)key.data(), key.size())); 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 { namespace {
template <class ConflictSetImpl> struct TestDriver { template <class ConflictSetImpl> struct TestDriver {
@@ -530,12 +482,21 @@ template <class ConflictSetImpl> struct TestDriver {
ConflictSetImpl cs{oldestVersion}; ConflictSetImpl cs{oldestVersion};
ReferenceImpl refImpl{oldestVersion}; ReferenceImpl refImpl{oldestVersion};
constexpr static auto kMaxKeySuffixLen = 8; constexpr static auto kMaxKeyLen = 64;
bool ok = true; bool ok = true;
const int prefixLen = arbitrary.bounded(512); static const char *resultToStr(ConflictSet::Result r) {
const int prefixByte = arbitrary.randT<uint8_t>(); 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 // Call until it returns true, for "done". Check internal invariants etc
// between calls to next. // between calls to next.
@@ -555,10 +516,9 @@ template <class ConflictSetImpl> struct TestDriver {
if (!arbitrary.hasEntropy()) { if (!arbitrary.hasEntropy()) {
return true; return true;
} }
int keyLen = prefixLen + arbitrary.bounded(kMaxKeySuffixLen); int keyLen = arbitrary.bounded(kMaxKeyLen);
auto *begin = new (arena) uint8_t[keyLen]; auto *begin = new (arena) uint8_t[keyLen];
memset(begin, prefixByte, prefixLen); arbitrary.randomBytes(begin, keyLen);
arbitrary.randomBytes(begin + prefixLen, keyLen - prefixLen);
keys.insert(std::string_view((const char *)begin, keyLen)); keys.insert(std::string_view((const char *)begin, keyLen));
} }
@@ -623,10 +583,9 @@ template <class ConflictSetImpl> struct TestDriver {
if (!arbitrary.hasEntropy()) { if (!arbitrary.hasEntropy()) {
return true; return true;
} }
int keyLen = prefixLen + arbitrary.bounded(kMaxKeySuffixLen); int keyLen = arbitrary.bounded(kMaxKeyLen);
auto *begin = new (arena) uint8_t[keyLen]; auto *begin = new (arena) uint8_t[keyLen];
memset(begin, prefixByte, prefixLen); arbitrary.randomBytes(begin, keyLen);
arbitrary.randomBytes(begin + prefixLen, keyLen - prefixLen);
keys.insert(std::string_view((const char *)begin, keyLen)); keys.insert(std::string_view((const char *)begin, keyLen));
} }
@@ -673,60 +632,32 @@ template <class ConflictSetImpl> struct TestDriver {
auto *results2 = auto *results2 =
new (arena) ConflictSet::Result[numPointReads + numRangeReads]; 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; CALLGRIND_START_INSTRUMENTATION;
cs.check(reads, results1, numPointReads + numRangeReads); cs.check(reads, results1, numPointReads + numRangeReads);
CALLGRIND_STOP_INSTRUMENTATION; CALLGRIND_STOP_INSTRUMENTATION;
refImpl.check(reads, results2, numPointReads + numRangeReads); refImpl.check(reads, results2, numPointReads + numRangeReads);
for (int i = 0; i < numPointReads + numRangeReads; ++i) {
auto compareResults = [reads](ConflictSet::Result *results1, if (results1[i] != results2[i]) {
ConflictSet::Result *results2, int count) { if (reads[i].end.len == 0) {
for (int i = 0; i < count; ++i) { fprintf(stderr,
if (results1[i] != results2[i]) { "Expected %s, got %s for read of {%s} at version %" PRId64
if (reads[i].end.len == 0) { "\n",
fprintf(stderr, resultToStr(results2[i]), resultToStr(results1[i]),
"Expected %s, got %s for read of {%s} at version %" PRId64 printable(reads[i].begin).c_str(), reads[i].readVersion);
"\n", } else {
resultToStr(results2[i]), resultToStr(results1[i]), fprintf(
printable(reads[i].begin).c_str(), reads[i].readVersion); stderr,
} else { "Expected %s, got %s for read of [%s, %s) at version %" PRId64
fprintf( "\n",
stderr, resultToStr(results2[i]), resultToStr(results1[i]),
"Expected %s, got %s for read of [%s, %s) at version %" PRId64 printable(reads[i].begin).c_str(),
"\n", printable(reads[i].end).c_str(), reads[i].readVersion);
resultToStr(results2[i]), resultToStr(results1[i]),
printable(reads[i].begin).c_str(),
printable(reads[i].end).c_str(), reads[i].readVersion);
}
return false;
} }
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; return false;
} }

32
Jenkinsfile vendored
View File

@@ -36,29 +36,6 @@ pipeline {
sh 'pre-commit run --all-files --show-diff-on-failure' 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('Release [gcc]') { stage('Release [gcc]') {
agent { agent {
dockerfile { dockerfile {
@@ -67,7 +44,7 @@ pipeline {
} }
} }
steps { 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()]) recordIssues(tools: [gcc()])
sh ''' sh '''
cd build cd build
@@ -89,7 +66,7 @@ pipeline {
} }
} }
steps { steps {
CleanBuildAndTest("-DCMAKE_TOOLCHAIN_FILE=../aarch64-toolchain.cmake -DCMAKE_CXX_FLAGS=-DNVALGRIND") CleanBuildAndTest("-DCMAKE_TOOLCHAIN_FILE=../aarch64-toolchain.cmake")
sh ''' sh '''
cd build cd build
cpack -G DEB cpack -G DEB
@@ -108,12 +85,9 @@ 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")
sh ''' sh '''
gcovr -f ConflictSet.cpp --cobertura > build/coverage.xml 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 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
sh '''
gcovr -f ConflictSet.cpp --fail-under-line 100 > /dev/null
'''
} }
} }
} }

116
README.md
View File

@@ -2,83 +2,81 @@ A data structure for optimistic concurrency control on ranges of bitwise-lexicog
Intended to replace FoundationDB's skip list. Intended to replace FoundationDB's skip list.
Hardware for all benchmarks is a mac m1 2020.
# FoundationDB's benchmark # FoundationDB's benchmark
## Skip list ## Skip list
``` ```
New conflict set: 1.957 sec New conflict set: 2.404 sec
0.639 Mtransactions/sec 0.520 Mtransactions/sec
2.555 Mkeys/sec 2.080 Mkeys/sec
Detect only: 1.845 sec Detect only: 2.266 sec
0.678 Mtransactions/sec 0.552 Mtransactions/sec
2.710 Mkeys/sec 2.207 Mkeys/sec
Skiplist only: 1.263 sec Skiplist only: 1.594 sec
0.990 Mtransactions/sec 0.784 Mtransactions/sec
3.960 Mkeys/sec 3.137 Mkeys/sec
Performance counters: Performance counters:
Build: 0.0546 Build: 0.071
Add: 0.0563 Add: 0.0641
Detect: 1.84 Detect: 2.27
D.Sort: 0.412 D.Sort: 0.44
D.Combine: 0.0141 D.Combine: 0.018
D.CheckRead: 0.671 D.CheckRead: 0.855
D.CheckIntraBatch: 0.0068 D.CheckIntraBatch: 0.00903
D.MergeWrite: 0.592 D.MergeWrite: 0.739
D.RemoveBefore: 0.146 D.RemoveBefore: 0.201
``` ```
## Radix tree (this implementation) ## Radix tree (this implementation)
``` ```
New conflict set: 1.366 sec New conflict set: 1.743 sec
0.915 Mtransactions/sec 0.717 Mtransactions/sec
3.660 Mkeys/sec 2.869 Mkeys/sec
Detect only: 1.248 sec Detect only: 1.611 sec
1.002 Mtransactions/sec 0.776 Mtransactions/sec
4.007 Mkeys/sec 3.103 Mkeys/sec
Skiplist only: 0.573 sec Skiplist only: 0.919 sec
2.182 Mtransactions/sec 1.360 Mtransactions/sec
8.730 Mkeys/sec 5.440 Mkeys/sec
Performance counters: Performance counters:
Build: 0.0594 Build: 0.0657
Add: 0.0572 Add: 0.0628
Detect: 1.25 Detect: 1.61
D.Sort: 0.418 D.Sort: 0.442
D.Combine: 0.0149 D.Combine: 0.0178
D.CheckRead: 0.232 D.CheckRead: 0.395
D.CheckIntraBatch: 0.0067 D.CheckIntraBatch: 0.00776
D.MergeWrite: 0.341 D.MergeWrite: 0.524
D.RemoveBefore: 0.232 D.RemoveBefore: 0.221
``` ```
# Our benchmark # Our benchmark
## 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` | | 270.07 | 3,702,706.03 | 0.4% | 0.01 | `point reads`
| 260.16 | 3,843,784.65 | 0.1% | 0.01 | `prefix reads` | | 285.76 | 3,499,437.03 | 1.5% | 0.01 | `prefix reads`
| 493.35 | 2,026,953.19 | 0.1% | 0.01 | `range reads` | | 532.54 | 1,877,794.90 | 0.7% | 0.01 | `range reads`
| 462.05 | 2,164,289.23 | 0.6% | 0.01 | `point writes` | | 528.50 | 1,892,132.94 | 0.7% | 0.01 | `point writes`
| 448.19 | 2,231,205.25 | 0.9% | 0.01 | `prefix writes` | | 516.53 | 1,935,978.22 | 0.9% | 0.01 | `prefix writes`
| 255.83 | 3,908,845.72 | 1.5% | 0.02 | `range writes` | | 303.34 | 3,296,630.84 | 3.6% | 0.05 | `range writes`
| 582.63 | 1,716,349.02 | 1.3% | 0.01 | `monotonic increasing point writes` | | 502.88 | 1,988,553.24 | 2.0% | 0.01 | `monotonic increasing point writes`
## 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` | | 14.52 | 68,850,842.99 | 1.2% | 0.01 | `point reads`
| 58.43 | 17,115,612.57 | 0.1% | 0.01 | `prefix reads` | | 60.89 | 16,422,538.22 | 1.5% | 0.01 | `prefix reads`
| 216.09 | 4,627,766.60 | 0.2% | 0.01 | `range reads` | | 226.89 | 4,407,362.98 | 0.5% | 0.01 | `range reads`
| 28.35 | 35,267,567.72 | 0.2% | 0.01 | `point writes` | | 22.99 | 43,498,198.49 | 0.2% | 0.01 | `point writes`
| 43.43 | 23,026,226.17 | 0.2% | 0.01 | `prefix writes` | | 50.51 | 19,799,864.54 | 1.0% | 0.01 | `prefix writes`
| 50.00 | 20,000,000.00 | 0.0% | 0.01 | `range writes` | | 82.50 | 12,121,212.12 | 2.6% | 0.03 | `range writes`
| 92.38 | 10,824,863.69 | 4.1% | 0.01 | `monotonic increasing point writes` | | 119.94 | 8,337,354.54 | 2.1% | 0.01 | `monotonic increasing point writes`
# "Real data" test # "Real data" test
@@ -87,13 +85,13 @@ Point queries only, best of three runs. Gc ratio is the ratio of time spent doin
## skip list ## 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 ## 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 ## hash table
@@ -101,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) (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%
``` ```

View File

@@ -1,18 +1,13 @@
#include <ConflictSet.h> #include <ConflictSet.h>
#include <cerrno>
#include <chrono> #include <chrono>
#include <cstdio>
#include <cstring> #include <cstring>
#include <fcntl.h> #include <fcntl.h>
#include <string_view>
#include <sys/mman.h> #include <sys/mman.h>
#include <sys/stat.h> #include <sys/stat.h>
#include <unistd.h> #include <unistd.h>
#include <vector> #include <vector>
using namespace weaselab;
double now() { double now() {
return std::chrono::duration_cast<std::chrono::nanoseconds>( return std::chrono::duration_cast<std::chrono::nanoseconds>(
std::chrono::steady_clock::now().time_since_epoch()) 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) { int main(int argc, const char **argv) {
// Use with this dataset https://snap.stanford.edu/data/memetracker9.html // 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 checkTime = 0;
double addTime = 0; double addTime = 0;
@@ -45,8 +40,6 @@ int main(int argc, const char **argv) {
int64_t version = 0; int64_t version = 0;
double timer = 0; double timer = 0;
int64_t peakMemory = 0;
for (int i = 1; i < argc; ++i) { for (int i = 1; i < argc; ++i) {
int fd = open(argv[i], O_RDONLY); int fd = open(argv[i], O_RDONLY);
struct stat st; struct stat st;
@@ -116,10 +109,6 @@ int main(int argc, const char **argv) {
write = {}; write = {};
reads.clear(); reads.clear();
if (cs.getBytes() > peakMemory) {
peakMemory = cs.getBytes();
}
timer = now(); timer = now();
cs.setOldestVersion(version - 10000); cs.setOldestVersion(version - 10000);
gcTime += now() - timer; gcTime += now() - timer;
@@ -129,9 +118,8 @@ int main(int argc, const char **argv) {
close(fd); close(fd);
} }
printf("Check: %g seconds, %g MB/s, Add: %g seconds, %g MB/s, Gc ratio: " printf(
"%g%%, Peak idle memory: %g\n", "Check: %g seconds, %g MB/s, Add: %g seconds, %g MB/s, Gc ratio: %g%%\n",
checkTime, checkBytes / checkTime * 1e-6, addTime, checkTime, checkBytes / checkTime * 1e-6, addTime,
addBytes / addTime * 1e-6, gcTime / (gcTime + addTime) * 1e2, addBytes / addTime * 1e-6, gcTime / (gcTime + addTime) * 1e2);
double(peakMemory));
} }

View File

@@ -16,8 +16,6 @@
* 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"
@@ -151,7 +149,7 @@ private:
setMaxVersion(level, v); setMaxVersion(level, v);
} }
void destroy() { safe_free(this, getNodeSize()); } void destroy() { free(this); }
private: private:
int getNodeSize() const { int getNodeSize() const {
@@ -270,21 +268,13 @@ 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() { ~SkipList() { destroy(); }
#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;
} }
@@ -613,15 +603,11 @@ struct __attribute__((visibility("hidden"))) ConflictSet::Impl {
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 = 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] = {w.begin.p, size_t(w.begin.len)};
values[i * 2 + 1] = w.end.len > 0 values[i * 2 + 1] = w.end.len > 0
? StringRef{w.end.p, size_t(w.end.len)} ? StringRef{w.end.p, size_t(w.end.len)}
: keyAfter(arena, values[i * 2]); : keyAfter(arena, values[i * 2]);
keyUpdates += 3; keyUpdates += 2;
} }
skipList.find(values, fingers, temp, ss); skipList.find(values, fingers, temp, ss);
skipList.addConflictRanges(fingers, ss / 2, writeVersion); skipList.addConflictRanges(fingers, ss / 2, writeVersion);
@@ -635,16 +621,14 @@ struct __attribute__((visibility("hidden"))) ConflictSet::Impl {
int temp; int temp;
std::span<const uint8_t> key = removalKey; std::span<const uint8_t> key = removalKey;
skipList.find(&key, &finger, &temp, 1); skipList.find(&key, &finger, &temp, 1);
skipList.removeBefore(oldestVersion, finger, std::exchange(keyUpdates, 10)); skipList.removeBefore(oldestVersion, finger, std::exchange(keyUpdates, 0));
removalArena = Arena(); removalArena = Arena();
removalKey = copyToArena( removalKey = copyToArena(
removalArena, {finger.getValue().data(), finger.getValue().size()}); removalArena, {finger.getValue().data(), finger.getValue().size()});
} }
int64_t totalBytes = 0;
private: private:
int64_t keyUpdates = 10; int64_t keyUpdates = 0;
Arena removalArena; Arena removalArena;
std::span<const uint8_t> removalKey; std::span<const uint8_t> removalKey;
int64_t oldestVersion; int64_t oldestVersion;
@@ -653,44 +637,25 @@ private:
void ConflictSet::check(const ReadRange *reads, Result *results, void ConflictSet::check(const ReadRange *reads, Result *results,
int count) const { int count) const {
impl->check(reads, results, count); return impl->check(reads, results, count);
} }
void ConflictSet::addWrites(const WriteRange *writes, int count, void ConflictSet::addWrites(const WriteRange *writes, int count,
int64_t writeVersion) { int64_t writeVersion) {
mallocBytesDelta = 0; return impl->addWrites(writes, count, writeVersion);
impl->addWrites(writes, count, writeVersion);
impl->totalBytes += mallocBytesDelta;
#if SHOW_MEMORY
if (impl->totalBytes != mallocBytes) {
abort();
}
#endif
} }
void ConflictSet::setOldestVersion(int64_t oldestVersion) { void ConflictSet::setOldestVersion(int64_t oldestVersion) {
mallocBytesDelta = 0; return impl->setOldestVersion(oldestVersion);
impl->setOldestVersion(oldestVersion);
impl->totalBytes += mallocBytesDelta;
#if SHOW_MEMORY
if (impl->totalBytes != mallocBytes) {
abort();
}
#endif
} }
int64_t ConflictSet::getBytes() const { return impl->totalBytes; }
ConflictSet::ConflictSet(int64_t oldestVersion) ConflictSet::ConflictSet(int64_t oldestVersion)
: impl((mallocBytesDelta = 0, : impl(new (safe_malloc(sizeof(Impl))) Impl{oldestVersion}) {}
new(safe_malloc(sizeof(Impl))) Impl{oldestVersion})) {
impl->totalBytes += mallocBytesDelta;
}
ConflictSet::~ConflictSet() { ConflictSet::~ConflictSet() {
if (impl) { if (impl) {
impl->~Impl(); impl->~Impl();
safe_free(impl, sizeof(Impl)); free(impl);
} }
} }
@@ -716,54 +681,20 @@ ConflictSet_check(void *cs, const ConflictSet_ReadRange *reads,
__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) {
auto *impl = (ConflictSet::Impl *)cs; ((ConflictSet::Impl *)cs)->addWrites(writes, count, writeVersion);
mallocBytesDelta = 0;
impl->addWrites(writes, count, writeVersion);
impl->totalBytes += mallocBytesDelta;
#if SHOW_MEMORY
if (impl->totalBytes != mallocBytes) {
abort();
}
#endif
} }
__attribute__((__visibility__("default"))) void __attribute__((__visibility__("default"))) void
ConflictSet_setOldestVersion(void *cs, int64_t oldestVersion) { ConflictSet_setOldestVersion(void *cs, int64_t oldestVersion) {
auto *impl = (ConflictSet::Impl *)cs; ((ConflictSet::Impl *)cs)->setOldestVersion(oldestVersion);
mallocBytesDelta = 0;
impl->setOldestVersion(oldestVersion);
impl->totalBytes += mallocBytesDelta;
#if SHOW_MEMORY
if (impl->totalBytes != mallocBytes) {
abort();
}
#endif
} }
__attribute__((__visibility__("default"))) void * __attribute__((__visibility__("default"))) void *
ConflictSet_create(int64_t oldestVersion) { ConflictSet_create(int64_t oldestVersion) {
mallocBytesDelta = 0; return new (safe_malloc(sizeof(ConflictSet::Impl)))
auto *result = new (safe_malloc(sizeof(ConflictSet::Impl)))
ConflictSet::Impl{oldestVersion}; ConflictSet::Impl{oldestVersion};
result->totalBytes += mallocBytesDelta;
return result;
} }
__attribute__((__visibility__("default"))) void ConflictSet_destroy(void *cs) { __attribute__((__visibility__("default"))) void ConflictSet_destroy(void *cs) {
using Impl = ConflictSet::Impl; using Impl = ConflictSet::Impl;
((Impl *)cs)->~Impl(); ((Impl *)cs)->~Impl();
safe_free(cs, sizeof(Impl)); free(cs);
}
__attribute__((__visibility__("default"))) int64_t
ConflictSet_getBytes(void *cs) {
using Impl = ConflictSet::Impl;
return ((Impl *)cs)->totalBytes;
} }
} }
#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

View File

@@ -5,6 +5,7 @@
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) {
printf("Running: %s\n", argv[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();

View File

@@ -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

View File

@@ -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

View File

@@ -1,8 +0,0 @@
__tlv_bootstrap
_abort
_bzero
_free
_malloc
_memcpy
_memmove
dyld_stub_binder

View File

@@ -1,136 +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, *reads, r, 1)
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()

View File

@@ -7,7 +7,6 @@ int main(void) {
ConflictSet_WriteRange w; ConflictSet_WriteRange w;
ConflictSet_Result result; ConflictSet_Result result;
ConflictSet_ReadRange r; ConflictSet_ReadRange r;
int64_t bytes;
w.begin.p = (const uint8_t *)"0000"; w.begin.p = (const uint8_t *)"0000";
w.begin.len = 4; w.begin.len = 4;
w.end.len = 0; w.end.len = 0;
@@ -18,8 +17,6 @@ int main(void) {
r.readVersion = 0; r.readVersion = 0;
ConflictSet_check(cs, &r, &result, 1); ConflictSet_check(cs, &r, &result, 1);
assert(result == ConflictSet_Conflict); assert(result == ConflictSet_Conflict);
bytes = ConflictSet_getBytes(cs);
assert(bytes > 0);
ConflictSet_destroy(cs); ConflictSet_destroy(cs);
return 0; return 0;
} }

View File

@@ -2,8 +2,6 @@
#include <cassert> #include <cassert>
using namespace weaselab;
int main(void) { int main(void) {
ConflictSet cs(0); ConflictSet cs(0);
ConflictSet::WriteRange w; ConflictSet::WriteRange w;
@@ -19,6 +17,4 @@ int main(void) {
r.readVersion = 0; r.readVersion = 0;
cs.check(&r, &result, 1); cs.check(&r, &result, 1);
assert(result == ConflictSet::Conflict); assert(result == ConflictSet::Conflict);
int64_t bytes = cs.getBytes();
assert(bytes > 0);
} }

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.

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