Compare commits
67 Commits
2e246ec6a4
...
v0.0.2
Author | SHA1 | Date | |
---|---|---|---|
24b0f6b7e4 | |||
e77c3fdee6 | |||
383b956bc0 | |||
5fad15305a | |||
ad91fb36a5 | |||
38c1481432 | |||
771ae896e7 | |||
5bf72bda61 | |||
a534f3b758 | |||
ae4fa889c7 | |||
e3d3b0ec0d | |||
dea8d6ae01 | |||
dbc6f2313a | |||
4f51878642 | |||
215865a462 | |||
348ebf016a | |||
377259ffa0 | |||
70220d95e7 | |||
71c39f9955 | |||
8cc17158fd | |||
ab211c646a | |||
7af961f141 | |||
a91df62608 | |||
0a1843a161 | |||
4edf0315d9 | |||
14515e186a | |||
b0085df5ad | |||
76a7e17b29 | |||
5cf43d1bfa | |||
25cc427ec5 | |||
c15c2e7b44 | |||
a4d1f91670 | |||
b7cdecaf71 | |||
cda28643a6 | |||
cdb5360b9a | |||
ef224a60f4 | |||
6222b74787 | |||
19edc6f78f | |||
3f9d01c46a | |||
db03c6f901 | |||
c1698b040b | |||
2e08b54785 | |||
aa6f237d50 | |||
becfd25139 | |||
d78b36821b | |||
ce79b47fbe | |||
727b7e642a | |||
cb4c2b7e1e | |||
ef9b789745 | |||
edd7bcaa1e | |||
be8ac879c5 | |||
83c7f66d67 | |||
a5710b8282 | |||
c31eebd5de | |||
ddeb059968 | |||
5a0bcf9a5a | |||
97717cec86 | |||
6a13c43a78 | |||
c6c438bae2 | |||
7d4f832b43 | |||
5b0c3c2428 | |||
f2b5e9b0bf | |||
8e0e65dac6 | |||
5aab76847a | |||
1a51aa00e5 | |||
3975bada0c | |||
a5330b6e23 |
2
.clangd
2
.clangd
@@ -1,2 +1,2 @@
|
||||
CompileFlags:
|
||||
Add: [-DENABLE_MAIN, -UNDEBUG, -DENABLE_FUZZ, -fexceptions]
|
||||
Add: [-DENABLE_MAIN, -UNDEBUG, -DENABLE_FUZZ, -DTHREAD_TEST, -fexceptions]
|
||||
|
@@ -1,11 +1,11 @@
|
||||
repos:
|
||||
- repo: https://github.com/pre-commit/mirrors-clang-format
|
||||
rev: b111689e7b5cba60be3c62d5db2bd1357f4d36ca
|
||||
rev: 6d365699efc33b1b432eab5b4ae331a19e1857de # frozen: v18.1.2
|
||||
hooks:
|
||||
- id: clang-format
|
||||
exclude: ".*third_party/.*"
|
||||
- repo: https://github.com/cheshirekow/cmake-format-precommit
|
||||
rev: e2c2116d86a80e72e7146a06e68b7c228afc6319
|
||||
rev: e2c2116d86a80e72e7146a06e68b7c228afc6319 # frozen: v0.6.13
|
||||
hooks:
|
||||
- id: cmake-format
|
||||
- repo: local
|
||||
@@ -23,4 +23,8 @@ repos:
|
||||
description: disallow checking in SHOW_MEMORY=1
|
||||
entry: '^#define SHOW_MEMORY 1$'
|
||||
language: pygrep
|
||||
types: [c++]
|
||||
types: [c++]
|
||||
- repo: https://github.com/shellcheck-py/shellcheck-py
|
||||
rev: a23f6b85d0fdd5bb9d564e2579e678033debbdff # frozen: v0.10.0.1
|
||||
hooks:
|
||||
- id: shellcheck
|
@@ -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(); }
|
||||
|
266
CMakeLists.txt
266
CMakeLists.txt
@@ -1,15 +1,26 @@
|
||||
cmake_minimum_required(VERSION 3.18)
|
||||
project(
|
||||
conflict_set
|
||||
VERSION 0.0.1
|
||||
conflict-set
|
||||
VERSION 0.0.2
|
||||
DESCRIPTION
|
||||
"A data structure for optimistic concurrency control on ranges of bitwise-lexicographically-ordered keys."
|
||||
HOMEPAGE_URL "https://git.weaselab.dev/weaselab/conflict-set"
|
||||
LANGUAGES C CXX)
|
||||
set(CMAKE_CXX_STANDARD 20)
|
||||
|
||||
include(CMakePushCheckState)
|
||||
include(CheckCXXCompilerFlag)
|
||||
include(CheckIncludeFileCXX)
|
||||
include(CheckCXXSourceCompiles)
|
||||
|
||||
set(DEFAULT_BUILD_TYPE "Release")
|
||||
|
||||
if(EMSCRIPTEN OR CMAKE_SYSTEM_NAME STREQUAL WASI)
|
||||
set(WASM ON)
|
||||
else()
|
||||
set(WASM OFF)
|
||||
endif()
|
||||
|
||||
if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
|
||||
message(
|
||||
STATUS
|
||||
@@ -23,16 +34,32 @@ if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
|
||||
endif()
|
||||
|
||||
add_compile_options(-fdata-sections -ffunction-sections -Wswitch-enum
|
||||
-Werror=switch-enum)
|
||||
-Werror=switch-enum -fPIC)
|
||||
|
||||
set(full_relro_flags "-pie;LINKER:-z,relro,-z,now,-z,noexecstack")
|
||||
cmake_push_check_state()
|
||||
list(APPEND CMAKE_REQUIRED_LINK_OPTIONS ${full_relro_flags})
|
||||
check_cxx_source_compiles("int main(){}" HAS_FULL_RELRO FAIL_REGEX "warning:")
|
||||
if(HAS_FULL_RELRO)
|
||||
add_link_options(${full_relro_flags})
|
||||
endif()
|
||||
cmake_pop_check_state()
|
||||
|
||||
set(version_script_flags LINKER:--version-script=${CMAKE_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
|
||||
# https://valgrind.org/docs/manual/manual-core-adv.html#manual-core-adv.clientreq
|
||||
include_directories(SYSTEM ${CMAKE_SOURCE_DIR}/third_party/valgrind)
|
||||
|
||||
if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
|
||||
add_compile_options(-Wno-maybe-uninitialized
|
||||
$<$<COMPILE_LANGUAGE:CXX>:-Wno-invalid-offsetof>)
|
||||
endif()
|
||||
add_compile_options($<$<COMPILE_LANGUAGE:CXX>:-Wno-invalid-offsetof>)
|
||||
|
||||
if(APPLE)
|
||||
add_link_options(-Wl,-dead_strip)
|
||||
@@ -40,32 +67,46 @@ else()
|
||||
add_link_options(-Wl,--gc-sections)
|
||||
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)
|
||||
if(EMSCRIPTEN)
|
||||
# https://github.com/emscripten-core/emscripten/issues/15377#issuecomment-1285167486
|
||||
add_link_options(-lnodefs.js -lnoderawfs.js)
|
||||
add_link_options(-s ALLOW_MEMORY_GROWTH)
|
||||
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)
|
||||
if(NOT USE_SIMD_FALLBACK)
|
||||
cmake_push_check_state()
|
||||
list(APPEND CMAKE_REQUIRED_FLAGS -msimd128)
|
||||
check_include_file_cxx("wasm_simd128.h" HAS_WASM_SIMD)
|
||||
if(HAS_WASM_SIMD)
|
||||
add_compile_options(-msimd128)
|
||||
add_compile_definitions(HAS_WASM_SIMD)
|
||||
endif()
|
||||
cmake_pop_check_state()
|
||||
|
||||
cmake_push_check_state()
|
||||
list(APPEND CMAKE_REQUIRED_FLAGS -mavx)
|
||||
check_include_file_cxx("immintrin.h" HAS_AVX)
|
||||
if(HAS_AVX)
|
||||
add_compile_options(-mavx)
|
||||
add_compile_definitions(HAS_AVX)
|
||||
endif()
|
||||
cmake_pop_check_state()
|
||||
|
||||
check_include_file_cxx("arm_neon.h" HAS_ARM_NEON)
|
||||
if(HAS_ARM_NEON)
|
||||
add_compile_definitions(HAS_ARM_NEON)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
set(CMAKE_CXX_IMPLICIT_LINK_LIBRARIES "")
|
||||
|
||||
add_library(${PROJECT_NAME}_object OBJECT ConflictSet.cpp)
|
||||
target_compile_options(${PROJECT_NAME}_object PRIVATE -fPIC -fno-exceptions
|
||||
add_library(${PROJECT_NAME}-object OBJECT ConflictSet.cpp)
|
||||
target_compile_options(${PROJECT_NAME}-object PRIVATE -fno-exceptions
|
||||
-fvisibility=hidden)
|
||||
target_include_directories(${PROJECT_NAME}_object
|
||||
target_include_directories(${PROJECT_NAME}-object
|
||||
PRIVATE ${CMAKE_SOURCE_DIR}/include)
|
||||
|
||||
add_library(${PROJECT_NAME} SHARED $<TARGET_OBJECTS:${PROJECT_NAME}_object>)
|
||||
add_library(${PROJECT_NAME} SHARED $<TARGET_OBJECTS:${PROJECT_NAME}-object>)
|
||||
set_target_properties(
|
||||
${PROJECT_NAME} PROPERTIES LIBRARY_OUTPUT_DIRECTORY
|
||||
"${CMAKE_BINARY_DIR}/radix_tree")
|
||||
@@ -73,24 +114,32 @@ if(NOT CMAKE_BUILD_TYPE STREQUAL Debug)
|
||||
set_target_properties(${PROJECT_NAME} PROPERTIES LINKER_LANGUAGE C)
|
||||
endif()
|
||||
|
||||
if(NOT APPLE)
|
||||
if(HAS_VERSION_SCRIPT)
|
||||
target_link_options(${PROJECT_NAME} PRIVATE
|
||||
LINKER:--version-script=${CMAKE_SOURCE_DIR}/linker.map)
|
||||
endif()
|
||||
|
||||
add_library(${PROJECT_NAME}_static STATIC
|
||||
$<TARGET_OBJECTS:${PROJECT_NAME}_object>)
|
||||
add_library(${PROJECT_NAME}-static STATIC
|
||||
$<TARGET_OBJECTS:${PROJECT_NAME}-object>)
|
||||
if(NOT CMAKE_BUILD_TYPE STREQUAL Debug)
|
||||
set_target_properties(${PROJECT_NAME}_static PROPERTIES LINKER_LANGUAGE C)
|
||||
set_target_properties(${PROJECT_NAME}-static PROPERTIES LINKER_LANGUAGE C)
|
||||
endif()
|
||||
|
||||
if(NOT APPLE AND CMAKE_OBJCOPY)
|
||||
if(APPLE)
|
||||
add_custom_command(
|
||||
TARGET conflict_set_static
|
||||
TARGET ${PROJECT_NAME}-static
|
||||
PRE_BUILD
|
||||
COMMAND ${CMAKE_SOURCE_DIR}/privatize_symbols_macos.sh
|
||||
$<TARGET_OBJECTS:${PROJECT_NAME}-object>)
|
||||
else()
|
||||
add_custom_command(
|
||||
TARGET ${PROJECT_NAME}-static
|
||||
POST_BUILD
|
||||
COMMAND
|
||||
${CMAKE_OBJCOPY} --keep-global-symbols=${CMAKE_SOURCE_DIR}/symbols.txt
|
||||
$<TARGET_FILE:${PROJECT_NAME}_static>)
|
||||
${CMAKE_OBJCOPY}
|
||||
--keep-global-symbols=${CMAKE_SOURCE_DIR}/symbol-exports.txt
|
||||
$<TARGET_FILE:${PROJECT_NAME}-static> || echo
|
||||
"Proceeding with all symbols global in static library")
|
||||
endif()
|
||||
|
||||
set(TEST_FLAGS -Wall -Wextra -Wunreachable-code -Wpedantic -UNDEBUG)
|
||||
@@ -99,30 +148,51 @@ include(CTest)
|
||||
|
||||
if(BUILD_TESTING)
|
||||
|
||||
# Shared library version of FoundationDB's skip list implementation
|
||||
add_library(skip_list SHARED SkipList.cpp)
|
||||
target_compile_options(skip_list PRIVATE -fPIC -fno-exceptions
|
||||
-fvisibility=hidden)
|
||||
target_include_directories(skip_list PUBLIC ${CMAKE_SOURCE_DIR}/include)
|
||||
set_target_properties(skip_list PROPERTIES LIBRARY_OUTPUT_DIRECTORY
|
||||
"${CMAKE_BINARY_DIR}/skip_list")
|
||||
set_target_properties(skip_list PROPERTIES OUTPUT_NAME ${PROJECT_NAME})
|
||||
set_target_properties(skip_list PROPERTIES VERSION ${PROJECT_VERSION}
|
||||
SOVERSION ${PROJECT_VERSION_MAJOR})
|
||||
# corpus tests, which are tests curated by libfuzzer. The goal is to get broad
|
||||
# coverage with a small number of tests.
|
||||
|
||||
# Shared library version of a std::unordered_map-based conflict set (point
|
||||
# queries only)
|
||||
add_library(hash_table SHARED HashTable.cpp)
|
||||
target_compile_options(hash_table PRIVATE -fPIC -fno-exceptions
|
||||
-fvisibility=hidden)
|
||||
target_include_directories(hash_table PUBLIC ${CMAKE_SOURCE_DIR}/include)
|
||||
set_target_properties(hash_table PROPERTIES LIBRARY_OUTPUT_DIRECTORY
|
||||
"${CMAKE_BINARY_DIR}/hash_table")
|
||||
set_target_properties(hash_table PROPERTIES OUTPUT_NAME ${PROJECT_NAME})
|
||||
set_target_properties(
|
||||
hash_table PROPERTIES VERSION ${PROJECT_VERSION} SOVERSION
|
||||
${PROJECT_VERSION_MAJOR})
|
||||
file(GLOB CORPUS_TESTS ${CMAKE_SOURCE_DIR}/corpus/*)
|
||||
|
||||
# extra testing that relies on shared libraries, which aren't available with
|
||||
# wasm
|
||||
if(NOT WASM)
|
||||
# Shared library version of FoundationDB's skip list implementation
|
||||
add_library(skip_list SHARED SkipList.cpp)
|
||||
target_compile_options(skip_list PRIVATE -fno-exceptions
|
||||
-fvisibility=hidden)
|
||||
target_include_directories(skip_list PUBLIC ${CMAKE_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)
|
||||
target_include_directories(conflict_set_main
|
||||
PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include)
|
||||
@@ -149,10 +219,7 @@ if(BUILD_TESTING)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# corpus tests
|
||||
|
||||
file(GLOB CORPUS_TESTS ${CMAKE_SOURCE_DIR}/corpus/*)
|
||||
|
||||
# whitebox tests
|
||||
add_executable(fuzz_driver ConflictSet.cpp FuzzTestDriver.cpp)
|
||||
target_compile_options(fuzz_driver PRIVATE ${TEST_FLAGS})
|
||||
if(NOT CMAKE_CROSSCOMPILING)
|
||||
@@ -167,13 +234,39 @@ if(BUILD_TESTING)
|
||||
add_test(NAME conflict_set_fuzz_${hash} COMMAND fuzz_driver ${TEST})
|
||||
endforeach()
|
||||
|
||||
# tsan tests
|
||||
if(NOT CMAKE_CROSSCOMPILING AND NOT CMAKE_BUILD_TYPE STREQUAL Debug)
|
||||
add_executable(tsan_driver ConflictSet.cpp FuzzTestDriver.cpp)
|
||||
target_compile_options(tsan_driver PRIVATE ${TEST_FLAGS} -fsanitize=thread)
|
||||
target_link_options(tsan_driver PRIVATE -fsanitize=thread)
|
||||
target_compile_definitions(tsan_driver PRIVATE ENABLE_FUZZ THREAD_TEST)
|
||||
target_include_directories(tsan_driver
|
||||
PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include)
|
||||
foreach(TEST ${CORPUS_TESTS})
|
||||
get_filename_component(hash ${TEST} NAME)
|
||||
add_test(NAME conflict_set_tsan_${hash} COMMAND tsan_driver ${TEST})
|
||||
endforeach()
|
||||
endif()
|
||||
|
||||
# blackbox tests
|
||||
add_executable(driver TestDriver.cpp)
|
||||
target_compile_options(driver PRIVATE ${TEST_FLAGS})
|
||||
target_link_libraries(driver PRIVATE ${PROJECT_NAME})
|
||||
foreach(TEST ${CORPUS_TESTS})
|
||||
get_filename_component(hash ${TEST} NAME)
|
||||
add_test(NAME conflict_set_blackbox_${hash} COMMAND driver ${TEST})
|
||||
endforeach()
|
||||
|
||||
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)
|
||||
# scripted tests. Written manually to fill in anything libfuzzer couldn't
|
||||
# find.
|
||||
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()
|
||||
|
||||
find_program(VALGRIND_EXE valgrind)
|
||||
if(VALGRIND_EXE AND NOT CMAKE_CROSSCOMPILING)
|
||||
@@ -182,12 +275,6 @@ if(BUILD_TESTING)
|
||||
$<TARGET_FILE:driver> ${CORPUS_TESTS})
|
||||
endif()
|
||||
|
||||
foreach(TEST ${CORPUS_TESTS})
|
||||
get_filename_component(hash ${TEST} NAME)
|
||||
add_test(NAME conflict_set_blackbox_${hash} COMMAND driver ${TEST})
|
||||
add_test(NAME skip_list_${hash} COMMAND driver_skip_list ${TEST})
|
||||
endforeach()
|
||||
|
||||
# api smoke tests
|
||||
|
||||
# c90
|
||||
@@ -208,28 +295,41 @@ if(BUILD_TESTING)
|
||||
PROPERTIES CXX_STANDARD_REQUIRED ON)
|
||||
add_test(NAME conflict_set_cxx_api_test COMMAND conflict_set_cxx_api_test)
|
||||
|
||||
if(NOT APPLE AND NOT CMAKE_BUILD_TYPE STREQUAL Debug)
|
||||
# symbol visibility tests
|
||||
if(NOT WASM AND NOT CMAKE_BUILD_TYPE STREQUAL Debug)
|
||||
if(APPLE)
|
||||
set(symbol_exports ${CMAKE_SOURCE_DIR}/apple-symbol-exports.txt)
|
||||
set(symbol_imports ${CMAKE_SOURCE_DIR}/apple-symbol-imports.txt)
|
||||
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(
|
||||
NAME conflict_set_shared_symbols
|
||||
COMMAND ${CMAKE_SOURCE_DIR}/test_symbols.sh
|
||||
$<TARGET_FILE:${PROJECT_NAME}> ${CMAKE_SOURCE_DIR}/symbols.txt)
|
||||
COMMAND
|
||||
${CMAKE_SOURCE_DIR}/test_symbols.sh $<TARGET_FILE:${PROJECT_NAME}>
|
||||
${symbol_exports} ${symbol_imports})
|
||||
add_test(
|
||||
NAME conflict_set_static_symbols
|
||||
COMMAND
|
||||
${CMAKE_SOURCE_DIR}/test_symbols.sh
|
||||
$<TARGET_FILE:${PROJECT_NAME}_static> ${CMAKE_SOURCE_DIR}/symbols.txt)
|
||||
$<TARGET_FILE:${PROJECT_NAME}-static> ${symbol_exports}
|
||||
${symbol_imports})
|
||||
endif()
|
||||
|
||||
# bench
|
||||
|
||||
add_executable(conflict_set_bench Bench.cpp)
|
||||
target_link_libraries(conflict_set_bench PRIVATE ${PROJECT_NAME})
|
||||
set_target_properties(conflict_set_bench PROPERTIES SKIP_BUILD_RPATH ON)
|
||||
|
||||
add_executable(real_data_bench RealDataBench.cpp)
|
||||
target_link_libraries(real_data_bench PRIVATE ${PROJECT_NAME})
|
||||
set_target_properties(real_data_bench PROPERTIES SKIP_BUILD_RPATH ON)
|
||||
endif()
|
||||
|
||||
# packaging
|
||||
|
||||
set(CPACK_PACKAGE_CONTACT andrew@weaselab.dev)
|
||||
@@ -242,6 +342,16 @@ set(CPACK_RESOURCE_FILE_README "${CMAKE_CURRENT_SOURCE_DIR}/README.md")
|
||||
set(CPACK_RPM_PACKAGE_ARCHITECTURE ${CMAKE_SYSTEM_PROCESSOR})
|
||||
set(CPACK_RPM_SPEC_INSTALL_POST "/bin/true") # avoid stripping
|
||||
set(CPACK_RPM_PACKAGE_LICENSE "Apache 2.0")
|
||||
set(CPACK_RPM_FILE_NAME RPM-DEFAULT)
|
||||
|
||||
# deb
|
||||
set(CPACK_DEBIAN_FILE_NAME DEB-DEFAULT)
|
||||
# see *-imports.txt - dependency versions need to be synced with symbol versions
|
||||
if(CMAKE_SYSTEM_PROCESSOR STREQUAL aarch64)
|
||||
set(CPACK_DEBIAN_PACKAGE_DEPENDS "libc6 (>= 2.17)")
|
||||
else()
|
||||
set(CPACK_DEBIAN_PACKAGE_DEPENDS "libc6 (>= 2.14)")
|
||||
endif()
|
||||
|
||||
include(CPack)
|
||||
|
||||
@@ -253,7 +363,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 +372,13 @@ set_target_properties(
|
||||
SOVERSION ${PROJECT_VERSION_MAJOR})
|
||||
|
||||
install(
|
||||
TARGETS ${PROJECT_NAME} ${PROJECT_NAME}_static
|
||||
EXPORT ConflictSetConfig
|
||||
TARGETS ${PROJECT_NAME} ${PROJECT_NAME}-static
|
||||
EXPORT ${PROJECT_NAME}Config
|
||||
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 ${PROJECT_NAME}Config
|
||||
DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/${PROJECT_NAME}/cmake)
|
||||
|
472
ConflictSet.cpp
472
ConflictSet.cpp
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -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
|
||||
}
|
||||
|
@@ -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}) {}
|
||||
: 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;
|
||||
}
|
||||
}
|
||||
|
191
Internal.h
191
Internal.h
@@ -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
|
||||
}
|
||||
@@ -127,6 +154,7 @@ inline void *operator new[](size_t size, std::align_val_t align,
|
||||
|
||||
/// align must be a power of two
|
||||
template <class T> T *align_up(T *t, size_t align) {
|
||||
assert(std::popcount(align) == 1);
|
||||
auto unaligned = uintptr_t(t);
|
||||
auto aligned = (unaligned + align - 1) & ~(align - 1);
|
||||
return reinterpret_cast<T *>(reinterpret_cast<char *>(t) + aligned -
|
||||
@@ -135,6 +163,7 @@ template <class T> T *align_up(T *t, size_t align) {
|
||||
|
||||
/// align must be a power of two
|
||||
constexpr inline int align_up(uint32_t unaligned, uint32_t align) {
|
||||
assert(std::popcount(align) == 1);
|
||||
return (unaligned + align - 1) & ~(align - 1);
|
||||
}
|
||||
|
||||
@@ -150,12 +179,10 @@ struct Arena::ArenaImpl {
|
||||
uint8_t *begin() { return reinterpret_cast<uint8_t *>(this + 1); }
|
||||
};
|
||||
|
||||
static_assert(sizeof(Arena::ArenaImpl) == 16);
|
||||
static_assert(alignof(Arena::ArenaImpl) == 8);
|
||||
|
||||
inline Arena::Arena(int initialSize) : impl(nullptr) {
|
||||
if (initialSize > 0) {
|
||||
auto allocationSize = align_up(initialSize + sizeof(ArenaImpl), 16);
|
||||
auto allocationSize =
|
||||
align_up(initialSize + sizeof(ArenaImpl), alignof(ArenaImpl));
|
||||
impl = (Arena::ArenaImpl *)safe_malloc(allocationSize);
|
||||
impl->prev = nullptr;
|
||||
impl->capacity = allocationSize - sizeof(ArenaImpl);
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -191,7 +218,7 @@ inline void *operator new(size_t size, std::align_val_t align, Arena &arena) {
|
||||
(arena.impl ? std::max<int>(sizeof(Arena::ArenaImpl),
|
||||
arena.impl->capacity * 2)
|
||||
: 0)),
|
||||
16);
|
||||
alignof(Arena::ArenaImpl));
|
||||
auto *impl = (Arena::ArenaImpl *)safe_malloc(allocationSize);
|
||||
impl->prev = arena.impl;
|
||||
impl->capacity = allocationSize - sizeof(Arena::ArenaImpl);
|
||||
@@ -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
13
Jenkinsfile
vendored
@@ -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
108
README.md
@@ -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%
|
||||
```
|
||||
|
@@ -1,13 +1,18 @@
|
||||
#include <ConflictSet.h>
|
||||
|
||||
#include <cerrno>
|
||||
#include <chrono>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <fcntl.h>
|
||||
#include <string_view>
|
||||
#include <sys/mman.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
#include <vector>
|
||||
|
||||
using namespace weaselab;
|
||||
|
||||
double now() {
|
||||
return std::chrono::duration_cast<std::chrono::nanoseconds>(
|
||||
std::chrono::steady_clock::now().time_since_epoch())
|
||||
@@ -28,7 +33,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 +45,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 +116,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 +129,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
155
ScriptTest.cpp
Normal 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);
|
||||
}
|
||||
}
|
40
SkipList.cpp
40
SkipList.cpp
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
8
aarch64-symbol-imports.txt
Normal file
8
aarch64-symbol-imports.txt
Normal file
@@ -0,0 +1,8 @@
|
||||
__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
|
17
apple-symbol-exports.txt
Normal file
17
apple-symbol-exports.txt
Normal file
@@ -0,0 +1,17 @@
|
||||
_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
|
7
apple-symbol-imports.txt
Normal file
7
apple-symbol-imports.txt
Normal file
@@ -0,0 +1,7 @@
|
||||
__tlv_bootstrap
|
||||
_abort
|
||||
_bzero
|
||||
_free
|
||||
_malloc
|
||||
_memcpy
|
||||
_memmove
|
@@ -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;
|
||||
}
|
||||
|
@@ -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.
BIN
corpus/00cc0f31d9e079e5c10967791cbfc214c876da48
Normal file
BIN
corpus/00cc0f31d9e079e5c10967791cbfc214c876da48
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
corpus/02057227d6245d09b874d53295f14ddb87ee54ff
Normal file
BIN
corpus/02057227d6245d09b874d53295f14ddb87ee54ff
Normal file
Binary file not shown.
BIN
corpus/0258a68ab599675ffb6639a7e1e3cea2879c79cf
Normal file
BIN
corpus/0258a68ab599675ffb6639a7e1e3cea2879c79cf
Normal file
Binary file not shown.
BIN
corpus/0325dc4513367ff3f788d540a3d5037cd8422645
Normal file
BIN
corpus/0325dc4513367ff3f788d540a3d5037cd8422645
Normal file
Binary file not shown.
BIN
corpus/0415011bcf7b79337a7fb013d85f9b840b8d2050
Normal file
BIN
corpus/0415011bcf7b79337a7fb013d85f9b840b8d2050
Normal file
Binary file not shown.
BIN
corpus/0503541cc3e72b44e61518ff3d525f226c64ace7
Normal file
BIN
corpus/0503541cc3e72b44e61518ff3d525f226c64ace7
Normal file
Binary file not shown.
@@ -1 +0,0 @@
|
||||
:
|
BIN
corpus/05bef24faf30d29da5b7caa52f80302985c00e8c
Normal file
BIN
corpus/05bef24faf30d29da5b7caa52f80302985c00e8c
Normal file
Binary file not shown.
Binary file not shown.
BIN
corpus/063d680d490143cf5fbcb7414f97affed2f66493
Normal file
BIN
corpus/063d680d490143cf5fbcb7414f97affed2f66493
Normal file
Binary file not shown.
BIN
corpus/064b20fcb90c53503dbb803dc85ff3c5427fe469
Normal file
BIN
corpus/064b20fcb90c53503dbb803dc85ff3c5427fe469
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
corpus/072efe2ef3d857c2a7d417e010933d496386ea2b
Normal file
BIN
corpus/072efe2ef3d857c2a7d417e010933d496386ea2b
Normal file
Binary file not shown.
BIN
corpus/07caf39a29c8f5f703a85dabf1069073f1155f9a
Normal file
BIN
corpus/07caf39a29c8f5f703a85dabf1069073f1155f9a
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
corpus/080c4a75f5549a222909e354ac0d00c63f11701a
Normal file
BIN
corpus/080c4a75f5549a222909e354ac0d00c63f11701a
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
corpus/09577892321d14b660c77955dc05ba4cf9bcc05c
Normal file
BIN
corpus/09577892321d14b660c77955dc05ba4cf9bcc05c
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
corpus/0b2b81913b5ef8e7dc1c0d9486f5dd1df578069c
Normal file
BIN
corpus/0b2b81913b5ef8e7dc1c0d9486f5dd1df578069c
Normal file
Binary file not shown.
Binary file not shown.
BIN
corpus/0cab3671b418e023a9b955a5bcdb339a5d80885d
Normal file
BIN
corpus/0cab3671b418e023a9b955a5bcdb339a5d80885d
Normal file
Binary file not shown.
Binary file not shown.
BIN
corpus/0d668e5a8e3e3fc505d99bb1b0fb2633bc8e616c
Normal file
BIN
corpus/0d668e5a8e3e3fc505d99bb1b0fb2633bc8e616c
Normal file
Binary file not shown.
BIN
corpus/0dd7586cd48024a419c0ab49808b958d6540becc
Normal file
BIN
corpus/0dd7586cd48024a419c0ab49808b958d6540becc
Normal file
Binary file not shown.
Binary file not shown.
BIN
corpus/0e8030a76f0434cddf6b81ca053e3cc208c2ea03
Normal file
BIN
corpus/0e8030a76f0434cddf6b81ca053e3cc208c2ea03
Normal file
Binary file not shown.
BIN
corpus/0ebb5811e76d95913a595fdeb53d8d440c84d027
Normal file
BIN
corpus/0ebb5811e76d95913a595fdeb53d8d440c84d027
Normal file
Binary file not shown.
BIN
corpus/0f5349817b85b03ef24015b1cda7aa69c06e53d9
Normal file
BIN
corpus/0f5349817b85b03ef24015b1cda7aa69c06e53d9
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
corpus/0f8b6a2d4ebe4472b25b81d4e8d35586c75af9f9
Normal file
BIN
corpus/0f8b6a2d4ebe4472b25b81d4e8d35586c75af9f9
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
corpus/13f781904a9065579d637731c787998f305cc860
Normal file
BIN
corpus/13f781904a9065579d637731c787998f305cc860
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
corpus/15d58ff012753e558bfc8e5ff95e456a24654fe1
Normal file
BIN
corpus/15d58ff012753e558bfc8e5ff95e456a24654fe1
Normal file
Binary file not shown.
BIN
corpus/15ff9acbb757117edbc8fcafa13a902f8dfad08d
Normal file
BIN
corpus/15ff9acbb757117edbc8fcafa13a902f8dfad08d
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
corpus/18a37da332a8fc0b894fb32da8aa17e981a20e16
Normal file
BIN
corpus/18a37da332a8fc0b894fb32da8aa17e981a20e16
Normal file
Binary file not shown.
BIN
corpus/18e0a1a32d1dfac596258d76367fe3d072863cab
Normal file
BIN
corpus/18e0a1a32d1dfac596258d76367fe3d072863cab
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
corpus/199a623aac6456b63c51775bfc307f92af877ee9
Normal file
BIN
corpus/199a623aac6456b63c51775bfc307f92af877ee9
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
corpus/1b1ee095f24377da663057a4c055795fac40bfda
Normal file
BIN
corpus/1b1ee095f24377da663057a4c055795fac40bfda
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
corpus/1ca245aada69d5d286c8e8782f03b2b5503a26f5
Normal file
BIN
corpus/1ca245aada69d5d286c8e8782f03b2b5503a26f5
Normal file
Binary file not shown.
BIN
corpus/1e24a4e9325031dadafecedb99f15b87e97a1812
Normal file
BIN
corpus/1e24a4e9325031dadafecedb99f15b87e97a1812
Normal file
Binary file not shown.
Binary file not shown.
BIN
corpus/1e33bf9cb1d0e84d9c9780608b43444495c4d8bf
Normal file
BIN
corpus/1e33bf9cb1d0e84d9c9780608b43444495c4d8bf
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
corpus/1fcf4bf1173d1054a5e85375ff7667f392508faa
Normal file
BIN
corpus/1fcf4bf1173d1054a5e85375ff7667f392508faa
Normal file
Binary file not shown.
Binary file not shown.
BIN
corpus/20a353ee3251c2f27b27794c4118cebb24d16b59
Normal file
BIN
corpus/20a353ee3251c2f27b27794c4118cebb24d16b59
Normal file
Binary file not shown.
BIN
corpus/20df7c1e5e3acf2e9d7b050a7dd591b4ace6819e
Normal file
BIN
corpus/20df7c1e5e3acf2e9d7b050a7dd591b4ace6819e
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
corpus/218f8656038aeb0bfd406d7abf7908501243a181
Normal file
BIN
corpus/218f8656038aeb0bfd406d7abf7908501243a181
Normal file
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user