49 Commits

Author SHA1 Message Date
71c39f9955 Opt-in to rpm/deb default package filenames
All checks were successful
Tests / Clang total: 1096, passed: 1096
Clang |Total|New|Outstanding|Fixed|Trend |:-:|:-:|:-:|:-:|:-: |0|0|0|0|:clap:
Tests / SIMD fallback total: 1096, passed: 1096
Tests / Release [gcc] total: 1096, passed: 1096
GNU C Compiler (gcc) |Total|New|Outstanding|Fixed|Trend |:-:|:-:|:-:|:-:|:-: |0|0|0|0|:clap:
Tests / Release [gcc,aarch64] total: 824, passed: 824
Tests / Coverage total: 823, passed: 823
weaselab/conflict-set/pipeline/head This commit looks good
2024-03-29 15:56:58 -07:00
8cc17158fd Fix preprocessing instructions for linux
All checks were successful
Tests / Clang total: 1096, passed: 1096
Clang |Total|New|Outstanding|Fixed|Trend |:-:|:-:|:-:|:-:|:-: |0|0|0|0|:clap:
Tests / SIMD fallback total: 1096, passed: 1096
Tests / Release [gcc] total: 1096, passed: 1096
GNU C Compiler (gcc) |Total|New|Outstanding|Fixed|Trend |:-:|:-:|:-:|:-:|:-: |0|0|0|0|:clap:
Tests / Release [gcc,aarch64] total: 824, passed: 824
Tests / Coverage total: 823, passed: 823
weaselab/conflict-set/pipeline/head This commit looks good
2024-03-28 14:58:29 -07:00
ab211c646a Apply compiler-appeasing syntax changes from Taoxi 2024-03-28 14:57:31 -07:00
7af961f141 Fix jenkins build
All checks were successful
Tests / Clang total: 1096, passed: 1096
Clang |Total|New|Outstanding|Fixed|Trend |:-:|:-:|:-:|:-:|:-: |0|0|0|0|:clap:
Tests / SIMD fallback total: 1096, passed: 1096
Tests / Release [gcc] total: 1096, passed: 1096
GNU C Compiler (gcc) |Total|New|Outstanding|Fixed|Trend |:-:|:-:|:-:|:-:|:-: |0|0|0|0|:clap:
Tests / Release [gcc,aarch64] total: 824, passed: 824
Tests / Coverage total: 823, passed: 823
weaselab/conflict-set/pipeline/head This commit looks good
2024-03-28 11:44:01 -07:00
a91df62608 Add USE_SIMD_FALLBACK build in jenkins
Some checks failed
Tests / Clang total: 1096, passed: 1096
Clang |Total|New|Outstanding|Fixed|Trend |:-:|:-:|:-:|:-:|:-: |0|0|0|0|:clap:
Tests / SIMD fallback total: 1096, passed: 1096
weaselab/conflict-set/pipeline/head There was a failure building this commit
2024-03-28 11:33:53 -07:00
0a1843a161 Add USE_SIMD_FALLBACK
All checks were successful
Tests / Clang total: 1096, passed: 1096
Clang |Total|New|Outstanding|Fixed|Trend |:-:|:-:|:-:|:-:|:-: |0|0|0|0|:clap:
Tests / Release [gcc] total: 1096, passed: 1096
GNU C Compiler (gcc) |Total|New|Outstanding|Fixed|Trend |:-:|:-:|:-:|:-:|:-: |0|0|0|0|:clap:
Tests / Release [gcc,aarch64] total: 824, passed: 824
Tests / Coverage total: 823, passed: 823
weaselab/conflict-set/pipeline/head This commit looks good
2024-03-28 11:12:50 -07:00
4edf0315d9 Find insertion point for Node16 with simd
Closes #13
2024-03-28 10:47:20 -07:00
14515e186a Update readme benchmarks again
All checks were successful
Tests / Clang total: 1096, passed: 1096
Clang |Total|New|Outstanding|Fixed|Trend |:-:|:-:|:-:|:-:|:-: |0|0|0|0|:clap:
Tests / Release [gcc] total: 1096, passed: 1096
GNU C Compiler (gcc) |Total|New|Outstanding|Fixed|Trend |:-:|:-:|:-:|:-:|:-: |0|0|0|0|:clap:
Tests / Release [gcc,aarch64] total: 824, passed: 824
Tests / Coverage total: 823, passed: 823
weaselab/conflict-set/pipeline/head This commit looks good
I think the last skiplisttest benchmark looked bad because I had vscode
+ firefox + ?? open
2024-03-27 16:47:36 -07:00
b0085df5ad Update symbols.txt
All checks were successful
Tests / Clang total: 1096, passed: 1096
Clang |Total|New|Outstanding|Fixed|Trend |:-:|:-:|:-:|:-:|:-: |0|0|0|0|:clap:
Tests / Release [gcc] total: 1096, passed: 1096
GNU C Compiler (gcc) |Total|New|Outstanding|Fixed|Trend |:-:|:-:|:-:|:-:|:-: |0|0|0|0|:clap:
Tests / Release [gcc,aarch64] total: 824, passed: 824
Tests / Coverage total: 823, passed: 823
weaselab/conflict-set/pipeline/head This commit looks good
2024-03-27 16:29:30 -07:00
76a7e17b29 Update readme benchmarks
Some checks failed
Tests / Clang total: 1096, failed: 2, passed: 1094
Tests / Release [gcc] total: 1096, failed: 2, passed: 1094
Tests / Release [gcc,aarch64] total: 824, failed: 2, passed: 822
Tests / Coverage total: 823, passed: 823
weaselab/conflict-set/pipeline/head There was a failure building this commit
I was incorrectly linking to an old build of conflict set, before the
conflict-set name change. I don't know what the regression is from, but
update the README for transparency now.
2024-03-27 16:12:45 -07:00
5cf43d1bfa Add weaselab namespace 2024-03-27 16:07:05 -07:00
25cc427ec5 Assert safe_free size is correct in debug builds
All checks were successful
Tests / Clang total: 1096, passed: 1096
Clang |Total|New|Outstanding|Fixed|Trend |:-:|:-:|:-:|:-:|:-: |0|0|0|0|:clap:
Tests / Release [gcc] total: 1096, passed: 1096
GNU C Compiler (gcc) |Total|New|Outstanding|Fixed|Trend |:-:|:-:|:-:|:-:|:-: |0|0|0|0|:clap:
Tests / Release [gcc,aarch64] total: 824, passed: 824
Tests / Coverage total: 823, passed: 823
weaselab/conflict-set/pipeline/head This commit looks good
Closes #16
2024-03-27 15:34:24 -07:00
c15c2e7b44 Remove redundant static assert
Closes #14
2024-03-27 12:41:45 -07:00
a4d1f91670 Update README benchmark after adding getBytes
All checks were successful
Tests / Clang total: 1096, passed: 1096
Clang |Total|New|Outstanding|Fixed|Trend |:-:|:-:|:-:|:-:|:-: |0|0|0|0|:clap:
Tests / Release [gcc] total: 1096, passed: 1096
GNU C Compiler (gcc) |Total|New|Outstanding|Fixed|Trend |:-:|:-:|:-:|:-:|:-: |0|0|0|0|:clap:
Tests / Release [gcc,aarch64] total: 824, passed: 824
Tests / Coverage total: 823, passed: 823
weaselab/conflict-set/pipeline/head This commit looks good
2024-03-20 16:26:05 -07:00
b7cdecaf71 Document thread-safety in terms of constness 2024-03-20 16:16:15 -07:00
cda28643a6 Fix ConflictSet_getBytes 2024-03-20 16:15:43 -07:00
cdb5360b9a Allow _GLOBAL_OFFSET_TABLE_ usage
All checks were successful
Tests / Clang total: 1096, passed: 1096
Clang |Total|New|Outstanding|Fixed|Trend |:-:|:-:|:-:|:-:|:-: |0|0|0|0|:clap:
Tests / Release [gcc] total: 1096, passed: 1096
GNU C Compiler (gcc) |Total|New|Outstanding|Fixed|Trend |:-:|:-:|:-:|:-:|:-: |0|0|0|0|:clap:
Tests / Release [gcc,aarch64] total: 824, passed: 824
Tests / Coverage total: 823, passed: 823
weaselab/conflict-set/pipeline/head This commit looks good
2024-03-20 12:39:18 -07:00
ef224a60f4 Allow use of __tls_get_addr
Some checks failed
Tests / Clang total: 1096, passed: 1096
Clang |Total|New|Outstanding|Fixed|Trend |:-:|:-:|:-:|:-:|:-: |0|0|0|0|:clap:
Tests / Release [gcc] total: 1096, failed: 1, passed: 1095
Tests / Release [gcc,aarch64] total: 824, passed: 824
Tests / Coverage total: 823, passed: 823
weaselab/conflict-set/pipeline/head There was a failure building this commit
2024-03-20 12:29:18 -07:00
6222b74787 Fix tests
Some checks failed
Tests / Clang total: 1096, failed: 2, passed: 1094
Tests / Release [gcc] total: 1096, failed: 2, passed: 1094
Tests / Release [gcc,aarch64] total: 824, passed: 824
Tests / Coverage total: 823, passed: 823
weaselab/conflict-set/pipeline/head There was a failure building this commit
Add the new symbol, and update the valgrind client request so that
Node::partialKeyCapacity is defined.
2024-03-20 12:21:59 -07:00
19edc6f78f Interface change! Add ConflictSet::getBytes
Some checks failed
Tests / Clang total: 1096, failed: 3, passed: 1093
Tests / Release [gcc] total: 1096, failed: 2, passed: 1094
Tests / Release [gcc,aarch64] total: 824, failed: 1, passed: 823
Tests / Coverage total: 823, failed: 1, passed: 822
weaselab/conflict-set/pipeline/head There was a failure building this commit
Closes #12
2024-03-20 12:11:34 -07:00
3f9d01c46a Users will now do find_package(conflict-set)
All checks were successful
Tests / Clang total: 1096, passed: 1096
Clang |Total|New|Outstanding|Fixed|Trend |:-:|:-:|:-:|:-:|:-: |0|0|0|0|:clap:
Tests / Release [gcc] total: 1096, passed: 1096
GNU C Compiler (gcc) |Total|New|Outstanding|Fixed|Trend |:-:|:-:|:-:|:-:|:-: |0|0|0|0|:clap:
Tests / Release [gcc,aarch64] total: 824, passed: 824
Tests / Coverage total: 823, passed: 823
weaselab/conflict-set/pipeline/head This commit looks good
2024-03-19 19:05:54 -07:00
db03c6f901 Fix invalid package name for debian
All checks were successful
Tests / Clang total: 1096, passed: 1096
Clang |Total|New|Outstanding|Fixed|Trend |:-:|:-:|:-:|:-:|:-: |0|0|0|0|:clap:
Tests / Release [gcc] total: 1096, passed: 1096
GNU C Compiler (gcc) |Total|New|Outstanding|Fixed|Trend |:-:|:-:|:-:|:-:|:-: |0|0|0|0|:clap:
Tests / Release [gcc,aarch64] total: 824, passed: 824
Tests / Coverage total: 823, passed: 823
weaselab/conflict-set/pipeline/head This commit looks good
2024-03-19 17:43:58 -07:00
c1698b040b Disable tsan for debug builds
All checks were successful
Tests / Clang total: 1096, passed: 1096
Clang |Total|New|Outstanding|Fixed|Trend |:-:|:-:|:-:|:-:|:-: |0|0|0|0|:clap:
Tests / Release [gcc] total: 1096, passed: 1096
GNU C Compiler (gcc) |Total|New|Outstanding|Fixed|Trend |:-:|:-:|:-:|:-:|:-: |0|0|0|0|:clap:
Tests / Release [gcc,aarch64] total: 824, passed: 824
Tests / Coverage total: 823, passed: 823
weaselab/conflict-set/pipeline/head This commit looks good
Too slow
2024-03-19 16:54:29 -07:00
2e08b54785 Update README.md with 1.0 benchmarks
Some checks reported errors
weaselab/conflict-set/pipeline/head Something is wrong with the build of this commit
Closes #7
2024-03-19 16:41:59 -07:00
aa6f237d50 Document and test thread safety properties
Some checks reported errors
Tests / Clang total: 1096, passed: 1096
Clang |Total|New|Outstanding|Fixed|Trend |:-:|:-:|:-:|:-:|:-: |0|0|0|0|:clap:
Tests / Release [gcc] total: 1096, passed: 1096
GNU C Compiler (gcc) |Total|New|Outstanding|Fixed|Trend |:-:|:-:|:-:|:-:|:-: |0|0|0|0|:clap:
Tests / Release [gcc,aarch64] total: 824, passed: 824
weaselab/conflict-set/pipeline/head Something is wrong with the build of this commit
Closes #2
2024-03-19 16:27:24 -07:00
becfd25139 De-templatize kUseFreeList
This results in smaller code. This is part of the "let the compiler be
in charge of inlining decisions" theme.
2024-03-19 15:12:31 -07:00
d78b36821b Remove more redundant nullptr checks 2024-03-19 15:05:34 -07:00
ce79b47fbe Revert 303b368fc5
This is a code-size / speed tradeoff. Maybe it's a good idea here, but
it's a bit weird to do this in some places and not others (there are
many places we can avoid switching on type this way). The compiler can
inline and then dead code eliminate to achieve the same effect, so we'll
just let the compiler be in charge of inlining decisions.
2024-03-19 15:01:13 -07:00
727b7e642a Save more redundant nullptr checks 2024-03-19 14:34:59 -07:00
cb4c2b7e1e Avoid redundant null check in some cases
All checks were successful
Tests / Clang total: 825, passed: 825
Clang |Total|New|Outstanding|Fixed|Trend |:-:|:-:|:-:|:-:|:-: |0|0|0|0|:clap:
Tests / Release [gcc] total: 825, passed: 825
GNU C Compiler (gcc) |Total|New|Outstanding|Fixed|Trend |:-:|:-:|:-:|:-:|:-: |0|0|0|0|:clap:
Tests / Release [gcc,aarch64] total: 824, passed: 824
Tests / Coverage total: 823, passed: 823
weaselab/conflict-set/pipeline/head This commit looks good
2024-03-19 14:29:13 -07:00
ef9b789745 Remove unused code 2024-03-19 11:12:03 -07:00
edd7bcaa1e Check ConflictSet preconditions in script_test
All checks were successful
Tests / Clang total: 825, passed: 825
Clang |Total|New|Outstanding|Fixed|Trend |:-:|:-:|:-:|:-:|:-: |0|0|0|0|:clap:
Tests / Release [gcc] total: 825, passed: 825
GNU C Compiler (gcc) |Total|New|Outstanding|Fixed|Trend |:-:|:-:|:-:|:-:|:-: |0|0|0|0|:clap:
Tests / Release [gcc,aarch64] total: 824, passed: 824
Tests / Coverage total: 823, passed: 823
weaselab/conflict-set/pipeline/head This commit looks good
2024-03-19 10:31:04 -07:00
be8ac879c5 Update README benchmarks
All checks were successful
Tests / Clang total: 825, passed: 825
Clang |Total|New|Outstanding|Fixed|Trend |:-:|:-:|:-:|:-:|:-: |0|0|0|0|:clap:
Tests / Release [gcc] total: 825, passed: 825
GNU C Compiler (gcc) |Total|New|Outstanding|Fixed|Trend |:-:|:-:|:-:|:-:|:-: |0|0|0|0|:clap:
Tests / Release [gcc,aarch64] total: 824, passed: 824
Tests / Coverage total: 823, passed: 823
weaselab/conflict-set/pipeline/head This commit looks good
2024-03-18 17:24:26 -07:00
83c7f66d67 Remove some redundant nullptr checks
All checks were successful
Tests / Clang total: 825, passed: 825
Clang |Total|New|Outstanding|Fixed|Trend |:-:|:-:|:-:|:-:|:-: |0|0|0|0|:clap:
Tests / Release [gcc] total: 825, passed: 825
GNU C Compiler (gcc) |Total|New|Outstanding|Fixed|Trend |:-:|:-:|:-:|:-:|:-: |0|0|0|0|:clap:
Tests / Release [gcc,aarch64] total: 824, passed: 824
Tests / Coverage total: 823, passed: 823
weaselab/conflict-set/pipeline/head This commit looks good
2024-03-18 16:22:24 -07:00
a5710b8282 Remove performance-only code from debug build for increased coverage 2024-03-18 16:21:55 -07:00
c31eebd5de No caller of CheckRangeRightSide::downLeftSpine has null n 2024-03-18 16:03:41 -07:00
ddeb059968 Remove more dead code 2024-03-18 16:01:56 -07:00
5a0bcf9a5a Strengthen precondition to checkRangeStartsWith
and remove resulting dead code
2024-03-18 15:44:44 -07:00
97717cec86 Remove suspected dead code
Removing it is definitely safe. I suspect that any way to get here would
have already returned from checkRangeRead during the search for the
common prefix
2024-03-18 15:30:15 -07:00
6a13c43a78 Remove mistakenly checked in printfs
All checks were successful
Tests / Clang total: 822, passed: 822
Clang |Total|New|Outstanding|Fixed|Trend |:-:|:-:|:-:|:-:|:-: |0|0|0|0|:clap:
Tests / Release [gcc] total: 822, passed: 822
GNU C Compiler (gcc) |Total|New|Outstanding|Fixed|Trend |:-:|:-:|:-:|:-:|:-: |0|0|0|0|:clap:
Tests / Release [gcc,aarch64] total: 821, passed: 821
Tests / Coverage total: 820, passed: 820
weaselab/conflict-set/pipeline/head This commit looks good
2024-03-18 14:03:09 -07:00
c6c438bae2 Address more missing coverage
cc #3. Found some non-trivial dead code this time!
2024-03-18 14:01:30 -07:00
7d4f832b43 Address some missing coverage
cc #3
2024-03-18 13:36:35 -07:00
5b0c3c2428 Exclude more from coverage
Only count ConflictSet.cpp in jenkins now, and also add some more
excludes for unreachable lines
2024-03-18 12:05:52 -07:00
f2b5e9b0bf Change max key len to 8, update corpus
Now that we don't have a fixed buffer reserved for partial key bytes,
there's nothing (obvious) that makes testing short versus long keys much
different. maybeDecreaseCapacity is an exception, and we'll write some
tests covering that manually.
2024-03-18 11:55:43 -07:00
8e0e65dac6 Count implicit key byte for maybeDecreaseCapacity 2024-03-18 11:42:53 -07:00
5aab76847a Add inner-full-word test
All checks were successful
Tests / Clang total: 934, passed: 934
Clang |Total|New|Outstanding|Fixed|Trend |:-:|:-:|:-:|:-:|:-: |0|0|0|0|:clap:
Tests / Release [gcc] total: 934, passed: 934
GNU C Compiler (gcc) |Total|New|Outstanding|Fixed|Trend |:-:|:-:|:-:|:-:|:-: |0|0|0|0|:clap:
Tests / Release [gcc,aarch64] total: 933, passed: 933
Tests / Coverage total: 932, passed: 932
weaselab/conflict-set/pipeline/head This commit looks good
2024-03-17 14:42:11 -07:00
1a51aa00e5 Run scripted tests
All checks were successful
Tests / Clang total: 933, passed: 933
Clang |Total|New|Outstanding|Fixed|Trend |:-:|:-:|:-:|:-:|:-: |0|0|0|0|:clap:
Tests / Release [gcc] total: 933, passed: 933
GNU C Compiler (gcc) |Total|New|Outstanding|Fixed|Trend |:-:|:-:|:-:|:-:|:-: |0|0|0|0|:clap:
Tests / Release [gcc,aarch64] total: 932, passed: 932
Tests / Coverage total: 931, passed: 931
weaselab/conflict-set/pipeline/head This commit looks good
2024-03-16 22:04:30 -07:00
3975bada0c Add a simple language for scripting tests 2024-03-16 22:02:00 -07:00
a5330b6e23 Minor paper tweaks 2024-03-16 12:45:36 -07:00
604 changed files with 1630 additions and 500 deletions

View File

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

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);
memcpy(r.data(), key.data(), key.size());
r[key.size()] = 0;
return {key.data(), int(key.size()), r.data(), int(r.size())};
return {{key.data(), int(key.size())}, {r.data(), int(r.size())}, 0};
}
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())};
return {{key.data(), int(key.size())}, {r.data(), int(r.size())}, 0};
}
void benchConflictSet() {
@@ -258,4 +258,4 @@ void benchConflictSet() {
}
}
int main(void) { benchConflictSet(); }
int main(void) { benchConflictSet(); }

View File

@@ -1,6 +1,6 @@
cmake_minimum_required(VERSION 3.18)
project(
conflict_set
conflict-set
VERSION 0.0.1
DESCRIPTION
"A data structure for optimistic concurrency control on ranges of bitwise-lexicographically-ordered keys."
@@ -25,6 +25,9 @@ endif()
add_compile_options(-fdata-sections -ffunction-sections -Wswitch-enum
-Werror=switch-enum)
option(USE_SIMD_FALLBACK
"Use fallback implementations of functions that use SIMD" OFF)
# This is encouraged according to
# https://valgrind.org/docs/manual/manual-core-adv.html#manual-core-adv.clientreq
include_directories(SYSTEM ${CMAKE_SOURCE_DIR}/third_party/valgrind)
@@ -43,18 +46,20 @@ endif()
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 -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)
check_include_file_cxx("arm_neon.h" HAS_ARM_NEON)
if(HAS_ARM_NEON)
add_compile_definitions(HAS_ARM_NEON)
endif()
endif()
set(CMAKE_CXX_IMPLICIT_LINK_LIBRARIES "")
@@ -78,19 +83,19 @@ if(NOT APPLE)
LINKER:--version-script=${CMAKE_SOURCE_DIR}/linker.map)
endif()
add_library(${PROJECT_NAME}_static STATIC
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(NOT APPLE AND CMAKE_OBJCOPY)
add_custom_command(
TARGET conflict_set_static
TARGET conflict-set-static
POST_BUILD
COMMAND
${CMAKE_OBJCOPY} --keep-global-symbols=${CMAKE_SOURCE_DIR}/symbols.txt
$<TARGET_FILE:${PROJECT_NAME}_static>)
$<TARGET_FILE:${PROJECT_NAME}-static>)
endif()
set(TEST_FLAGS -Wall -Wextra -Wunreachable-code -Wpedantic -UNDEBUG)
@@ -167,10 +172,35 @@ if(BUILD_TESTING)
add_test(NAME conflict_set_fuzz_${hash} COMMAND fuzz_driver ${TEST})
endforeach()
# tsan
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()
add_executable(driver TestDriver.cpp)
target_compile_options(driver PRIVATE ${TEST_FLAGS})
target_link_libraries(driver PRIVATE ${PROJECT_NAME})
add_executable(script_test ScriptTest.cpp)
target_compile_options(script_test PRIVATE ${TEST_FLAGS})
target_link_libraries(script_test PRIVATE ${PROJECT_NAME})
file(GLOB SCRIPT_TESTS ${CMAKE_SOURCE_DIR}/script_tests/*)
foreach(TEST ${SCRIPT_TESTS})
get_filename_component(name ${TEST} NAME)
add_test(NAME conflict_set_script_${name} COMMAND script_test ${TEST})
endforeach()
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)
@@ -217,7 +247,7 @@ if(BUILD_TESTING)
NAME conflict_set_static_symbols
COMMAND
${CMAKE_SOURCE_DIR}/test_symbols.sh
$<TARGET_FILE:${PROJECT_NAME}_static> ${CMAKE_SOURCE_DIR}/symbols.txt)
$<TARGET_FILE:${PROJECT_NAME}-static> ${CMAKE_SOURCE_DIR}/symbols.txt)
endif()
# bench
@@ -242,6 +272,10 @@ 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)
include(CPack)
@@ -253,7 +287,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}>)
@@ -262,13 +296,13 @@ set_target_properties(
SOVERSION ${PROJECT_VERSION_MAJOR})
install(
TARGETS ${PROJECT_NAME} ${PROJECT_NAME}_static
EXPORT ConflictSetConfig
TARGETS ${PROJECT_NAME} ${PROJECT_NAME}-static
EXPORT conflict-setConfig
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 ConflictSetConfig
DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/ConflictSet/cmake)
install(EXPORT conflict-setConfig
DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/conflict-set/cmake)

View File

@@ -29,6 +29,7 @@ limitations under the License.
#include <span>
#include <string>
#include <string_view>
#include <type_traits>
#include <utility>
#ifdef HAS_AVX
@@ -39,6 +40,8 @@ limitations under the License.
#include <memcheck.h>
using namespace weaselab;
// Use assert for checking potentially complex properties during tests.
// Use assume to hint simple properties to the optimizer.
@@ -216,6 +219,7 @@ struct Node {
/* end section that's copied to the next node */
uint8_t *partialKey();
size_t size() const;
Type getType() const { return type; }
int32_t getCapacity() const { return partialKeyCapacity; }
@@ -249,6 +253,8 @@ struct Node0 : Node {
void copyChildrenAndKeyFrom(const Node0 &other);
void copyChildrenAndKeyFrom(const struct Node3 &other);
size_t size() const { return sizeof(Node0) + getCapacity(); }
};
struct Node3 : Node {
@@ -262,6 +268,8 @@ struct Node3 : Node {
void copyChildrenAndKeyFrom(const Node0 &other);
void copyChildrenAndKeyFrom(const Node3 &other);
void copyChildrenAndKeyFrom(const struct Node16 &other);
size_t size() const { return sizeof(Node3) + getCapacity(); }
};
struct Node16 : Node {
@@ -275,6 +283,8 @@ struct Node16 : Node {
void copyChildrenAndKeyFrom(const Node3 &other);
void copyChildrenAndKeyFrom(const Node16 &other);
void copyChildrenAndKeyFrom(const struct Node48 &other);
size_t size() const { return sizeof(Node16) + getCapacity(); }
};
struct Node48 : Node {
@@ -290,6 +300,8 @@ struct Node48 : Node {
void copyChildrenAndKeyFrom(const Node16 &other);
void copyChildrenAndKeyFrom(const Node48 &other);
void copyChildrenAndKeyFrom(const struct Node256 &other);
size_t size() const { return sizeof(Node48) + getCapacity(); }
};
struct Node256 : Node {
@@ -299,6 +311,8 @@ struct Node256 : Node {
uint8_t *partialKey() { return (uint8_t *)(this + 1); }
void copyChildrenAndKeyFrom(const Node48 &other);
void copyChildrenAndKeyFrom(const Node256 &other);
size_t size() const { return sizeof(Node256) + getCapacity(); }
};
inline void Node0::copyChildrenAndKeyFrom(const Node0 &other) {
@@ -472,7 +486,8 @@ inline void Node256::copyChildrenAndKeyFrom(const Node256 &other) {
namespace {
std::string getSearchPathPrintable(Node *n);
}
std::string getSearchPath(Node *n);
} // namespace
// Bound memory usage following the analysis in the ART paper
@@ -534,7 +549,7 @@ template <class T> struct BoundedFreeListAllocator {
} else {
// The intent is to filter out too-small nodes in the freelist
removeNode(n);
safe_free(n);
safe_free(n, sizeof(T) + n->partialKeyCapacity);
}
}
@@ -546,10 +561,9 @@ template <class T> struct BoundedFreeListAllocator {
}
void release(T *p) {
static_assert(std::is_trivially_destructible_v<T>);
if (freeListBytes >= kFreeListMaxMemory) {
removeNode(p);
return safe_free(p);
return safe_free(p, sizeof(T) + p->partialKeyCapacity);
}
memcpy((void *)p, &freeList, sizeof(freeList));
freeList = p;
@@ -559,11 +573,11 @@ template <class T> struct BoundedFreeListAllocator {
~BoundedFreeListAllocator() {
for (void *iter = freeList; iter != nullptr;) {
VALGRIND_MAKE_MEM_DEFINED(iter, sizeof(iter));
auto *tmp = iter;
VALGRIND_MAKE_MEM_DEFINED(iter, sizeof(Node));
auto *tmp = (T *)iter;
memcpy(&iter, iter, sizeof(void *));
removeNode(((T *)tmp));
safe_free(tmp);
removeNode((tmp));
safe_free(tmp, sizeof(T) + tmp->partialKeyCapacity);
}
}
@@ -589,6 +603,23 @@ uint8_t *Node::partialKey() {
}
}
size_t Node::size() const {
switch (type) {
case Type_Node0:
return ((Node0 *)this)->size();
case Type_Node3:
return ((Node3 *)this)->size();
case Type_Node16:
return ((Node16 *)this)->size();
case Type_Node48:
return ((Node48 *)this)->size();
case Type_Node256:
return ((Node256 *)this)->size();
default: // GCOVR_EXCL_LINE
__builtin_unreachable(); // GCOVR_EXCL_LINE
}
}
struct NodeAllocators {
BoundedFreeListAllocator<Node0> node0;
BoundedFreeListAllocator<Node3> node3;
@@ -676,7 +707,7 @@ template <class NodeT> int getNodeIndex(NodeT *self, uint8_t index) {
// Precondition - an entry for index must exist in the node
Node *&getChildExists(Node *self, uint8_t index) {
switch (self->getType()) {
case Type_Node0:
case Type_Node0: // GCOVR_EXCL_LINE
__builtin_unreachable(); // GCOVR_EXCL_LINE
case Type_Node3: {
auto *self3 = static_cast<Node3 *>(self);
@@ -924,6 +955,42 @@ Node *&getOrCreateChild(Node *&self, uint8_t index,
assert(self->getType() == Type_Node16);
++self->numChildren;
#ifdef HAS_AVX
__m128i key_vec = _mm_set1_epi8(index);
__m128i indices;
memcpy(&indices, self16->index, sizeof(self16->index));
__m128i results = _mm_cmpeq_epi8(key_vec, _mm_min_epu8(key_vec, indices));
int mask = (1 << (self->numChildren - 1)) - 1;
uint32_t bitfield = _mm_movemask_epi8(results) & mask;
bitfield |= uint32_t(1) << (self->numChildren - 1);
int i = std::countr_zero(bitfield);
if (i < self->numChildren - 1) {
memmove(self16->index + i + 1, self16->index + i,
self->numChildren - (i + 1));
memmove(self16->children + i + 1, self16->children + i,
(self->numChildren - (i + 1)) * sizeof(Child));
}
#elif defined(HAS_ARM_NEON)
uint8x16_t indices;
memcpy(&indices, self16->index, sizeof(self16->index));
// 0xff for each leq
auto results = vcleq_u8(vdupq_n_u8(index), indices);
uint64_t mask = (uint64_t(1) << ((self->numChildren - 1) * 4)) - 1;
// 0xf for each 0xff (within mask)
uint64_t bitfield =
vget_lane_u64(
vreinterpret_u64_u8(vshrn_n_u16(vreinterpretq_u16_u8(results), 4)),
0) &
mask;
bitfield |= uint64_t(0xf) << ((self->numChildren - 1) * 4);
int i = std::countr_zero(bitfield) / 4;
if (i < self->numChildren - 1) {
memmove(self16->index + i + 1, self16->index + i,
self->numChildren - (i + 1));
memmove(self16->children + i + 1, self16->children + i,
(self->numChildren - (i + 1)) * sizeof(Child));
}
#else
int i = 0;
for (; i < int(self->numChildren) - 1; ++i) {
if (int(self16->index[i]) > int(index)) {
@@ -934,6 +1001,7 @@ Node *&getOrCreateChild(Node *&self, uint8_t index,
break;
}
}
#endif
self16->index[i] = index;
auto &result = self16->children[i].child;
result = nullptr;
@@ -949,8 +1017,8 @@ Node *&getOrCreateChild(Node *&self, uint8_t index,
self = newSelf;
goto insert256;
}
insert48:
insert48:
auto *self48 = static_cast<Node48 *>(self);
self48->bitSet.set(index);
++self->numChildren;
@@ -962,6 +1030,7 @@ Node *&getOrCreateChild(Node *&self, uint8_t index,
return result;
}
case Type_Node256: {
insert256:
auto *self256 = static_cast<Node256 *>(self);
++self->numChildren;
@@ -997,21 +1066,21 @@ Node *nextLogical(Node *node) {
// Invalidates `self`, replacing it with a node of at least capacity.
// Does not return nodes to freelists when kUseFreeList is false.
template <bool kUseFreeList>
void freeAndMakeCapacityAtLeast(Node *&self, int capacity,
NodeAllocators *allocators,
ConflictSet::Impl *impl) {
ConflictSet::Impl *impl,
const bool kUseFreeList) {
switch (self->getType()) {
case Type_Node0: {
auto *self0 = (Node0 *)self;
auto *newSelf = allocators->node0.allocate(capacity);
newSelf->copyChildrenAndKeyFrom(*self0);
getInTree(self, impl) = newSelf;
if constexpr (kUseFreeList) {
if (kUseFreeList) {
allocators->node0.release(self0);
} else {
removeNode(self0);
safe_free(self0);
safe_free(self0, self0->size());
}
self = newSelf;
} break;
@@ -1020,11 +1089,11 @@ void freeAndMakeCapacityAtLeast(Node *&self, int capacity,
auto *newSelf = allocators->node3.allocate(capacity);
newSelf->copyChildrenAndKeyFrom(*self3);
getInTree(self, impl) = newSelf;
if constexpr (kUseFreeList) {
if (kUseFreeList) {
allocators->node3.release(self3);
} else {
removeNode(self3);
safe_free(self3);
safe_free(self3, self3->size());
}
self = newSelf;
} break;
@@ -1033,11 +1102,11 @@ void freeAndMakeCapacityAtLeast(Node *&self, int capacity,
auto *newSelf = allocators->node16.allocate(capacity);
newSelf->copyChildrenAndKeyFrom(*self16);
getInTree(self, impl) = newSelf;
if constexpr (kUseFreeList) {
if (kUseFreeList) {
allocators->node16.release(self16);
} else {
removeNode(self16);
safe_free(self16);
safe_free(self16, self16->size());
}
self = newSelf;
} break;
@@ -1046,11 +1115,11 @@ void freeAndMakeCapacityAtLeast(Node *&self, int capacity,
auto *newSelf = allocators->node48.allocate(capacity);
newSelf->copyChildrenAndKeyFrom(*self48);
getInTree(self, impl) = newSelf;
if constexpr (kUseFreeList) {
if (kUseFreeList) {
allocators->node48.release(self48);
} else {
removeNode(self48);
safe_free(self48);
safe_free(self48, self48->size());
}
self = newSelf;
} break;
@@ -1059,11 +1128,11 @@ void freeAndMakeCapacityAtLeast(Node *&self, int capacity,
auto *newSelf = allocators->node256.allocate(capacity);
newSelf->copyChildrenAndKeyFrom(*self256);
getInTree(self, impl) = newSelf;
if constexpr (kUseFreeList) {
if (kUseFreeList) {
allocators->node256.release(self256);
} else {
removeNode(self256);
safe_free(self256);
safe_free(self256, self256->size());
}
self = newSelf;
} break;
@@ -1078,15 +1147,13 @@ void freeAndMakeCapacityAtLeast(Node *&self, int capacity,
void maybeDecreaseCapacity(Node *&self, NodeAllocators *allocators,
ConflictSet::Impl *impl) {
const int maxCapacity =
(self->numChildren + int(self->entryPresent)) * self->partialKeyLen;
(self->numChildren + int(self->entryPresent)) * (self->partialKeyLen + 1);
if (self->getCapacity() <= maxCapacity) {
return;
}
freeAndMakeCapacityAtLeast</*kUseFreeList*/ false>(self, maxCapacity,
allocators, impl);
freeAndMakeCapacityAtLeast(self, maxCapacity, allocators, impl, false);
}
template <Type kType>
void maybeDownsize(Node *self, NodeAllocators *allocators,
ConflictSet::Impl *impl, Node *&dontInvalidate) {
@@ -1094,10 +1161,8 @@ void maybeDownsize(Node *self, NodeAllocators *allocators,
fprintf(stderr, "maybeDownsize: %s\n", getSearchPathPrintable(self).c_str());
#endif
assert(self->getType() == kType);
static_assert(kType != Type_Node0);
switch (kType) {
case Type_Node0:
switch (self->getType()) {
case Type_Node0: // GCOVR_EXCL_LINE
__builtin_unreachable(); // GCOVR_EXCL_LINE
case Type_Node3: {
auto *self3 = (Node3 *)self;
@@ -1112,8 +1177,7 @@ void maybeDownsize(Node *self, NodeAllocators *allocators,
if (minCapacity > child->getCapacity()) {
const bool update = child == dontInvalidate;
freeAndMakeCapacityAtLeast</*kUseFreeList*/ true>(child, minCapacity,
allocators, impl);
freeAndMakeCapacityAtLeast(child, minCapacity, allocators, impl, true);
if (update) {
dontInvalidate = child;
}
@@ -1200,52 +1264,18 @@ Node *erase(Node *self, NodeAllocators *allocators, ConflictSet::Impl *impl,
if (self->numChildren != 0) {
const bool update = result == dontInvalidate;
switch (self->getType()) {
case Type_Node0:
__builtin_unreachable(); // GCOVR_EXCL_LINE
case Type_Node3:
maybeDownsize<Type_Node3>(self, allocators, impl, result);
break;
case Type_Node16:
maybeDownsize<Type_Node16>(self, allocators, impl, result);
break;
case Type_Node48:
maybeDownsize<Type_Node48>(self, allocators, impl, result);
break;
case Type_Node256:
maybeDownsize<Type_Node256>(self, allocators, impl, result);
break;
default: // GCOVR_EXCL_LINE
__builtin_unreachable(); // GCOVR_EXCL_LINE
}
maybeDownsize(self, allocators, impl, result);
if (update) {
dontInvalidate = result;
}
return result;
}
switch (self->getType()) {
case Type_Node0:
allocators->node0.release((Node0 *)self);
break;
case Type_Node3:
allocators->node3.release((Node3 *)self);
break;
case Type_Node16:
allocators->node16.release((Node16 *)self);
break;
case Type_Node48:
allocators->node48.release((Node48 *)self);
break;
case Type_Node256:
allocators->node256.release((Node256 *)self);
break;
default: // GCOVR_EXCL_LINE
__builtin_unreachable(); // GCOVR_EXCL_LINE
}
assert(self->getType() == Type_Node0);
allocators->node0.release((Node0 *)self);
switch (parent->getType()) {
case Type_Node0:
case Type_Node0: // GCOVR_EXCL_LINE
__builtin_unreachable(); // GCOVR_EXCL_LINE
case Type_Node3: {
auto *parent3 = static_cast<Node3 *>(parent);
@@ -1259,16 +1289,7 @@ Node *erase(Node *self, NodeAllocators *allocators, ConflictSet::Impl *impl,
(parent->numChildren - (nodeIndex + 1)));
--parent->numChildren;
if (parent->numChildren == 0 && !parent->entryPresent &&
parent->parent != nullptr) {
return erase(parent, allocators, impl, dontInvalidate);
} else {
const bool update = result == dontInvalidate;
maybeDownsize<Type_Node3>(parent, allocators, impl, result);
if (update) {
dontInvalidate = result;
}
}
assert(parent->numChildren > 0 || parent->entryPresent);
} break;
case Type_Node16: {
auto *parent16 = static_cast<Node16 *>(parent);
@@ -1282,16 +1303,9 @@ Node *erase(Node *self, NodeAllocators *allocators, ConflictSet::Impl *impl,
(parent->numChildren - (nodeIndex + 1)));
--parent->numChildren;
if (parent->numChildren == 0 && !parent->entryPresent &&
parent->parent != nullptr) {
return erase(parent, allocators, impl, dontInvalidate);
} else {
const bool update = result == dontInvalidate;
maybeDownsize<Type_Node16>(parent, allocators, impl, result);
if (update) {
dontInvalidate = result;
}
}
// By kMinChildrenNode16
assert(parent->numChildren > 0);
} break;
case Type_Node48: {
auto *parent48 = static_cast<Node48 *>(parent);
@@ -1309,16 +1323,9 @@ Node *erase(Node *self, NodeAllocators *allocators, ConflictSet::Impl *impl,
}
--parent->numChildren;
if (parent->numChildren == 0 && !parent->entryPresent &&
parent->parent != nullptr) {
return erase(parent, allocators, impl, dontInvalidate);
} else {
const bool update = result == dontInvalidate;
maybeDownsize<Type_Node48>(parent, allocators, impl, result);
if (update) {
dontInvalidate = result;
}
}
// By kMinChildrenNode48
assert(parent->numChildren > 0);
} break;
case Type_Node256: {
auto *parent256 = static_cast<Node256 *>(parent);
@@ -1326,21 +1333,21 @@ Node *erase(Node *self, NodeAllocators *allocators, ConflictSet::Impl *impl,
parent256->children[parentsIndex].child = nullptr;
--parent->numChildren;
if (parent->numChildren == 0 && !parent->entryPresent &&
parent->parent != nullptr) {
return erase(parent, allocators, impl, dontInvalidate);
} else {
const bool update = result == dontInvalidate;
maybeDownsize<Type_Node256>(parent, allocators, impl, result);
if (update) {
dontInvalidate = result;
}
}
// By kMinChildrenNode256
assert(parent->numChildren > 0);
} break;
default: // GCOVR_EXCL_LINE
__builtin_unreachable(); // GCOVR_EXCL_LINE
}
const bool update = result == dontInvalidate;
maybeDownsize(parent, allocators, impl, result);
if (update) {
dontInvalidate = result;
}
return result;
}
@@ -1374,7 +1381,7 @@ constexpr int kUnrollFactor = 4;
bool compareStride(const uint8_t *ap, const uint8_t *bp) {
#if defined(HAS_ARM_NEON)
static_assert(kStride == 64);
uint8x16_t x[4];
uint8x16_t x[4]; // GCOVR_EXCL_LINE
for (int i = 0; i < 4; ++i) {
x[i] = vceqq_u8(vld1q_u8(ap + i * 16), vld1q_u8(bp + i * 16));
}
@@ -1384,7 +1391,7 @@ bool compareStride(const uint8_t *ap, const uint8_t *bp) {
uint64_t(-1);
#elif defined(HAS_AVX)
static_assert(kStride == 64);
__m128i x[4];
__m128i x[4]; // GCOVR_EXCL_LINE
for (int i = 0; i < 4; ++i) {
x[i] = _mm_cmpeq_epi8(_mm_loadu_si128((__m128i *)(ap + i * 16)),
_mm_loadu_si128((__m128i *)(bp + i * 16)));
@@ -1403,7 +1410,7 @@ bool compareStride(const uint8_t *ap, const uint8_t *bp) {
int firstNeqStride(const uint8_t *ap, const uint8_t *bp) {
#if defined(HAS_AVX)
static_assert(kStride == 64);
uint64_t c[kStride / 16];
uint64_t c[kStride / 16]; // GCOVR_EXCL_LINE
for (int i = 0; i < kStride; i += 16) {
const auto a = _mm_loadu_si128((__m128i *)(ap + i));
const auto b = _mm_loadu_si128((__m128i *)(bp + i));
@@ -1445,18 +1452,6 @@ int longestCommonPrefix(const uint8_t *ap, const uint8_t *bp, int cl) {
goto bytes;
}
// Optimistic early return
{
uint64_t a;
uint64_t b;
memcpy(&a, ap, 8);
memcpy(&b, bp, 8);
const auto mismatched = a ^ b;
if (mismatched) {
return std::countr_zero(mismatched) / 8;
}
}
// kStride * kUnrollCount at a time
end = cl & ~(kStride * kUnrollFactor - 1);
while (i < end) {
@@ -1484,8 +1479,8 @@ int longestCommonPrefix(const uint8_t *ap, const uint8_t *bp, int cl) {
// word at a time
end = cl & ~(sizeof(uint64_t) - 1);
while (i < end) {
uint64_t a;
uint64_t b;
uint64_t a; // GCOVR_EXCL_LINE
uint64_t b; // GCOVR_EXCL_LINE
memcpy(&a, ap, 8);
memcpy(&b, bp, 8);
const auto mismatched = a ^ b;
@@ -1574,6 +1569,9 @@ bool checkPointRead(Node *n, const std::span<const uint8_t> key,
goto downLeftSpine;
} else {
n = nextSibling(n);
if (n == nullptr) {
return true;
}
goto downLeftSpine;
}
}
@@ -1590,6 +1588,9 @@ bool checkPointRead(Node *n, const std::span<const uint8_t> key,
goto downLeftSpine;
} else {
n = nextSibling(n);
if (n == nullptr) {
return true;
}
goto downLeftSpine;
}
}
@@ -1604,9 +1605,6 @@ bool checkPointRead(Node *n, const std::span<const uint8_t> key,
}
}
downLeftSpine:
if (n == nullptr) {
return true;
}
for (;;) {
if (n->entryPresent) {
return n->entry.rangeVersion <= readVersion;
@@ -1640,7 +1638,7 @@ int64_t maxBetweenExclusive(Node *n, int begin, int end) {
}
}
switch (n->getType()) {
case Type_Node0:
case Type_Node0: // GCOVR_EXCL_LINE
// We would have returned above, after not finding a child
__builtin_unreachable(); // GCOVR_EXCL_LINE
case Type_Node3: {
@@ -1703,10 +1701,13 @@ Vector<uint8_t> getSearchPath(Arena &arena, Node *n) {
}
std::reverse(result.begin(), result.end());
return result;
}
} // GCOVR_EXCL_LINE
// Return true if the max version among all keys that start with key + [child],
// where begin < child < end, is <= readVersion
// where begin < child < end, is <= readVersion.
//
// Precondition: transitively, no child of n has a search path that's a longer
// prefix of key than n
bool checkRangeStartsWith(Node *n, std::span<const uint8_t> key, int begin,
int end, int64_t readVersion,
ConflictSet::Impl *impl) {
@@ -1714,60 +1715,58 @@ bool checkRangeStartsWith(Node *n, std::span<const uint8_t> key, int begin,
fprintf(stderr, "%s(%02x,%02x)*\n", printable(key).c_str(), begin, end);
#endif
auto remaining = key;
for (;;) {
if (maxVersion(n, impl) <= readVersion) {
return true;
}
if (remaining.size() == 0) {
return maxBetweenExclusive(n, begin, end) <= readVersion;
}
if (remaining.size() == 0) {
return maxBetweenExclusive(n, begin, end) <= readVersion;
}
auto *child = getChild(n, remaining[0]);
if (child == nullptr) {
int c = getChildGeq(n, remaining[0]);
if (c >= 0) {
n = getChildExists(n, c);
auto *child = getChild(n, remaining[0]);
if (child == nullptr) {
int c = getChildGeq(n, remaining[0]);
if (c >= 0) {
n = getChildExists(n, c);
goto downLeftSpine;
} else {
n = nextSibling(n);
if (n == nullptr) {
return true;
}
goto downLeftSpine;
}
}
n = child;
remaining = remaining.subspan(1, remaining.size() - 1);
assert(n->partialKeyLen > 0);
{
int commonLen = std::min<int>(n->partialKeyLen, remaining.size());
int i = longestCommonPrefix(n->partialKey(), remaining.data(), commonLen);
if (i < commonLen) {
auto c = n->partialKey()[i] <=> remaining[i];
if (c > 0) {
goto downLeftSpine;
} else {
n = nextSibling(n);
if (n == nullptr) {
return true;
}
goto downLeftSpine;
}
}
n = child;
remaining = remaining.subspan(1, remaining.size() - 1);
if (n->partialKeyLen > 0) {
int commonLen = std::min<int>(n->partialKeyLen, remaining.size());
int i = longestCommonPrefix(n->partialKey(), remaining.data(), commonLen);
if (i < commonLen) {
auto c = n->partialKey()[i] <=> remaining[i];
if (c > 0) {
goto downLeftSpine;
} else {
n = nextSibling(n);
goto downLeftSpine;
}
}
if (commonLen == n->partialKeyLen) {
// partial key matches
remaining = remaining.subspan(commonLen, remaining.size() - commonLen);
} else if (n->partialKeyLen > int(remaining.size())) {
if (begin < n->partialKey()[remaining.size()] &&
n->partialKey()[remaining.size()] < end) {
if (n->entryPresent && n->entry.rangeVersion > readVersion) {
return false;
}
return maxVersion(n, impl) <= readVersion;
}
return true;
assert(n->partialKeyLen > int(remaining.size()));
if (begin < n->partialKey()[remaining.size()] &&
n->partialKey()[remaining.size()] < end) {
if (n->entryPresent && n->entry.rangeVersion > readVersion) {
return false;
}
return maxVersion(n, impl) <= readVersion;
}
}
downLeftSpine:
if (n == nullptr) {
return true;
}
__builtin_unreachable(); // GCOVR_EXCL_LINE
downLeftSpine:
for (;;) {
if (n->entryPresent) {
return n->entry.rangeVersion <= readVersion;
@@ -1835,6 +1834,10 @@ struct CheckRangeLeftSide {
return true;
} else {
n = nextSibling(n);
if (n == nullptr) {
ok = true;
return true;
}
return downLeftSpine();
}
}
@@ -1862,6 +1865,10 @@ struct CheckRangeLeftSide {
return true;
} else {
n = nextSibling(n);
if (n == nullptr) {
ok = true;
return true;
}
return downLeftSpine();
}
}
@@ -1870,9 +1877,7 @@ struct CheckRangeLeftSide {
remaining =
remaining.subspan(commonLen, remaining.size() - commonLen);
} else if (n->partialKeyLen > int(remaining.size())) {
if (searchPathLen < prefixLen) {
return downLeftSpine();
}
assert(searchPathLen >= prefixLen);
if (n->entryPresent && n->entry.rangeVersion > readVersion) {
ok = false;
return true;
@@ -1900,10 +1905,6 @@ struct CheckRangeLeftSide {
bool downLeftSpine() {
phase = DownLeftSpine;
if (n == nullptr) {
ok = true;
return true;
}
return false;
}
};
@@ -2051,10 +2052,7 @@ struct CheckRangeRightSide {
bool downLeftSpine() {
phase = DownLeftSpine;
if (n == nullptr) {
ok = true;
return true;
}
assert(n != nullptr);
return false;
}
};
@@ -2248,7 +2246,7 @@ void destroyTree(Node *root) {
assert(c != nullptr);
toFree.push_back(c);
}
safe_free(n);
safe_free(n, n->size());
}
}
@@ -2390,6 +2388,9 @@ Iterator firstGeq(Node *n, const std::span<const uint8_t> key) {
goto downLeftSpine;
} else {
n = nextSibling(n);
if (n == nullptr) {
return {nullptr, 1};
}
goto downLeftSpine;
}
}
@@ -2420,9 +2421,6 @@ Iterator firstGeq(Node *n, const std::span<const uint8_t> key) {
}
}
downLeftSpine:
if (n == nullptr) {
return {nullptr, 1};
}
for (;;) {
if (n->entryPresent) {
return {n, 1};
@@ -2472,19 +2470,24 @@ struct __attribute__((visibility("hidden"))) ConflictSet::Impl {
return;
}
this->oldestVersion = oldestVersion;
#ifdef NDEBUG
// This is here for performance reasons, since we want to amortize the cost
// of storing the search path as a string. In tests, we want to exercise the
// rest of the code often.
if (keyUpdates < 100) {
return;
}
#endif
Node *n = firstGeq(root, removalKey).n;
// There's no way to erase removalKey without introducing a key after it
assert(n != nullptr);
// Don't erase the root
if (n == root) {
n = nextLogical(n);
n = nextPhysical(n);
}
for (; keyUpdates > 0 && n != nullptr; --keyUpdates) {
if (std::max(n->entry.pointVersion, n->entry.rangeVersion) <=
oldestVersion) {
if (n->entryPresent && std::max(n->entry.pointVersion,
n->entry.rangeVersion) <= oldestVersion) {
// Any transaction n would have prevented from committing is
// going to fail with TooOld anyway.
@@ -2495,18 +2498,15 @@ struct __attribute__((visibility("hidden"))) ConflictSet::Impl {
n = erase(n, &allocators, this, dummy);
} else {
maybeDecreaseCapacity(n, &allocators, this);
n = nextLogical(n);
n = nextPhysical(n);
}
}
if (n == nullptr) {
removalKey = {};
return;
} else {
removalKeyArena = Arena();
removalKey = getSearchPath(removalKeyArena, n);
}
if (keyUpdates == 0) {
keyUpdates = 10;
}
removalKeyArena = Arena();
removalKey = getSearchPath(removalKeyArena, n);
}
explicit Impl(int64_t oldestVersion) : oldestVersion(oldestVersion) {
@@ -2535,6 +2535,7 @@ struct __attribute__((visibility("hidden"))) ConflictSet::Impl {
Node *root;
int64_t rootMaxVersion;
int64_t oldestVersion;
int64_t totalBytes = 0;
};
// Precondition - an entry for index must exist in the node
@@ -2545,7 +2546,7 @@ int64_t &maxVersion(Node *n, ConflictSet::Impl *impl) {
return impl->rootMaxVersion;
}
switch (n->getType()) {
case Type_Node0:
case Type_Node0: // GCOVR_EXCL_LINE
__builtin_unreachable(); // GCOVR_EXCL_LINE
case Type_Node3: {
auto *n3 = static_cast<Node3 *>(n);
@@ -2588,20 +2589,39 @@ void ConflictSet::check(const ReadRange *reads, Result *results,
void ConflictSet::addWrites(const WriteRange *writes, int count,
int64_t writeVersion) {
return impl->addWrites(writes, count, writeVersion);
mallocBytesDelta = 0;
impl->addWrites(writes, count, writeVersion);
impl->totalBytes += mallocBytesDelta;
#if SHOW_MEMORY
if (impl->totalBytes != mallocBytes) {
abort();
}
#endif
}
void ConflictSet::setOldestVersion(int64_t oldestVersion) {
return impl->setOldestVersion(oldestVersion);
mallocBytesDelta = 0;
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)
: impl(new (safe_malloc(sizeof(Impl))) Impl{oldestVersion}) {}
: impl((mallocBytesDelta = 0,
new (safe_malloc(sizeof(Impl))) Impl{oldestVersion})) {
impl->totalBytes += mallocBytesDelta;
}
ConflictSet::~ConflictSet() {
if (impl) {
impl->~Impl();
safe_free(impl);
safe_free(impl, sizeof(*impl));
}
}
@@ -2627,24 +2647,44 @@ ConflictSet_check(void *cs, const ConflictSet_ReadRange *reads,
__attribute__((__visibility__("default"))) void
ConflictSet_addWrites(void *cs, const ConflictSet_WriteRange *writes, int count,
int64_t writeVersion) {
((ConflictSet::Impl *)cs)->addWrites(writes, count, writeVersion);
auto *impl = ((ConflictSet::Impl *)cs);
mallocBytesDelta = 0;
impl->addWrites(writes, count, writeVersion);
impl->totalBytes += mallocBytesDelta;
}
__attribute__((__visibility__("default"))) void
ConflictSet_setOldestVersion(void *cs, int64_t oldestVersion) {
((ConflictSet::Impl *)cs)->setOldestVersion(oldestVersion);
auto *impl = ((ConflictSet::Impl *)cs);
mallocBytesDelta = 0;
impl->setOldestVersion(oldestVersion);
impl->totalBytes += mallocBytesDelta;
}
__attribute__((__visibility__("default"))) void *
ConflictSet_create(int64_t oldestVersion) {
return new (safe_malloc(sizeof(ConflictSet::Impl)))
mallocBytesDelta = 0;
auto *result = new (safe_malloc(sizeof(ConflictSet::Impl)))
ConflictSet::Impl{oldestVersion};
result->totalBytes += mallocBytesDelta;
return result;
}
__attribute__((__visibility__("default"))) void ConflictSet_destroy(void *cs) {
using Impl = ConflictSet::Impl;
((Impl *)cs)->~Impl();
safe_free(cs);
safe_free(cs, sizeof(Impl));
}
__attribute__((__visibility__("default"))) int64_t
ConflictSet_getBytes(void *cs) {
using Impl = ConflictSet::Impl;
return ((Impl *)cs)->totalBytes;
}
}
// Make sure abi is well-defined
static_assert(std::is_standard_layout_v<ConflictSet::Result>);
static_assert(std::is_standard_layout_v<ConflictSet::Key>);
static_assert(std::is_standard_layout_v<ConflictSet::ReadRange>);
static_assert(std::is_standard_layout_v<ConflictSet::WriteRange>);
namespace {
std::string getSearchPathPrintable(Node *n) {
@@ -2839,8 +2879,8 @@ Iterator firstGeq(Node *n, std::string_view key) {
case Type_Node256:
minNumChildren = kMinChildrenNode256;
break;
default: // GCOVR_EXCL_LINE
__builtin_unreachable(); // GCOVR_EXCL_LINE
default:
abort();
}
if (node->numChildren + int(node->entryPresent) < minNumChildren) {
fprintf(stderr,
@@ -2857,8 +2897,8 @@ Iterator firstGeq(Node *n, std::string_view key) {
}
}
bool checkCorrectness(Node *node, int64_t oldestVersion,
ConflictSet::Impl *impl) {
[[maybe_unused]] bool checkCorrectness(Node *node, int64_t oldestVersion,
ConflictSet::Impl *impl) {
bool success = true;
checkParentPointers(node, success);
@@ -2898,8 +2938,8 @@ int64_t getNodeSize(struct Node *n) {
return sizeof(Node48);
case Type_Node256:
return sizeof(Node256);
default: // GCOVR_EXCL_LINE
__builtin_unreachable(); // GCOVR_EXCL_LINE
default:
abort();
}
}

View File

@@ -2,15 +2,25 @@
#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) {
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());
}
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
}

View File

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

View File

@@ -2,6 +2,8 @@
#include "ConflictSet.h"
using namespace weaselab;
#include <bit>
#include <cassert>
#include <compare>
@@ -10,10 +12,12 @@
#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>
@@ -35,6 +39,17 @@ 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.
@@ -43,43 +58,55 @@ operator<=>(const std::span<const uint8_t> &lhs,
#if SHOW_MEMORY
inline int64_t mallocBytes = 0;
inline int64_t peakMallocBytes = 0;
constexpr auto kIntMallocHeaderSize = 16;
#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;
}
void *p = malloc(s + kIntMallocHeaderSize);
if (p == nullptr) {
abort();
}
memcpy(p, &s, sizeof(s));
return (char *)p + kIntMallocHeaderSize;
#else
void *p = malloc(s);
if (p == nullptr) {
abort();
}
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) {
__attribute__((always_inline)) inline void safe_free(void *p, size_t s) {
mallocBytesDelta -= s;
#if SHOW_MEMORY
size_t s;
memcpy(&s, (char *)p - kIntMallocHeaderSize, sizeof(s));
mallocBytes -= s;
free((char *)p - kIntMallocHeaderSize);
free(p);
#else
#ifndef NDEBUG
(char *&)p -= kMallocHeaderSize;
size_t expected;
memcpy(&expected, p, sizeof(expected));
assert(s == expected);
#endif
free(p);
#endif
}
@@ -166,7 +193,7 @@ inline Arena::Arena(int initialSize) : impl(nullptr) {
inline void onDestroy(Arena::ArenaImpl *impl) {
while (impl) {
auto *prev = impl->prev;
safe_free(impl);
safe_free(impl, sizeof(Arena::ArenaImpl) + impl->capacity);
impl = prev;
}
}
@@ -386,34 +413,6 @@ 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) {
writeVersionMap[""] = oldestVersion;
@@ -507,6 +506,18 @@ 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 {
@@ -519,22 +530,10 @@ template <class ConflictSetImpl> struct TestDriver {
ConflictSetImpl cs{oldestVersion};
ReferenceImpl refImpl{oldestVersion};
constexpr static auto kMaxKeyLen = 128;
constexpr static auto kMaxKeyLen = 8;
bool ok = true;
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() {
@@ -669,32 +668,60 @@ 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);
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);
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;
}
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;
}

13
Jenkinsfile vendored
View File

@@ -48,6 +48,17 @@ pipeline {
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]') {
agent {
dockerfile {
@@ -97,7 +108,7 @@ 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 --exclude '.*third_party.*' --cobertura > build/coverage.xml
gcovr -f ConflictSet.cpp --cobertura > build/coverage.xml
'''
cobertura autoUpdateHealth: false, autoUpdateStability: false, coberturaReportFile: 'build/coverage.xml', conditionalCoverageTargets: '70, 0, 0', failUnhealthy: false, failUnstable: false, lineCoverageTargets: '80, 0, 0', maxNumberOfBuilds: 0, methodCoverageTargets: '80, 0, 0', onlyStable: false, sourceEncoding: 'ASCII', zoomCoverageChart: false
}

108
README.md
View File

@@ -9,49 +9,49 @@ Hardware for all benchmarks is a mac m1 2020.
## Skip list
```
New conflict set: 1.962 sec
0.637 Mtransactions/sec
2.549 Mkeys/sec
Detect only: 1.853 sec
0.674 Mtransactions/sec
2.698 Mkeys/sec
Skiplist only: 1.269 sec
0.985 Mtransactions/sec
3.940 Mkeys/sec
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
Performance counters:
Build: 0.0526
Add: 0.054
Detect: 1.85
D.Sort: 0.413
D.Combine: 0.0148
D.CheckRead: 0.678
D.CheckIntraBatch: 0.00679
D.MergeWrite: 0.591
D.RemoveBefore: 0.147
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
```
## Radix tree (this implementation)
```
New conflict set: 1.342 sec
0.931 Mtransactions/sec
3.726 Mkeys/sec
Detect only: 1.227 sec
1.018 Mtransactions/sec
4.074 Mkeys/sec
Skiplist only: 0.576 sec
2.169 Mtransactions/sec
8.676 Mkeys/sec
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
Performance counters:
Build: 0.0568
Add: 0.0564
Detect: 1.23
D.Sort: 0.414
D.Combine: 0.0162
D.CheckRead: 0.228
D.CheckIntraBatch: 0.00665
D.MergeWrite: 0.348
D.RemoveBefore: 0.211
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
```
# Our benchmark
@@ -60,25 +60,25 @@ Performance counters:
| ns/op | op/s | err% | total | benchmark
|--------------------:|--------------------:|--------:|----------:|:----------
| 254.17 | 3,934,426.23 | 0.3% | 0.01 | `point reads`
| 275.58 | 3,628,769.92 | 2.1% | 0.01 | `prefix reads`
| 494.87 | 2,020,718.51 | 0.7% | 0.01 | `range reads`
| 495.91 | 2,016,512.61 | 1.7% | 0.01 | `point writes`
| 478.11 | 2,091,578.00 | 0.8% | 0.01 | `prefix writes`
| 307.08 | 3,256,480.40 | 1.9% | 0.04 | `range writes`
| 610.89 | 1,636,953.81 | 0.7% | 0.01 | `monotonic increasing point writes`
| 246.99 | 4,048,700.59 | 0.2% | 0.01 | `point reads`
| 260.16 | 3,843,784.65 | 0.1% | 0.01 | `prefix reads`
| 493.35 | 2,026,953.19 | 0.1% | 0.01 | `range reads`
| 462.05 | 2,164,289.23 | 0.6% | 0.01 | `point writes`
| 448.19 | 2,231,205.25 | 0.9% | 0.01 | `prefix writes`
| 255.83 | 3,908,845.72 | 1.5% | 0.02 | `range writes`
| 582.63 | 1,716,349.02 | 1.3% | 0.01 | `monotonic increasing point writes`
## Radix tree (this implementation)
| ns/op | op/s | err% | total | benchmark
|--------------------:|--------------------:|--------:|----------:|:----------
| 22.39 | 44,667,832.17 | 0.2% | 0.01 | `point reads`
| 53.89 | 18,554,840.41 | 3.4% | 0.01 | `prefix reads`
| 217.71 | 4,593,220.24 | 0.2% | 0.01 | `range reads`
| 29.72 | 33,642,099.66 | 1.1% | 0.01 | `point writes`
| 44.86 | 22,294,037.02 | 0.8% | 0.01 | `prefix writes`
| 55.00 | 18,181,818.18 | 0.8% | 0.03 | `range writes`
| 109.21 | 9,156,917.53 | 2.9% | 0.01 | `monotonic increasing point writes`
| 19.42 | 51,483,206.67 | 0.3% | 0.01 | `point reads`
| 58.43 | 17,115,612.57 | 0.1% | 0.01 | `prefix reads`
| 216.09 | 4,627,766.60 | 0.2% | 0.01 | `range reads`
| 28.35 | 35,267,567.72 | 0.2% | 0.01 | `point writes`
| 43.43 | 23,026,226.17 | 0.2% | 0.01 | `prefix writes`
| 50.00 | 20,000,000.00 | 0.0% | 0.01 | `range writes`
| 92.38 | 10,824,863.69 | 4.1% | 0.01 | `monotonic increasing point writes`
# "Real data" test
@@ -87,13 +87,13 @@ Point queries only, best of three runs. Gc ratio is the ratio of time spent doin
## skip list
```
Check: 11.7282 seconds, 318.763 MB/s, Add: 5.76499 seconds, 121.776 MB/s, Gc ratio: 48.0772%
Check: 11.3385 seconds, 329.718 MB/s, Add: 5.35612 seconds, 131.072 MB/s, Gc ratio: 45.7173%
```
## radix tree
```
Check: 3.27475 seconds, 1141.62 MB/s, Add: 2.13594 seconds, 328.678 MB/s, Gc ratio: 50.1739%
Check: 2.48583 seconds, 1503.93 MB/s, Add: 2.12768 seconds, 329.954 MB/s, Gc ratio: 41.7943%
```
## hash table
@@ -101,5 +101,5 @@ Check: 3.27475 seconds, 1141.62 MB/s, Add: 2.13594 seconds, 328.678 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.86291 seconds, 2006.81 MB/s, Add: 0.923653 seconds, 760.067 MB/s, Gc ratio: 54.2605%
```
Check: 1.83386 seconds, 2038.6 MB/s, Add: 0.601411 seconds, 1167.32 MB/s, Gc ratio: 48.9776%
```

View File

@@ -8,6 +8,8 @@
#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())
@@ -28,7 +30,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;
@@ -40,6 +42,8 @@ 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;
@@ -109,6 +113,10 @@ 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;
@@ -118,8 +126,9 @@ 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%%\n",
checkTime, checkBytes / checkTime * 1e-6, addTime,
addBytes / addTime * 1e-6, gcTime / (gcTime + addTime) * 1e2);
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));
}

155
ScriptTest.cpp Normal file
View File

@@ -0,0 +1,155 @@
#include "Internal.h"
#include <ConflictSet.h>
#include <chrono>
#include <cstring>
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <unistd.h>
#include <vector>
inline size_t getPageSize() {
static size_t kPageSize = sysconf(_SC_PAGESIZE);
return kPageSize;
}
/// Helper for rounding up to page size (or some other alignment)
constexpr inline size_t rightAlign(size_t offset, size_t alignment) {
return offset % alignment == 0 ? offset
: ((offset / alignment) + 1) * alignment;
}
using StringView = std::basic_string_view<uint8_t>;
inline StringView operator"" _v(const char *str, size_t size) {
return {reinterpret_cast<const uint8_t *>(str), size};
}
int main(int argc, const char **argv) {
ConflictSet cs{0};
ReferenceImpl ref{0};
for (int i = 1; i < argc; ++i) {
int fd = open(argv[i], O_RDONLY);
struct stat st;
if (fstat(fd, &st) == -1) {
int err = errno;
fprintf(stderr, "stat error %s - %s\n", argv[i], strerror(err));
fflush(stderr);
abort();
}
int64_t size = rightAlign(st.st_size, getPageSize());
const uint8_t *begin =
(uint8_t *)mmap(0, size, PROT_READ, MAP_PRIVATE, fd, 0);
madvise((void *)begin, size, MADV_SEQUENTIAL);
auto *const mapOriginal = begin;
const auto sizeOriginal = size;
StringView b;
StringView e;
int64_t v = 0;
int64_t lastWriteVersion = 0;
int64_t lastOldestVersion = 0;
std::vector<ConflictSet::WriteRange> writeRanges;
std::vector<ConflictSet::ReadRange> readRanges;
std::vector<ConflictSet::Result> results;
for (uint8_t *end = (uint8_t *)memchr(begin, '\n', size); end != nullptr;) {
StringView line{begin, static_cast<size_t>(end - begin)};
size -= end - begin + 1;
begin = end + 1;
end = (uint8_t *)memchr(begin, '\n', size);
if (line.starts_with("begin"_v)) {
b = line.substr("begin "_v.size(), line.size());
printf("b <- %.*s\n", int(b.size()), b.data());
} else if (line.starts_with("end"_v)) {
e = line.substr("end "_v.size(), line.size());
printf("e <- %.*s\n", int(e.size()), e.data());
} else if (line.starts_with("version"_v)) {
line = line.substr("version "_v.size(), line.size());
v = 0;
for (auto c : line) {
v = v * 10 + int(c) - int('0');
}
printf("v <- %" PRId64 "\n", v);
} else if (line.starts_with("pointread"_v)) {
printf("pointread\n");
ConflictSet::ReadRange r;
r.begin.p = b.data();
r.begin.len = b.size();
r.end.len = 0;
r.readVersion = v;
readRanges.push_back(r);
} else if (line.starts_with("pointwrite"_v)) {
printf("pointwrite\n");
assert(writeRanges.empty() ||
(writeRanges.back().end.len == 0 ? writeRanges.back().begin
: writeRanges.back().end) < b);
ConflictSet::WriteRange w;
w.begin.p = b.data();
w.begin.len = b.size();
w.end.len = 0;
writeRanges.push_back(w);
} else if (line.starts_with("rangeread"_v)) {
printf("rangeread\n");
ConflictSet::ReadRange r;
r.begin.p = b.data();
r.begin.len = b.size();
r.end.p = e.data();
r.end.len = e.size();
r.readVersion = v;
readRanges.push_back(r);
} else if (line.starts_with("rangewrite"_v)) {
printf("rangewrite\n");
assert(b < e);
assert(writeRanges.empty() ||
(writeRanges.back().end.len == 0 ? writeRanges.back().begin
: writeRanges.back().end) < b);
ConflictSet::WriteRange w;
w.begin.p = b.data();
w.begin.len = b.size();
w.end.p = e.data();
w.end.len = e.size();
writeRanges.push_back(w);
} else if (line.starts_with("check"_v)) {
printf("check\n");
Arena arena;
auto *expected = new (arena) ConflictSet::Result[readRanges.size()];
auto *actual = new (arena) ConflictSet::Result[readRanges.size()];
ref.check(readRanges.data(), expected, readRanges.size());
cs.check(readRanges.data(), actual, readRanges.size());
for (int i = 0; i < int(readRanges.size()); ++i) {
if (expected[i] != actual[i]) {
fprintf(stderr, "Expected %s, got %s at index %d\n",
resultToStr(expected[i]), resultToStr(actual[i]), i);
return 1;
}
}
readRanges = {};
} else if (line.starts_with("addwrites"_v)) {
printf("addwrites\n");
assert(v > lastWriteVersion);
lastWriteVersion = v;
cs.addWrites(writeRanges.data(), writeRanges.size(), v);
ref.addWrites(writeRanges.data(), writeRanges.size(), v);
writeRanges = {};
} else if (line.starts_with("setoldest"_v)) {
printf("setoldest\n");
assert(v > lastOldestVersion);
lastOldestVersion = v;
cs.setOldestVersion(v);
ref.setOldestVersion(v);
} else if (line.empty() || line.starts_with(";"_v)) {
// skip
} else {
printf("Unrecognized line: %.*s\n", int(line.size()), line.data());
}
}
munmap((void *)mapOriginal, sizeOriginal);
close(fd);
}
}

View File

@@ -149,7 +149,7 @@ private:
setMaxVersion(level, v);
}
void destroy() { safe_free(this); }
void destroy() { safe_free(this, getNodeSize()); }
private:
int getNodeSize() const {
@@ -627,6 +627,8 @@ struct __attribute__((visibility("hidden"))) ConflictSet::Impl {
removalArena, {finger.getValue().data(), finger.getValue().size()});
}
int64_t totalBytes = 0;
private:
int64_t keyUpdates = 10;
Arena removalArena;
@@ -637,25 +639,44 @@ private:
void ConflictSet::check(const ReadRange *reads, Result *results,
int count) const {
return impl->check(reads, results, count);
impl->check(reads, results, count);
}
void ConflictSet::addWrites(const WriteRange *writes, int count,
int64_t writeVersion) {
return impl->addWrites(writes, count, writeVersion);
mallocBytesDelta = 0;
impl->addWrites(writes, count, writeVersion);
impl->totalBytes += mallocBytesDelta;
#if SHOW_MEMORY
if (impl->totalBytes != mallocBytes) {
abort();
}
#endif
}
void ConflictSet::setOldestVersion(int64_t oldestVersion) {
return impl->setOldestVersion(oldestVersion);
mallocBytesDelta = 0;
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)
: impl(new (safe_malloc(sizeof(Impl))) Impl{oldestVersion}) {}
: impl((mallocBytesDelta = 0,
new (safe_malloc(sizeof(Impl))) Impl{oldestVersion})) {
impl->totalBytes += mallocBytesDelta;
}
ConflictSet::~ConflictSet() {
if (impl) {
impl->~Impl();
safe_free(impl);
safe_free(impl, sizeof(Impl));
}
}
@@ -695,7 +716,12 @@ ConflictSet_create(int64_t oldestVersion) {
__attribute__((__visibility__("default"))) void ConflictSet_destroy(void *cs) {
using Impl = ConflictSet::Impl;
((Impl *)cs)->~Impl();
safe_free(cs);
safe_free(cs, sizeof(Impl));
}
__attribute__((__visibility__("default"))) int64_t
ConflictSet_getBytes(void *cs) {
using Impl = ConflictSet::Impl;
return ((Impl *)cs)->totalBytes;
}
}

View File

@@ -7,6 +7,7 @@ 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;
@@ -17,6 +18,8 @@ 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;
}

View File

@@ -2,6 +2,8 @@
#include <cassert>
using namespace weaselab;
int main(void) {
ConflictSet cs(0);
ConflictSet::WriteRange w;
@@ -17,4 +19,6 @@ 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.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

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