Compare commits
92 Commits
cb4c2b7e1e
...
v0.0.6
Author | SHA1 | Date | |
---|---|---|---|
13d447c9fe | |||
da7523c5cf | |||
a074bc6f72 | |||
1553a44986 | |||
859ac352e6 | |||
2eb461b8ea | |||
e2e92f4ef5 | |||
f6f25cfcce | |||
c13dc88ff4 | |||
aa5dbb2887 | |||
ea76e04cda | |||
452007e079 | |||
37c75f747b | |||
c96d682483 | |||
6e63fd5126 | |||
f2678de811 | |||
4d7ad075b2 | |||
d2e1863593 | |||
bf91bca16d | |||
08ed17f47b | |||
76a45f16ad | |||
c15d296432 | |||
64a98c529c | |||
ed1388ed21 | |||
309d315956 | |||
eab2e46a56 | |||
85db1a8786 | |||
717f9d6829 | |||
fd93300ce8 | |||
b7e16b31ff | |||
a324d31518 | |||
fdb05e0e33 | |||
7c27d4a972 | |||
738de01cb4 | |||
325cab6a95 | |||
0b2821941a | |||
a40b5dcd74 | |||
193b1926ff | |||
1c900c5a8c | |||
90fdcdd51a | |||
eb3f6823eb | |||
1534e10b75 | |||
3c100ccee8 | |||
5cf45d1c35 | |||
4f97932893 | |||
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 |
2
.clangd
2
.clangd
@@ -1,2 +1,2 @@
|
|||||||
CompileFlags:
|
CompileFlags:
|
||||||
Add: [-DENABLE_MAIN, -UNDEBUG, -DENABLE_FUZZ, -fexceptions]
|
Add: [-DENABLE_MAIN, -UNDEBUG, -DENABLE_FUZZ, -DTHREAD_TEST, -fexceptions]
|
||||||
|
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,2 +1,3 @@
|
|||||||
.cache
|
.cache
|
||||||
|
__pycache__
|
||||||
build
|
build
|
||||||
|
@@ -1,11 +1,11 @@
|
|||||||
repos:
|
repos:
|
||||||
- repo: https://github.com/pre-commit/mirrors-clang-format
|
- repo: https://github.com/pre-commit/mirrors-clang-format
|
||||||
rev: b111689e7b5cba60be3c62d5db2bd1357f4d36ca
|
rev: 6d365699efc33b1b432eab5b4ae331a19e1857de # frozen: v18.1.2
|
||||||
hooks:
|
hooks:
|
||||||
- id: clang-format
|
- id: clang-format
|
||||||
exclude: ".*third_party/.*"
|
exclude: ".*third_party/.*"
|
||||||
- repo: https://github.com/cheshirekow/cmake-format-precommit
|
- repo: https://github.com/cheshirekow/cmake-format-precommit
|
||||||
rev: e2c2116d86a80e72e7146a06e68b7c228afc6319
|
rev: e2c2116d86a80e72e7146a06e68b7c228afc6319 # frozen: v0.6.13
|
||||||
hooks:
|
hooks:
|
||||||
- id: cmake-format
|
- id: cmake-format
|
||||||
- repo: local
|
- repo: local
|
||||||
@@ -13,7 +13,7 @@ repos:
|
|||||||
- id: debug verbose check
|
- id: debug verbose check
|
||||||
name: disallow checking in DEBUG_VERBOSE=1
|
name: disallow checking in DEBUG_VERBOSE=1
|
||||||
description: disallow checking in DEBUG_VERBOSE=1
|
description: disallow checking in DEBUG_VERBOSE=1
|
||||||
entry: '^#define DEBUG_VERBOSE 1$'
|
entry: "^#define DEBUG_VERBOSE 1$"
|
||||||
language: pygrep
|
language: pygrep
|
||||||
types: [c++]
|
types: [c++]
|
||||||
- repo: local
|
- repo: local
|
||||||
@@ -21,6 +21,14 @@ repos:
|
|||||||
- id: debug verbose check
|
- id: debug verbose check
|
||||||
name: disallow checking in SHOW_MEMORY=1
|
name: disallow checking in SHOW_MEMORY=1
|
||||||
description: disallow checking in SHOW_MEMORY=1
|
description: disallow checking in SHOW_MEMORY=1
|
||||||
entry: '^#define SHOW_MEMORY 1$'
|
entry: "^#define SHOW_MEMORY 1$"
|
||||||
language: pygrep
|
language: pygrep
|
||||||
types: [c++]
|
types: [c++]
|
||||||
|
- repo: https://github.com/shellcheck-py/shellcheck-py
|
||||||
|
rev: a23f6b85d0fdd5bb9d564e2579e678033debbdff # frozen: v0.10.0.1
|
||||||
|
hooks:
|
||||||
|
- id: shellcheck
|
||||||
|
- repo: https://github.com/psf/black
|
||||||
|
rev: 552baf822992936134cbd31a38f69c8cfe7c0f05 # frozen: 24.3.0
|
||||||
|
hooks:
|
||||||
|
- id: black
|
||||||
|
@@ -34,7 +34,7 @@ ConflictSet::ReadRange singleton(Arena &arena, std::span<const uint8_t> key) {
|
|||||||
std::span<uint8_t>(new (arena) uint8_t[key.size() + 1], key.size() + 1);
|
std::span<uint8_t>(new (arena) uint8_t[key.size() + 1], key.size() + 1);
|
||||||
memcpy(r.data(), key.data(), key.size());
|
memcpy(r.data(), key.data(), key.size());
|
||||||
r[key.size()] = 0;
|
r[key.size()] = 0;
|
||||||
return {key.data(), int(key.size()), r.data(), int(r.size())};
|
return {{key.data(), int(key.size())}, {r.data(), int(r.size())}, 0};
|
||||||
}
|
}
|
||||||
|
|
||||||
ConflictSet::ReadRange prefixRange(Arena &arena, std::span<const uint8_t> key) {
|
ConflictSet::ReadRange prefixRange(Arena &arena, std::span<const uint8_t> key) {
|
||||||
@@ -52,7 +52,7 @@ ConflictSet::ReadRange prefixRange(Arena &arena, std::span<const uint8_t> key) {
|
|||||||
auto r = std::span<uint8_t>(new (arena) uint8_t[index + 1], index + 1);
|
auto r = std::span<uint8_t>(new (arena) uint8_t[index + 1], index + 1);
|
||||||
memcpy(r.data(), key.data(), index + 1);
|
memcpy(r.data(), key.data(), index + 1);
|
||||||
r[r.size() - 1]++;
|
r[r.size() - 1]++;
|
||||||
return {key.data(), int(key.size()), r.data(), int(r.size())};
|
return {{key.data(), int(key.size())}, {r.data(), int(r.size())}, 0};
|
||||||
}
|
}
|
||||||
|
|
||||||
void benchConflictSet() {
|
void benchConflictSet() {
|
||||||
@@ -258,4 +258,4 @@ void benchConflictSet() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int main(void) { benchConflictSet(); }
|
int main(void) { benchConflictSet(); }
|
||||||
|
333
CMakeLists.txt
333
CMakeLists.txt
@@ -1,15 +1,30 @@
|
|||||||
cmake_minimum_required(VERSION 3.18)
|
cmake_minimum_required(VERSION 3.18)
|
||||||
project(
|
project(
|
||||||
conflict_set
|
conflict-set
|
||||||
VERSION 0.0.1
|
VERSION 0.0.6
|
||||||
DESCRIPTION
|
DESCRIPTION
|
||||||
"A data structure for optimistic concurrency control on ranges of bitwise-lexicographically-ordered keys."
|
"A data structure for optimistic concurrency control on ranges of bitwise-lexicographically-ordered keys."
|
||||||
HOMEPAGE_URL "https://git.weaselab.dev/weaselab/conflict-set"
|
HOMEPAGE_URL "https://git.weaselab.dev/weaselab/conflict-set"
|
||||||
LANGUAGES C CXX)
|
LANGUAGES C CXX)
|
||||||
set(CMAKE_CXX_STANDARD 20)
|
set(CMAKE_CXX_STANDARD 20)
|
||||||
|
|
||||||
|
file(WRITE ${CMAKE_CURRENT_BINARY_DIR}/version.txt ${PROJECT_VERSION})
|
||||||
|
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/version.txt.in
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/paper/version.txt)
|
||||||
|
|
||||||
|
include(CMakePushCheckState)
|
||||||
|
include(CheckCXXCompilerFlag)
|
||||||
|
include(CheckIncludeFileCXX)
|
||||||
|
include(CheckCXXSourceCompiles)
|
||||||
|
|
||||||
set(DEFAULT_BUILD_TYPE "Release")
|
set(DEFAULT_BUILD_TYPE "Release")
|
||||||
|
|
||||||
|
if(EMSCRIPTEN OR CMAKE_SYSTEM_NAME STREQUAL WASI)
|
||||||
|
set(WASM ON)
|
||||||
|
else()
|
||||||
|
set(WASM OFF)
|
||||||
|
endif()
|
||||||
|
|
||||||
if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
|
if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
|
||||||
message(
|
message(
|
||||||
STATUS
|
STATUS
|
||||||
@@ -23,16 +38,33 @@ if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
|
|||||||
endif()
|
endif()
|
||||||
|
|
||||||
add_compile_options(-fdata-sections -ffunction-sections -Wswitch-enum
|
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_CURRENT_SOURCE_DIR}/linker.map)
|
||||||
|
cmake_push_check_state()
|
||||||
|
list(APPEND CMAKE_REQUIRED_LINK_OPTIONS ${version_script_flags})
|
||||||
|
check_cxx_source_compiles("int main(){}" HAS_VERSION_SCRIPT FAIL_REGEX
|
||||||
|
"warning:")
|
||||||
|
cmake_pop_check_state()
|
||||||
|
|
||||||
|
option(USE_SIMD_FALLBACK
|
||||||
|
"Use fallback implementations of functions that use SIMD" OFF)
|
||||||
|
|
||||||
# This is encouraged according to
|
# This is encouraged according to
|
||||||
# https://valgrind.org/docs/manual/manual-core-adv.html#manual-core-adv.clientreq
|
# https://valgrind.org/docs/manual/manual-core-adv.html#manual-core-adv.clientreq
|
||||||
include_directories(SYSTEM ${CMAKE_SOURCE_DIR}/third_party/valgrind)
|
include_directories(SYSTEM ${CMAKE_CURRENT_SOURCE_DIR}/third_party/valgrind)
|
||||||
|
|
||||||
if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
|
add_compile_options($<$<COMPILE_LANGUAGE:CXX>:-Wno-invalid-offsetof>)
|
||||||
add_compile_options(-Wno-maybe-uninitialized
|
|
||||||
$<$<COMPILE_LANGUAGE:CXX>:-Wno-invalid-offsetof>)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
if(APPLE)
|
if(APPLE)
|
||||||
add_link_options(-Wl,-dead_strip)
|
add_link_options(-Wl,-dead_strip)
|
||||||
@@ -40,89 +72,137 @@ else()
|
|||||||
add_link_options(-Wl,--gc-sections)
|
add_link_options(-Wl,--gc-sections)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
include(CheckIncludeFileCXX)
|
if(EMSCRIPTEN)
|
||||||
include(CMakePushCheckState)
|
# https://github.com/emscripten-core/emscripten/issues/15377#issuecomment-1285167486
|
||||||
|
add_link_options(-lnodefs.js -lnoderawfs.js)
|
||||||
cmake_push_check_state()
|
add_link_options(-s ALLOW_MEMORY_GROWTH)
|
||||||
list(APPEND CMAKE_REQUIRED_FLAGS -mavx)
|
|
||||||
check_include_file_cxx("immintrin.h" HAS_AVX)
|
|
||||||
if(HAS_AVX)
|
|
||||||
add_compile_options(-mavx)
|
|
||||||
add_compile_definitions(HAS_AVX)
|
|
||||||
endif()
|
endif()
|
||||||
cmake_pop_check_state()
|
|
||||||
|
|
||||||
check_include_file_cxx("arm_neon.h" HAS_ARM_NEON)
|
if(NOT USE_SIMD_FALLBACK)
|
||||||
if(HAS_ARM_NEON)
|
cmake_push_check_state()
|
||||||
add_compile_definitions(HAS_ARM_NEON)
|
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()
|
endif()
|
||||||
|
|
||||||
set(CMAKE_CXX_IMPLICIT_LINK_LIBRARIES "")
|
set(CMAKE_CXX_IMPLICIT_LINK_LIBRARIES "")
|
||||||
|
|
||||||
add_library(${PROJECT_NAME}_object OBJECT ConflictSet.cpp)
|
add_library(${PROJECT_NAME}-object OBJECT ConflictSet.cpp)
|
||||||
target_compile_options(${PROJECT_NAME}_object PRIVATE -fPIC -fno-exceptions
|
target_compile_options(${PROJECT_NAME}-object PRIVATE -fno-exceptions
|
||||||
-fvisibility=hidden)
|
-fvisibility=hidden)
|
||||||
target_include_directories(${PROJECT_NAME}_object
|
target_include_directories(${PROJECT_NAME}-object
|
||||||
PRIVATE ${CMAKE_SOURCE_DIR}/include)
|
PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include)
|
||||||
|
|
||||||
add_library(${PROJECT_NAME} SHARED $<TARGET_OBJECTS:${PROJECT_NAME}_object>)
|
add_library(${PROJECT_NAME} SHARED $<TARGET_OBJECTS:${PROJECT_NAME}-object>)
|
||||||
set_target_properties(
|
set_target_properties(
|
||||||
${PROJECT_NAME} PROPERTIES LIBRARY_OUTPUT_DIRECTORY
|
${PROJECT_NAME} PROPERTIES LIBRARY_OUTPUT_DIRECTORY
|
||||||
"${CMAKE_BINARY_DIR}/radix_tree")
|
"${CMAKE_CURRENT_BINARY_DIR}/radix_tree")
|
||||||
if(NOT CMAKE_BUILD_TYPE STREQUAL Debug)
|
if(NOT CMAKE_BUILD_TYPE STREQUAL Debug)
|
||||||
set_target_properties(${PROJECT_NAME} PROPERTIES LINKER_LANGUAGE C)
|
set_target_properties(${PROJECT_NAME} PROPERTIES LINKER_LANGUAGE C)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(NOT APPLE)
|
if(HAS_VERSION_SCRIPT)
|
||||||
target_link_options(${PROJECT_NAME} PRIVATE
|
target_link_options(
|
||||||
LINKER:--version-script=${CMAKE_SOURCE_DIR}/linker.map)
|
${PROJECT_NAME} PRIVATE
|
||||||
|
LINKER:--version-script=${CMAKE_CURRENT_SOURCE_DIR}/linker.map)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
add_library(${PROJECT_NAME}_static STATIC
|
add_library(${PROJECT_NAME}-static STATIC
|
||||||
$<TARGET_OBJECTS:${PROJECT_NAME}_object>)
|
$<TARGET_OBJECTS:${PROJECT_NAME}-object>)
|
||||||
if(NOT CMAKE_BUILD_TYPE STREQUAL Debug)
|
if(NOT CMAKE_BUILD_TYPE STREQUAL Debug)
|
||||||
set_target_properties(${PROJECT_NAME}_static PROPERTIES LINKER_LANGUAGE C)
|
set_target_properties(${PROJECT_NAME}-static PROPERTIES LINKER_LANGUAGE C)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(NOT APPLE AND CMAKE_OBJCOPY)
|
if(APPLE)
|
||||||
add_custom_command(
|
add_custom_command(
|
||||||
TARGET conflict_set_static
|
TARGET ${PROJECT_NAME}-static
|
||||||
|
PRE_LINK
|
||||||
|
COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/privatize_symbols_macos.sh
|
||||||
|
$<TARGET_OBJECTS:${PROJECT_NAME}-object>)
|
||||||
|
else()
|
||||||
|
add_custom_command(
|
||||||
|
TARGET ${PROJECT_NAME}-static
|
||||||
POST_BUILD
|
POST_BUILD
|
||||||
COMMAND
|
COMMAND
|
||||||
${CMAKE_OBJCOPY} --keep-global-symbols=${CMAKE_SOURCE_DIR}/symbols.txt
|
${CMAKE_OBJCOPY}
|
||||||
$<TARGET_FILE:${PROJECT_NAME}_static>)
|
--keep-global-symbols=${CMAKE_CURRENT_SOURCE_DIR}/symbol-exports.txt
|
||||||
|
$<TARGET_FILE:${PROJECT_NAME}-static> || echo
|
||||||
|
"Proceeding with all symbols global in static library")
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
set(TEST_FLAGS -Wall -Wextra -Wunreachable-code -Wpedantic -UNDEBUG)
|
|
||||||
|
|
||||||
include(CTest)
|
include(CTest)
|
||||||
|
|
||||||
if(BUILD_TESTING)
|
# disable tests if this is being used through e.g. FetchContent
|
||||||
|
if(CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR AND BUILD_TESTING)
|
||||||
|
|
||||||
# Shared library version of FoundationDB's skip list implementation
|
set(TEST_FLAGS -Wall -Wextra -Wunreachable-code -Wpedantic -UNDEBUG)
|
||||||
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})
|
|
||||||
|
|
||||||
# Shared library version of a std::unordered_map-based conflict set (point
|
# corpus tests, which are tests curated by libfuzzer. The goal is to get broad
|
||||||
# queries only)
|
# coverage with a small number of tests.
|
||||||
add_library(hash_table SHARED HashTable.cpp)
|
|
||||||
target_compile_options(hash_table PRIVATE -fPIC -fno-exceptions
|
|
||||||
-fvisibility=hidden)
|
|
||||||
target_include_directories(hash_table PUBLIC ${CMAKE_SOURCE_DIR}/include)
|
|
||||||
set_target_properties(hash_table PROPERTIES LIBRARY_OUTPUT_DIRECTORY
|
|
||||||
"${CMAKE_BINARY_DIR}/hash_table")
|
|
||||||
set_target_properties(hash_table PROPERTIES OUTPUT_NAME ${PROJECT_NAME})
|
|
||||||
set_target_properties(
|
|
||||||
hash_table PROPERTIES VERSION ${PROJECT_VERSION} SOVERSION
|
|
||||||
${PROJECT_VERSION_MAJOR})
|
|
||||||
|
|
||||||
|
file(GLOB CORPUS_TESTS ${CMAKE_CURRENT_SOURCE_DIR}/corpus/*)
|
||||||
|
|
||||||
|
# extra testing that relies on shared libraries, which aren't available with
|
||||||
|
# wasm
|
||||||
|
if(NOT WASM)
|
||||||
|
# Shared library version of FoundationDB's skip list implementation
|
||||||
|
add_library(skip_list SHARED SkipList.cpp)
|
||||||
|
target_compile_options(skip_list PRIVATE -fno-exceptions
|
||||||
|
-fvisibility=hidden)
|
||||||
|
target_include_directories(skip_list
|
||||||
|
PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include)
|
||||||
|
set_target_properties(
|
||||||
|
skip_list PROPERTIES LIBRARY_OUTPUT_DIRECTORY
|
||||||
|
"${CMAKE_CURRENT_BINARY_DIR}/skip_list")
|
||||||
|
set_target_properties(skip_list PROPERTIES OUTPUT_NAME ${PROJECT_NAME})
|
||||||
|
set_target_properties(
|
||||||
|
skip_list PROPERTIES VERSION ${PROJECT_VERSION} SOVERSION
|
||||||
|
${PROJECT_VERSION_MAJOR})
|
||||||
|
|
||||||
|
# Shared library version of a std::unordered_map-based conflict set (point
|
||||||
|
# queries only)
|
||||||
|
add_library(hash_table SHARED HashTable.cpp)
|
||||||
|
target_compile_options(hash_table PRIVATE -fno-exceptions
|
||||||
|
-fvisibility=hidden)
|
||||||
|
target_include_directories(hash_table
|
||||||
|
PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include)
|
||||||
|
set_target_properties(
|
||||||
|
hash_table PROPERTIES LIBRARY_OUTPUT_DIRECTORY
|
||||||
|
"${CMAKE_CURRENT_BINARY_DIR}/hash_table")
|
||||||
|
set_target_properties(hash_table PROPERTIES OUTPUT_NAME ${PROJECT_NAME})
|
||||||
|
set_target_properties(
|
||||||
|
hash_table PROPERTIES VERSION ${PROJECT_VERSION} SOVERSION
|
||||||
|
${PROJECT_VERSION_MAJOR})
|
||||||
|
|
||||||
|
add_executable(driver_skip_list TestDriver.cpp)
|
||||||
|
target_compile_options(driver_skip_list PRIVATE ${TEST_FLAGS})
|
||||||
|
target_link_libraries(driver_skip_list PRIVATE skip_list)
|
||||||
|
|
||||||
|
foreach(TEST ${CORPUS_TESTS})
|
||||||
|
get_filename_component(hash ${TEST} NAME)
|
||||||
|
add_test(NAME skip_list_${hash} COMMAND driver_skip_list ${TEST})
|
||||||
|
endforeach()
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# ad hoc testing
|
||||||
add_executable(conflict_set_main ConflictSet.cpp)
|
add_executable(conflict_set_main ConflictSet.cpp)
|
||||||
target_include_directories(conflict_set_main
|
target_include_directories(conflict_set_main
|
||||||
PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include)
|
PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include)
|
||||||
@@ -149,10 +229,7 @@ if(BUILD_TESTING)
|
|||||||
endif()
|
endif()
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
# corpus tests
|
# whitebox tests
|
||||||
|
|
||||||
file(GLOB CORPUS_TESTS ${CMAKE_SOURCE_DIR}/corpus/*)
|
|
||||||
|
|
||||||
add_executable(fuzz_driver ConflictSet.cpp FuzzTestDriver.cpp)
|
add_executable(fuzz_driver ConflictSet.cpp FuzzTestDriver.cpp)
|
||||||
target_compile_options(fuzz_driver PRIVATE ${TEST_FLAGS})
|
target_compile_options(fuzz_driver PRIVATE ${TEST_FLAGS})
|
||||||
if(NOT CMAKE_CROSSCOMPILING)
|
if(NOT CMAKE_CROSSCOMPILING)
|
||||||
@@ -167,23 +244,51 @@ if(BUILD_TESTING)
|
|||||||
add_test(NAME conflict_set_fuzz_${hash} COMMAND fuzz_driver ${TEST})
|
add_test(NAME conflict_set_fuzz_${hash} COMMAND fuzz_driver ${TEST})
|
||||||
endforeach()
|
endforeach()
|
||||||
|
|
||||||
|
# tsan tests
|
||||||
|
if(NOT CMAKE_CROSSCOMPILING AND NOT CMAKE_BUILD_TYPE STREQUAL Debug)
|
||||||
|
add_executable(tsan_driver ConflictSet.cpp FuzzTestDriver.cpp)
|
||||||
|
target_compile_options(tsan_driver PRIVATE ${TEST_FLAGS} -fsanitize=thread)
|
||||||
|
target_link_options(tsan_driver PRIVATE -fsanitize=thread)
|
||||||
|
target_compile_definitions(tsan_driver PRIVATE ENABLE_FUZZ THREAD_TEST)
|
||||||
|
target_include_directories(tsan_driver
|
||||||
|
PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include)
|
||||||
|
foreach(TEST ${CORPUS_TESTS})
|
||||||
|
get_filename_component(hash ${TEST} NAME)
|
||||||
|
add_test(NAME conflict_set_tsan_${hash} COMMAND tsan_driver ${TEST})
|
||||||
|
endforeach()
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# blackbox tests
|
||||||
add_executable(driver TestDriver.cpp)
|
add_executable(driver TestDriver.cpp)
|
||||||
target_compile_options(driver PRIVATE ${TEST_FLAGS})
|
target_compile_options(driver PRIVATE ${TEST_FLAGS})
|
||||||
target_link_libraries(driver PRIVATE ${PROJECT_NAME})
|
target_link_libraries(driver PRIVATE ${PROJECT_NAME})
|
||||||
|
foreach(TEST ${CORPUS_TESTS})
|
||||||
add_executable(script_test ScriptTest.cpp)
|
get_filename_component(hash ${TEST} NAME)
|
||||||
target_compile_options(script_test PRIVATE ${TEST_FLAGS})
|
add_test(NAME conflict_set_blackbox_${hash} COMMAND driver ${TEST})
|
||||||
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()
|
endforeach()
|
||||||
|
|
||||||
add_executable(driver_skip_list TestDriver.cpp)
|
# scripted tests. Written manually to fill in anything libfuzzer couldn't
|
||||||
target_compile_options(driver_skip_list PRIVATE ${TEST_FLAGS})
|
# find.
|
||||||
target_link_libraries(driver_skip_list PRIVATE skip_list)
|
if(NOT CMAKE_CROSSCOMPILING)
|
||||||
|
find_package(Python3 REQUIRED COMPONENTS Interpreter)
|
||||||
|
set_property(
|
||||||
|
DIRECTORY
|
||||||
|
APPEND
|
||||||
|
PROPERTY CMAKE_CONFIGURE_DEPENDS
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/test_conflict_set.py)
|
||||||
|
execute_process(
|
||||||
|
COMMAND ${Python3_EXECUTABLE}
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/test_conflict_set.py list
|
||||||
|
OUTPUT_VARIABLE SCRIPT_TESTS)
|
||||||
|
foreach(TEST ${SCRIPT_TESTS})
|
||||||
|
add_test(
|
||||||
|
NAME script_test_${TEST}
|
||||||
|
COMMAND
|
||||||
|
${Python3_EXECUTABLE}
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/test_conflict_set.py test ${TEST}
|
||||||
|
--build-dir ${CMAKE_CURRENT_BINARY_DIR})
|
||||||
|
endforeach()
|
||||||
|
endif()
|
||||||
|
|
||||||
find_program(VALGRIND_EXE valgrind)
|
find_program(VALGRIND_EXE valgrind)
|
||||||
if(VALGRIND_EXE AND NOT CMAKE_CROSSCOMPILING)
|
if(VALGRIND_EXE AND NOT CMAKE_CROSSCOMPILING)
|
||||||
@@ -192,12 +297,6 @@ if(BUILD_TESTING)
|
|||||||
$<TARGET_FILE:driver> ${CORPUS_TESTS})
|
$<TARGET_FILE:driver> ${CORPUS_TESTS})
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
foreach(TEST ${CORPUS_TESTS})
|
|
||||||
get_filename_component(hash ${TEST} NAME)
|
|
||||||
add_test(NAME conflict_set_blackbox_${hash} COMMAND driver ${TEST})
|
|
||||||
add_test(NAME skip_list_${hash} COMMAND driver_skip_list ${TEST})
|
|
||||||
endforeach()
|
|
||||||
|
|
||||||
# api smoke tests
|
# api smoke tests
|
||||||
|
|
||||||
# c90
|
# c90
|
||||||
@@ -218,31 +317,46 @@ if(BUILD_TESTING)
|
|||||||
PROPERTIES CXX_STANDARD_REQUIRED ON)
|
PROPERTIES CXX_STANDARD_REQUIRED ON)
|
||||||
add_test(NAME conflict_set_cxx_api_test COMMAND conflict_set_cxx_api_test)
|
add_test(NAME conflict_set_cxx_api_test COMMAND conflict_set_cxx_api_test)
|
||||||
|
|
||||||
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_CURRENT_SOURCE_DIR}/apple-symbol-exports.txt)
|
||||||
|
set(symbol_imports ${CMAKE_CURRENT_SOURCE_DIR}/apple-symbol-imports.txt)
|
||||||
|
else()
|
||||||
|
set(symbol_exports ${CMAKE_CURRENT_SOURCE_DIR}/symbol-exports.txt)
|
||||||
|
if(CMAKE_SYSTEM_PROCESSOR STREQUAL aarch64)
|
||||||
|
set(symbol_imports
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/aarch64-symbol-imports.txt)
|
||||||
|
else()
|
||||||
|
set(symbol_imports ${CMAKE_CURRENT_SOURCE_DIR}/symbol-imports.txt)
|
||||||
|
endif()
|
||||||
|
endif()
|
||||||
add_test(
|
add_test(
|
||||||
NAME conflict_set_shared_symbols
|
NAME conflict_set_shared_symbols
|
||||||
COMMAND ${CMAKE_SOURCE_DIR}/test_symbols.sh
|
COMMAND
|
||||||
$<TARGET_FILE:${PROJECT_NAME}> ${CMAKE_SOURCE_DIR}/symbols.txt)
|
${CMAKE_CURRENT_SOURCE_DIR}/test_symbols.sh
|
||||||
|
$<TARGET_FILE:${PROJECT_NAME}> ${symbol_exports} ${symbol_imports})
|
||||||
add_test(
|
add_test(
|
||||||
NAME conflict_set_static_symbols
|
NAME conflict_set_static_symbols
|
||||||
COMMAND
|
COMMAND
|
||||||
${CMAKE_SOURCE_DIR}/test_symbols.sh
|
${CMAKE_CURRENT_SOURCE_DIR}/test_symbols.sh
|
||||||
$<TARGET_FILE:${PROJECT_NAME}_static> ${CMAKE_SOURCE_DIR}/symbols.txt)
|
$<TARGET_FILE:${PROJECT_NAME}-static> ${symbol_exports}
|
||||||
|
${symbol_imports})
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
# bench
|
# bench
|
||||||
|
|
||||||
add_executable(conflict_set_bench Bench.cpp)
|
add_executable(conflict_set_bench Bench.cpp)
|
||||||
target_link_libraries(conflict_set_bench PRIVATE ${PROJECT_NAME})
|
target_link_libraries(conflict_set_bench PRIVATE ${PROJECT_NAME})
|
||||||
set_target_properties(conflict_set_bench PROPERTIES SKIP_BUILD_RPATH ON)
|
set_target_properties(conflict_set_bench PROPERTIES SKIP_BUILD_RPATH ON)
|
||||||
|
|
||||||
add_executable(real_data_bench RealDataBench.cpp)
|
add_executable(real_data_bench RealDataBench.cpp)
|
||||||
target_link_libraries(real_data_bench PRIVATE ${PROJECT_NAME})
|
target_link_libraries(real_data_bench PRIVATE ${PROJECT_NAME})
|
||||||
set_target_properties(real_data_bench PROPERTIES SKIP_BUILD_RPATH ON)
|
set_target_properties(real_data_bench PROPERTIES SKIP_BUILD_RPATH ON)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
# packaging
|
# packaging
|
||||||
|
|
||||||
set(CPACK_PACKAGE_CONTACT andrew@weaselab.dev)
|
set(CPACK_PACKAGE_CONTACT andrew@weaselab.dev)
|
||||||
|
set(CMAKE_INSTALL_DEFAULT_COMPONENT_NAME all)
|
||||||
|
|
||||||
set(CPACK_PACKAGE_VENDOR "Weaselab")
|
set(CPACK_PACKAGE_VENDOR "Weaselab")
|
||||||
set(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_CURRENT_SOURCE_DIR}/LICENSE")
|
set(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_CURRENT_SOURCE_DIR}/LICENSE")
|
||||||
@@ -252,6 +366,30 @@ set(CPACK_RESOURCE_FILE_README "${CMAKE_CURRENT_SOURCE_DIR}/README.md")
|
|||||||
set(CPACK_RPM_PACKAGE_ARCHITECTURE ${CMAKE_SYSTEM_PROCESSOR})
|
set(CPACK_RPM_PACKAGE_ARCHITECTURE ${CMAKE_SYSTEM_PROCESSOR})
|
||||||
set(CPACK_RPM_SPEC_INSTALL_POST "/bin/true") # avoid stripping
|
set(CPACK_RPM_SPEC_INSTALL_POST "/bin/true") # avoid stripping
|
||||||
set(CPACK_RPM_PACKAGE_LICENSE "Apache 2.0")
|
set(CPACK_RPM_PACKAGE_LICENSE "Apache 2.0")
|
||||||
|
set(CPACK_RPM_FILE_NAME RPM-DEFAULT)
|
||||||
|
|
||||||
|
# deb
|
||||||
|
set(CPACK_DEBIAN_FILE_NAME DEB-DEFAULT)
|
||||||
|
# see *-imports.txt - dependency versions need to be synced with symbol versions
|
||||||
|
if(CMAKE_SYSTEM_PROCESSOR STREQUAL aarch64)
|
||||||
|
set(CPACK_DEBIAN_PACKAGE_DEPENDS "libc6 (>= 2.17)")
|
||||||
|
else()
|
||||||
|
set(CPACK_DEBIAN_PACKAGE_DEPENDS "libc6 (>= 2.14)")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# macos
|
||||||
|
set(CMAKE_OSX_DEPLOYMENT_TARGET 11.0)
|
||||||
|
if(APPLE)
|
||||||
|
find_program(PANDOC_EXE pandoc)
|
||||||
|
if(PANDOC_EXE)
|
||||||
|
execute_process(COMMAND ${PANDOC_EXE} ${CMAKE_CURRENT_SOURCE_DIR}/README.md
|
||||||
|
-o ${CMAKE_CURRENT_BINARY_DIR}/README.txt)
|
||||||
|
set(CPACK_RESOURCE_FILE_README ${CMAKE_CURRENT_BINARY_DIR}/README.txt)
|
||||||
|
endif()
|
||||||
|
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/LICENSE
|
||||||
|
${CMAKE_CURRENT_BINARY_DIR}/LICENSE.txt COPYONLY)
|
||||||
|
set(CPACK_RESOURCE_FILE_LICENSE ${CMAKE_CURRENT_BINARY_DIR}/LICENSE.txt)
|
||||||
|
endif()
|
||||||
|
|
||||||
include(CPack)
|
include(CPack)
|
||||||
|
|
||||||
@@ -263,7 +401,7 @@ target_include_directories(
|
|||||||
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_NAME}>)
|
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_NAME}>)
|
||||||
|
|
||||||
target_include_directories(
|
target_include_directories(
|
||||||
${PROJECT_NAME}_static
|
${PROJECT_NAME}-static
|
||||||
PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
|
PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
|
||||||
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_NAME}>)
|
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_NAME}>)
|
||||||
|
|
||||||
@@ -272,13 +410,16 @@ set_target_properties(
|
|||||||
SOVERSION ${PROJECT_VERSION_MAJOR})
|
SOVERSION ${PROJECT_VERSION_MAJOR})
|
||||||
|
|
||||||
install(
|
install(
|
||||||
TARGETS ${PROJECT_NAME} ${PROJECT_NAME}_static
|
TARGETS ${PROJECT_NAME} ${PROJECT_NAME}-static
|
||||||
EXPORT ConflictSetConfig
|
EXPORT ${PROJECT_NAME}Config
|
||||||
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
|
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
|
||||||
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
|
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
|
||||||
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
|
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
|
||||||
|
|
||||||
install(DIRECTORY include/
|
install(DIRECTORY include/
|
||||||
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_NAME})
|
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_NAME})
|
||||||
|
|
||||||
install(EXPORT ConflictSetConfig
|
install(EXPORT ${PROJECT_NAME}Config
|
||||||
DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/ConflictSet/cmake)
|
DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/${PROJECT_NAME}/cmake)
|
||||||
|
|
||||||
|
cpack_add_component(all)
|
||||||
|
312
ConflictSet.cpp
312
ConflictSet.cpp
@@ -29,6 +29,7 @@ limitations under the License.
|
|||||||
#include <span>
|
#include <span>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <string_view>
|
#include <string_view>
|
||||||
|
#include <type_traits>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
||||||
#ifdef HAS_AVX
|
#ifdef HAS_AVX
|
||||||
@@ -39,6 +40,8 @@ limitations under the License.
|
|||||||
|
|
||||||
#include <memcheck.h>
|
#include <memcheck.h>
|
||||||
|
|
||||||
|
using namespace weaselab;
|
||||||
|
|
||||||
// Use assert for checking potentially complex properties during tests.
|
// Use assert for checking potentially complex properties during tests.
|
||||||
// Use assume to hint simple properties to the optimizer.
|
// Use assume to hint simple properties to the optimizer.
|
||||||
|
|
||||||
@@ -216,6 +219,7 @@ struct Node {
|
|||||||
/* end section that's copied to the next node */
|
/* end section that's copied to the next node */
|
||||||
|
|
||||||
uint8_t *partialKey();
|
uint8_t *partialKey();
|
||||||
|
size_t size() const;
|
||||||
|
|
||||||
Type getType() const { return type; }
|
Type getType() const { return type; }
|
||||||
int32_t getCapacity() const { return partialKeyCapacity; }
|
int32_t getCapacity() const { return partialKeyCapacity; }
|
||||||
@@ -249,6 +253,8 @@ struct Node0 : Node {
|
|||||||
|
|
||||||
void copyChildrenAndKeyFrom(const Node0 &other);
|
void copyChildrenAndKeyFrom(const Node0 &other);
|
||||||
void copyChildrenAndKeyFrom(const struct Node3 &other);
|
void copyChildrenAndKeyFrom(const struct Node3 &other);
|
||||||
|
|
||||||
|
size_t size() const { return sizeof(Node0) + getCapacity(); }
|
||||||
};
|
};
|
||||||
|
|
||||||
struct Node3 : Node {
|
struct Node3 : Node {
|
||||||
@@ -262,6 +268,8 @@ struct Node3 : Node {
|
|||||||
void copyChildrenAndKeyFrom(const Node0 &other);
|
void copyChildrenAndKeyFrom(const Node0 &other);
|
||||||
void copyChildrenAndKeyFrom(const Node3 &other);
|
void copyChildrenAndKeyFrom(const Node3 &other);
|
||||||
void copyChildrenAndKeyFrom(const struct Node16 &other);
|
void copyChildrenAndKeyFrom(const struct Node16 &other);
|
||||||
|
|
||||||
|
size_t size() const { return sizeof(Node3) + getCapacity(); }
|
||||||
};
|
};
|
||||||
|
|
||||||
struct Node16 : Node {
|
struct Node16 : Node {
|
||||||
@@ -275,6 +283,8 @@ struct Node16 : Node {
|
|||||||
void copyChildrenAndKeyFrom(const Node3 &other);
|
void copyChildrenAndKeyFrom(const Node3 &other);
|
||||||
void copyChildrenAndKeyFrom(const Node16 &other);
|
void copyChildrenAndKeyFrom(const Node16 &other);
|
||||||
void copyChildrenAndKeyFrom(const struct Node48 &other);
|
void copyChildrenAndKeyFrom(const struct Node48 &other);
|
||||||
|
|
||||||
|
size_t size() const { return sizeof(Node16) + getCapacity(); }
|
||||||
};
|
};
|
||||||
|
|
||||||
struct Node48 : Node {
|
struct Node48 : Node {
|
||||||
@@ -290,6 +300,8 @@ struct Node48 : Node {
|
|||||||
void copyChildrenAndKeyFrom(const Node16 &other);
|
void copyChildrenAndKeyFrom(const Node16 &other);
|
||||||
void copyChildrenAndKeyFrom(const Node48 &other);
|
void copyChildrenAndKeyFrom(const Node48 &other);
|
||||||
void copyChildrenAndKeyFrom(const struct Node256 &other);
|
void copyChildrenAndKeyFrom(const struct Node256 &other);
|
||||||
|
|
||||||
|
size_t size() const { return sizeof(Node48) + getCapacity(); }
|
||||||
};
|
};
|
||||||
|
|
||||||
struct Node256 : Node {
|
struct Node256 : Node {
|
||||||
@@ -299,6 +311,8 @@ struct Node256 : Node {
|
|||||||
uint8_t *partialKey() { return (uint8_t *)(this + 1); }
|
uint8_t *partialKey() { return (uint8_t *)(this + 1); }
|
||||||
void copyChildrenAndKeyFrom(const Node48 &other);
|
void copyChildrenAndKeyFrom(const Node48 &other);
|
||||||
void copyChildrenAndKeyFrom(const Node256 &other);
|
void copyChildrenAndKeyFrom(const Node256 &other);
|
||||||
|
|
||||||
|
size_t size() const { return sizeof(Node256) + getCapacity(); }
|
||||||
};
|
};
|
||||||
|
|
||||||
inline void Node0::copyChildrenAndKeyFrom(const Node0 &other) {
|
inline void Node0::copyChildrenAndKeyFrom(const Node0 &other) {
|
||||||
@@ -535,7 +549,7 @@ template <class T> struct BoundedFreeListAllocator {
|
|||||||
} else {
|
} else {
|
||||||
// The intent is to filter out too-small nodes in the freelist
|
// The intent is to filter out too-small nodes in the freelist
|
||||||
removeNode(n);
|
removeNode(n);
|
||||||
safe_free(n);
|
safe_free(n, sizeof(T) + n->partialKeyCapacity);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -547,10 +561,9 @@ template <class T> struct BoundedFreeListAllocator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void release(T *p) {
|
void release(T *p) {
|
||||||
static_assert(std::is_trivially_destructible_v<T>);
|
|
||||||
if (freeListBytes >= kFreeListMaxMemory) {
|
if (freeListBytes >= kFreeListMaxMemory) {
|
||||||
removeNode(p);
|
removeNode(p);
|
||||||
return safe_free(p);
|
return safe_free(p, sizeof(T) + p->partialKeyCapacity);
|
||||||
}
|
}
|
||||||
memcpy((void *)p, &freeList, sizeof(freeList));
|
memcpy((void *)p, &freeList, sizeof(freeList));
|
||||||
freeList = p;
|
freeList = p;
|
||||||
@@ -560,11 +573,11 @@ template <class T> struct BoundedFreeListAllocator {
|
|||||||
|
|
||||||
~BoundedFreeListAllocator() {
|
~BoundedFreeListAllocator() {
|
||||||
for (void *iter = freeList; iter != nullptr;) {
|
for (void *iter = freeList; iter != nullptr;) {
|
||||||
VALGRIND_MAKE_MEM_DEFINED(iter, sizeof(iter));
|
VALGRIND_MAKE_MEM_DEFINED(iter, sizeof(Node));
|
||||||
auto *tmp = iter;
|
auto *tmp = (T *)iter;
|
||||||
memcpy(&iter, iter, sizeof(void *));
|
memcpy(&iter, iter, sizeof(void *));
|
||||||
removeNode(((T *)tmp));
|
removeNode((tmp));
|
||||||
safe_free(tmp);
|
safe_free(tmp, sizeof(T) + tmp->partialKeyCapacity);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -590,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 {
|
struct NodeAllocators {
|
||||||
BoundedFreeListAllocator<Node0> node0;
|
BoundedFreeListAllocator<Node0> node0;
|
||||||
BoundedFreeListAllocator<Node3> node3;
|
BoundedFreeListAllocator<Node3> node3;
|
||||||
@@ -740,7 +770,7 @@ template <class NodeT> int getChildGeqSimd(NodeT *self, int child) {
|
|||||||
|
|
||||||
// cachegrind says the plain loop is fewer instructions and more mis-predicted
|
// cachegrind says the plain loop is fewer instructions and more mis-predicted
|
||||||
// branches. Microbenchmark says plain loop is faster. It's written in this
|
// branches. Microbenchmark says plain loop is faster. It's written in this
|
||||||
// weird "generic" way though in case someday we can use the simd
|
// weird "generic" way though so that someday we can use the simd
|
||||||
// implementation easily if we want.
|
// implementation easily if we want.
|
||||||
if constexpr (std::is_same_v<NodeT, Node3>) {
|
if constexpr (std::is_same_v<NodeT, Node3>) {
|
||||||
Node3 *n = (Node3 *)self;
|
Node3 *n = (Node3 *)self;
|
||||||
@@ -925,6 +955,42 @@ Node *&getOrCreateChild(Node *&self, uint8_t index,
|
|||||||
assert(self->getType() == Type_Node16);
|
assert(self->getType() == Type_Node16);
|
||||||
|
|
||||||
++self->numChildren;
|
++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;
|
int i = 0;
|
||||||
for (; i < int(self->numChildren) - 1; ++i) {
|
for (; i < int(self->numChildren) - 1; ++i) {
|
||||||
if (int(self16->index[i]) > int(index)) {
|
if (int(self16->index[i]) > int(index)) {
|
||||||
@@ -935,6 +1001,7 @@ Node *&getOrCreateChild(Node *&self, uint8_t index,
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
self16->index[i] = index;
|
self16->index[i] = index;
|
||||||
auto &result = self16->children[i].child;
|
auto &result = self16->children[i].child;
|
||||||
result = nullptr;
|
result = nullptr;
|
||||||
@@ -950,8 +1017,8 @@ Node *&getOrCreateChild(Node *&self, uint8_t index,
|
|||||||
self = newSelf;
|
self = newSelf;
|
||||||
goto insert256;
|
goto insert256;
|
||||||
}
|
}
|
||||||
insert48:
|
|
||||||
|
|
||||||
|
insert48:
|
||||||
auto *self48 = static_cast<Node48 *>(self);
|
auto *self48 = static_cast<Node48 *>(self);
|
||||||
self48->bitSet.set(index);
|
self48->bitSet.set(index);
|
||||||
++self->numChildren;
|
++self->numChildren;
|
||||||
@@ -963,6 +1030,7 @@ Node *&getOrCreateChild(Node *&self, uint8_t index,
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
case Type_Node256: {
|
case Type_Node256: {
|
||||||
|
|
||||||
insert256:
|
insert256:
|
||||||
auto *self256 = static_cast<Node256 *>(self);
|
auto *self256 = static_cast<Node256 *>(self);
|
||||||
++self->numChildren;
|
++self->numChildren;
|
||||||
@@ -998,21 +1066,21 @@ Node *nextLogical(Node *node) {
|
|||||||
|
|
||||||
// Invalidates `self`, replacing it with a node of at least capacity.
|
// Invalidates `self`, replacing it with a node of at least capacity.
|
||||||
// Does not return nodes to freelists when kUseFreeList is false.
|
// Does not return nodes to freelists when kUseFreeList is false.
|
||||||
template <bool kUseFreeList>
|
|
||||||
void freeAndMakeCapacityAtLeast(Node *&self, int capacity,
|
void freeAndMakeCapacityAtLeast(Node *&self, int capacity,
|
||||||
NodeAllocators *allocators,
|
NodeAllocators *allocators,
|
||||||
ConflictSet::Impl *impl) {
|
ConflictSet::Impl *impl,
|
||||||
|
const bool kUseFreeList) {
|
||||||
switch (self->getType()) {
|
switch (self->getType()) {
|
||||||
case Type_Node0: {
|
case Type_Node0: {
|
||||||
auto *self0 = (Node0 *)self;
|
auto *self0 = (Node0 *)self;
|
||||||
auto *newSelf = allocators->node0.allocate(capacity);
|
auto *newSelf = allocators->node0.allocate(capacity);
|
||||||
newSelf->copyChildrenAndKeyFrom(*self0);
|
newSelf->copyChildrenAndKeyFrom(*self0);
|
||||||
getInTree(self, impl) = newSelf;
|
getInTree(self, impl) = newSelf;
|
||||||
if constexpr (kUseFreeList) {
|
if (kUseFreeList) {
|
||||||
allocators->node0.release(self0);
|
allocators->node0.release(self0);
|
||||||
} else {
|
} else {
|
||||||
removeNode(self0);
|
removeNode(self0);
|
||||||
safe_free(self0);
|
safe_free(self0, self0->size());
|
||||||
}
|
}
|
||||||
self = newSelf;
|
self = newSelf;
|
||||||
} break;
|
} break;
|
||||||
@@ -1021,11 +1089,11 @@ void freeAndMakeCapacityAtLeast(Node *&self, int capacity,
|
|||||||
auto *newSelf = allocators->node3.allocate(capacity);
|
auto *newSelf = allocators->node3.allocate(capacity);
|
||||||
newSelf->copyChildrenAndKeyFrom(*self3);
|
newSelf->copyChildrenAndKeyFrom(*self3);
|
||||||
getInTree(self, impl) = newSelf;
|
getInTree(self, impl) = newSelf;
|
||||||
if constexpr (kUseFreeList) {
|
if (kUseFreeList) {
|
||||||
allocators->node3.release(self3);
|
allocators->node3.release(self3);
|
||||||
} else {
|
} else {
|
||||||
removeNode(self3);
|
removeNode(self3);
|
||||||
safe_free(self3);
|
safe_free(self3, self3->size());
|
||||||
}
|
}
|
||||||
self = newSelf;
|
self = newSelf;
|
||||||
} break;
|
} break;
|
||||||
@@ -1034,11 +1102,11 @@ void freeAndMakeCapacityAtLeast(Node *&self, int capacity,
|
|||||||
auto *newSelf = allocators->node16.allocate(capacity);
|
auto *newSelf = allocators->node16.allocate(capacity);
|
||||||
newSelf->copyChildrenAndKeyFrom(*self16);
|
newSelf->copyChildrenAndKeyFrom(*self16);
|
||||||
getInTree(self, impl) = newSelf;
|
getInTree(self, impl) = newSelf;
|
||||||
if constexpr (kUseFreeList) {
|
if (kUseFreeList) {
|
||||||
allocators->node16.release(self16);
|
allocators->node16.release(self16);
|
||||||
} else {
|
} else {
|
||||||
removeNode(self16);
|
removeNode(self16);
|
||||||
safe_free(self16);
|
safe_free(self16, self16->size());
|
||||||
}
|
}
|
||||||
self = newSelf;
|
self = newSelf;
|
||||||
} break;
|
} break;
|
||||||
@@ -1047,11 +1115,11 @@ void freeAndMakeCapacityAtLeast(Node *&self, int capacity,
|
|||||||
auto *newSelf = allocators->node48.allocate(capacity);
|
auto *newSelf = allocators->node48.allocate(capacity);
|
||||||
newSelf->copyChildrenAndKeyFrom(*self48);
|
newSelf->copyChildrenAndKeyFrom(*self48);
|
||||||
getInTree(self, impl) = newSelf;
|
getInTree(self, impl) = newSelf;
|
||||||
if constexpr (kUseFreeList) {
|
if (kUseFreeList) {
|
||||||
allocators->node48.release(self48);
|
allocators->node48.release(self48);
|
||||||
} else {
|
} else {
|
||||||
removeNode(self48);
|
removeNode(self48);
|
||||||
safe_free(self48);
|
safe_free(self48, self48->size());
|
||||||
}
|
}
|
||||||
self = newSelf;
|
self = newSelf;
|
||||||
} break;
|
} break;
|
||||||
@@ -1060,11 +1128,11 @@ void freeAndMakeCapacityAtLeast(Node *&self, int capacity,
|
|||||||
auto *newSelf = allocators->node256.allocate(capacity);
|
auto *newSelf = allocators->node256.allocate(capacity);
|
||||||
newSelf->copyChildrenAndKeyFrom(*self256);
|
newSelf->copyChildrenAndKeyFrom(*self256);
|
||||||
getInTree(self, impl) = newSelf;
|
getInTree(self, impl) = newSelf;
|
||||||
if constexpr (kUseFreeList) {
|
if (kUseFreeList) {
|
||||||
allocators->node256.release(self256);
|
allocators->node256.release(self256);
|
||||||
} else {
|
} else {
|
||||||
removeNode(self256);
|
removeNode(self256);
|
||||||
safe_free(self256);
|
safe_free(self256, self256->size());
|
||||||
}
|
}
|
||||||
self = newSelf;
|
self = newSelf;
|
||||||
} break;
|
} break;
|
||||||
@@ -1078,16 +1146,20 @@ void freeAndMakeCapacityAtLeast(Node *&self, int capacity,
|
|||||||
// capacity.
|
// capacity.
|
||||||
void maybeDecreaseCapacity(Node *&self, NodeAllocators *allocators,
|
void maybeDecreaseCapacity(Node *&self, NodeAllocators *allocators,
|
||||||
ConflictSet::Impl *impl) {
|
ConflictSet::Impl *impl) {
|
||||||
|
|
||||||
const int maxCapacity =
|
const int maxCapacity =
|
||||||
(self->numChildren + int(self->entryPresent)) * (self->partialKeyLen + 1);
|
(self->numChildren + int(self->entryPresent)) * (self->partialKeyLen + 1);
|
||||||
|
#if DEBUG_VERBOSE && !defined(NDEBUG)
|
||||||
|
fprintf(stderr, "maybeDecreaseCapacity: current: %d, max: %d, key: %s\n",
|
||||||
|
self->getCapacity(), maxCapacity,
|
||||||
|
getSearchPathPrintable(self).c_str());
|
||||||
|
#endif
|
||||||
if (self->getCapacity() <= maxCapacity) {
|
if (self->getCapacity() <= maxCapacity) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
freeAndMakeCapacityAtLeast</*kUseFreeList*/ false>(self, maxCapacity,
|
freeAndMakeCapacityAtLeast(self, maxCapacity, allocators, impl, false);
|
||||||
allocators, impl);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
template <Type kType>
|
|
||||||
void maybeDownsize(Node *self, NodeAllocators *allocators,
|
void maybeDownsize(Node *self, NodeAllocators *allocators,
|
||||||
ConflictSet::Impl *impl, Node *&dontInvalidate) {
|
ConflictSet::Impl *impl, Node *&dontInvalidate) {
|
||||||
|
|
||||||
@@ -1095,9 +1167,7 @@ void maybeDownsize(Node *self, NodeAllocators *allocators,
|
|||||||
fprintf(stderr, "maybeDownsize: %s\n", getSearchPathPrintable(self).c_str());
|
fprintf(stderr, "maybeDownsize: %s\n", getSearchPathPrintable(self).c_str());
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
assert(self->getType() == kType);
|
switch (self->getType()) {
|
||||||
static_assert(kType != Type_Node0);
|
|
||||||
switch (kType) {
|
|
||||||
case Type_Node0: // GCOVR_EXCL_LINE
|
case Type_Node0: // GCOVR_EXCL_LINE
|
||||||
__builtin_unreachable(); // GCOVR_EXCL_LINE
|
__builtin_unreachable(); // GCOVR_EXCL_LINE
|
||||||
case Type_Node3: {
|
case Type_Node3: {
|
||||||
@@ -1113,8 +1183,7 @@ void maybeDownsize(Node *self, NodeAllocators *allocators,
|
|||||||
|
|
||||||
if (minCapacity > child->getCapacity()) {
|
if (minCapacity > child->getCapacity()) {
|
||||||
const bool update = child == dontInvalidate;
|
const bool update = child == dontInvalidate;
|
||||||
freeAndMakeCapacityAtLeast</*kUseFreeList*/ true>(child, minCapacity,
|
freeAndMakeCapacityAtLeast(child, minCapacity, allocators, impl, true);
|
||||||
allocators, impl);
|
|
||||||
if (update) {
|
if (update) {
|
||||||
dontInvalidate = child;
|
dontInvalidate = child;
|
||||||
}
|
}
|
||||||
@@ -1201,24 +1270,7 @@ Node *erase(Node *self, NodeAllocators *allocators, ConflictSet::Impl *impl,
|
|||||||
|
|
||||||
if (self->numChildren != 0) {
|
if (self->numChildren != 0) {
|
||||||
const bool update = result == dontInvalidate;
|
const bool update = result == dontInvalidate;
|
||||||
switch (self->getType()) {
|
maybeDownsize(self, allocators, impl, result);
|
||||||
case Type_Node0: // GCOVR_EXCL_LINE
|
|
||||||
__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
|
|
||||||
}
|
|
||||||
if (update) {
|
if (update) {
|
||||||
dontInvalidate = result;
|
dontInvalidate = result;
|
||||||
}
|
}
|
||||||
@@ -1244,11 +1296,6 @@ Node *erase(Node *self, NodeAllocators *allocators, ConflictSet::Impl *impl,
|
|||||||
|
|
||||||
--parent->numChildren;
|
--parent->numChildren;
|
||||||
assert(parent->numChildren > 0 || parent->entryPresent);
|
assert(parent->numChildren > 0 || parent->entryPresent);
|
||||||
const bool update = result == dontInvalidate;
|
|
||||||
maybeDownsize<Type_Node3>(parent, allocators, impl, result);
|
|
||||||
if (update) {
|
|
||||||
dontInvalidate = result;
|
|
||||||
}
|
|
||||||
} break;
|
} break;
|
||||||
case Type_Node16: {
|
case Type_Node16: {
|
||||||
auto *parent16 = static_cast<Node16 *>(parent);
|
auto *parent16 = static_cast<Node16 *>(parent);
|
||||||
@@ -1265,13 +1312,6 @@ Node *erase(Node *self, NodeAllocators *allocators, ConflictSet::Impl *impl,
|
|||||||
|
|
||||||
// By kMinChildrenNode16
|
// By kMinChildrenNode16
|
||||||
assert(parent->numChildren > 0);
|
assert(parent->numChildren > 0);
|
||||||
|
|
||||||
const bool update = result == dontInvalidate;
|
|
||||||
maybeDownsize<Type_Node16>(parent, allocators, impl, result);
|
|
||||||
if (update) {
|
|
||||||
dontInvalidate = result;
|
|
||||||
}
|
|
||||||
|
|
||||||
} break;
|
} break;
|
||||||
case Type_Node48: {
|
case Type_Node48: {
|
||||||
auto *parent48 = static_cast<Node48 *>(parent);
|
auto *parent48 = static_cast<Node48 *>(parent);
|
||||||
@@ -1292,12 +1332,6 @@ Node *erase(Node *self, NodeAllocators *allocators, ConflictSet::Impl *impl,
|
|||||||
|
|
||||||
// By kMinChildrenNode48
|
// By kMinChildrenNode48
|
||||||
assert(parent->numChildren > 0);
|
assert(parent->numChildren > 0);
|
||||||
|
|
||||||
const bool update = result == dontInvalidate;
|
|
||||||
maybeDownsize<Type_Node48>(parent, allocators, impl, result);
|
|
||||||
if (update) {
|
|
||||||
dontInvalidate = result;
|
|
||||||
}
|
|
||||||
} break;
|
} break;
|
||||||
case Type_Node256: {
|
case Type_Node256: {
|
||||||
auto *parent256 = static_cast<Node256 *>(parent);
|
auto *parent256 = static_cast<Node256 *>(parent);
|
||||||
@@ -1309,16 +1343,17 @@ Node *erase(Node *self, NodeAllocators *allocators, ConflictSet::Impl *impl,
|
|||||||
// By kMinChildrenNode256
|
// By kMinChildrenNode256
|
||||||
assert(parent->numChildren > 0);
|
assert(parent->numChildren > 0);
|
||||||
|
|
||||||
const bool update = result == dontInvalidate;
|
|
||||||
maybeDownsize<Type_Node256>(parent, allocators, impl, result);
|
|
||||||
if (update) {
|
|
||||||
dontInvalidate = result;
|
|
||||||
}
|
|
||||||
} break;
|
} break;
|
||||||
default: // GCOVR_EXCL_LINE
|
default: // GCOVR_EXCL_LINE
|
||||||
__builtin_unreachable(); // GCOVR_EXCL_LINE
|
__builtin_unreachable(); // GCOVR_EXCL_LINE
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const bool update = result == dontInvalidate;
|
||||||
|
maybeDownsize(parent, allocators, impl, result);
|
||||||
|
if (update) {
|
||||||
|
dontInvalidate = result;
|
||||||
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1698,6 +1733,9 @@ bool checkRangeStartsWith(Node *n, std::span<const uint8_t> key, int begin,
|
|||||||
goto downLeftSpine;
|
goto downLeftSpine;
|
||||||
} else {
|
} else {
|
||||||
n = nextSibling(n);
|
n = nextSibling(n);
|
||||||
|
if (n == nullptr) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
goto downLeftSpine;
|
goto downLeftSpine;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1715,6 +1753,9 @@ bool checkRangeStartsWith(Node *n, std::span<const uint8_t> key, int begin,
|
|||||||
goto downLeftSpine;
|
goto downLeftSpine;
|
||||||
} else {
|
} else {
|
||||||
n = nextSibling(n);
|
n = nextSibling(n);
|
||||||
|
if (n == nullptr) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
goto downLeftSpine;
|
goto downLeftSpine;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1732,9 +1773,6 @@ bool checkRangeStartsWith(Node *n, std::span<const uint8_t> key, int begin,
|
|||||||
__builtin_unreachable(); // GCOVR_EXCL_LINE
|
__builtin_unreachable(); // GCOVR_EXCL_LINE
|
||||||
|
|
||||||
downLeftSpine:
|
downLeftSpine:
|
||||||
if (n == nullptr) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
for (;;) {
|
for (;;) {
|
||||||
if (n->entryPresent) {
|
if (n->entryPresent) {
|
||||||
return n->entry.rangeVersion <= readVersion;
|
return n->entry.rangeVersion <= readVersion;
|
||||||
@@ -1802,6 +1840,10 @@ struct CheckRangeLeftSide {
|
|||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
n = nextSibling(n);
|
n = nextSibling(n);
|
||||||
|
if (n == nullptr) {
|
||||||
|
ok = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
return downLeftSpine();
|
return downLeftSpine();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1829,6 +1871,10 @@ struct CheckRangeLeftSide {
|
|||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
n = nextSibling(n);
|
n = nextSibling(n);
|
||||||
|
if (n == nullptr) {
|
||||||
|
ok = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
return downLeftSpine();
|
return downLeftSpine();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1865,10 +1911,6 @@ struct CheckRangeLeftSide {
|
|||||||
|
|
||||||
bool downLeftSpine() {
|
bool downLeftSpine() {
|
||||||
phase = DownLeftSpine;
|
phase = DownLeftSpine;
|
||||||
if (n == nullptr) {
|
|
||||||
ok = true;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -2210,7 +2252,7 @@ void destroyTree(Node *root) {
|
|||||||
assert(c != nullptr);
|
assert(c != nullptr);
|
||||||
toFree.push_back(c);
|
toFree.push_back(c);
|
||||||
}
|
}
|
||||||
safe_free(n);
|
safe_free(n, n->size());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2353,7 +2395,11 @@ Iterator firstGeq(Node *n, const std::span<const uint8_t> key) {
|
|||||||
} else {
|
} else {
|
||||||
n = nextSibling(n);
|
n = nextSibling(n);
|
||||||
if (n == nullptr) {
|
if (n == nullptr) {
|
||||||
return {nullptr, 1};
|
// This line is genuinely unreachable from any entry point of the
|
||||||
|
// final library, since we can't remove a key without introducing a
|
||||||
|
// key after it, and the only production caller of firstGeq is for
|
||||||
|
// resuming the setOldestVersion scan.
|
||||||
|
return {nullptr, 1}; // GCOVR_EXCL_LINE
|
||||||
}
|
}
|
||||||
goto downLeftSpine;
|
goto downLeftSpine;
|
||||||
}
|
}
|
||||||
@@ -2467,13 +2513,17 @@ struct __attribute__((visibility("hidden"))) ConflictSet::Impl {
|
|||||||
}
|
}
|
||||||
if (n == nullptr) {
|
if (n == nullptr) {
|
||||||
removalKey = {};
|
removalKey = {};
|
||||||
return;
|
} else {
|
||||||
|
removalKeyArena = Arena();
|
||||||
|
removalKey = getSearchPath(removalKeyArena, n);
|
||||||
}
|
}
|
||||||
removalKeyArena = Arena();
|
|
||||||
removalKey = getSearchPath(removalKeyArena, n);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
explicit Impl(int64_t oldestVersion) : oldestVersion(oldestVersion) {
|
explicit Impl(int64_t oldestVersion) : oldestVersion(oldestVersion) {
|
||||||
|
#if DEBUG_VERBOSE
|
||||||
|
fprintf(stderr, "radix_tree: create\n");
|
||||||
|
#endif
|
||||||
|
|
||||||
// Insert ""
|
// Insert ""
|
||||||
root = allocators.node0.allocate(0);
|
root = allocators.node0.allocate(0);
|
||||||
root->numChildren = 0;
|
root->numChildren = 0;
|
||||||
@@ -2488,7 +2538,12 @@ struct __attribute__((visibility("hidden"))) ConflictSet::Impl {
|
|||||||
root->entry.pointVersion = oldestVersion;
|
root->entry.pointVersion = oldestVersion;
|
||||||
root->entry.rangeVersion = oldestVersion;
|
root->entry.rangeVersion = oldestVersion;
|
||||||
}
|
}
|
||||||
~Impl() { destroyTree(root); }
|
~Impl() {
|
||||||
|
#if DEBUG_VERBOSE
|
||||||
|
fprintf(stderr, "radix_tree: destroy\n");
|
||||||
|
#endif
|
||||||
|
destroyTree(root);
|
||||||
|
}
|
||||||
|
|
||||||
NodeAllocators allocators;
|
NodeAllocators allocators;
|
||||||
|
|
||||||
@@ -2499,6 +2554,7 @@ struct __attribute__((visibility("hidden"))) ConflictSet::Impl {
|
|||||||
Node *root;
|
Node *root;
|
||||||
int64_t rootMaxVersion;
|
int64_t rootMaxVersion;
|
||||||
int64_t oldestVersion;
|
int64_t oldestVersion;
|
||||||
|
int64_t totalBytes = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Precondition - an entry for index must exist in the node
|
// Precondition - an entry for index must exist in the node
|
||||||
@@ -2541,31 +2597,77 @@ Node *&getInTree(Node *n, ConflictSet::Impl *impl) {
|
|||||||
: getChildExists(n->parent, n->parentsIndex);
|
: getChildExists(n->parent, n->parentsIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Internal entry points. Public entry points should just delegate to these
|
||||||
|
|
||||||
|
void internal_check(ConflictSet::Impl *impl,
|
||||||
|
const ConflictSet::ReadRange *reads,
|
||||||
|
ConflictSet::Result *results, int count) {
|
||||||
|
impl->check(reads, results, count);
|
||||||
|
}
|
||||||
|
void internal_addWrites(ConflictSet::Impl *impl,
|
||||||
|
const ConflictSet::WriteRange *writes, int count,
|
||||||
|
int64_t writeVersion) {
|
||||||
|
mallocBytesDelta = 0;
|
||||||
|
impl->addWrites(writes, count, writeVersion);
|
||||||
|
impl->totalBytes += mallocBytesDelta;
|
||||||
|
#if SHOW_MEMORY
|
||||||
|
if (impl->totalBytes != mallocBytes) {
|
||||||
|
abort();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void internal_setOldestVersion(ConflictSet::Impl *impl, int64_t oldestVersion) {
|
||||||
|
mallocBytesDelta = 0;
|
||||||
|
impl->setOldestVersion(oldestVersion);
|
||||||
|
impl->totalBytes += mallocBytesDelta;
|
||||||
|
#if SHOW_MEMORY
|
||||||
|
if (impl->totalBytes != mallocBytes) {
|
||||||
|
abort();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
ConflictSet::Impl *internal_create(int64_t oldestVersion) {
|
||||||
|
mallocBytesDelta = 0;
|
||||||
|
auto *result = new (safe_malloc(sizeof(ConflictSet::Impl)))
|
||||||
|
ConflictSet::Impl{oldestVersion};
|
||||||
|
result->totalBytes += mallocBytesDelta;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
void internal_destroy(ConflictSet::Impl *impl) {
|
||||||
|
impl->~Impl();
|
||||||
|
safe_free(impl, sizeof(ConflictSet::Impl));
|
||||||
|
}
|
||||||
|
|
||||||
|
int64_t internal_getBytes(ConflictSet::Impl *impl) { return impl->totalBytes; }
|
||||||
|
|
||||||
// ==================== END IMPLEMENTATION ====================
|
// ==================== END IMPLEMENTATION ====================
|
||||||
|
|
||||||
// GCOVR_EXCL_START
|
// GCOVR_EXCL_START
|
||||||
|
|
||||||
void ConflictSet::check(const ReadRange *reads, Result *results,
|
void ConflictSet::check(const ReadRange *reads, Result *results,
|
||||||
int count) const {
|
int count) const {
|
||||||
return impl->check(reads, results, count);
|
internal_check(impl, reads, results, count);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ConflictSet::addWrites(const WriteRange *writes, int count,
|
void ConflictSet::addWrites(const WriteRange *writes, int count,
|
||||||
int64_t writeVersion) {
|
int64_t writeVersion) {
|
||||||
return impl->addWrites(writes, count, writeVersion);
|
internal_addWrites(impl, writes, count, writeVersion);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ConflictSet::setOldestVersion(int64_t oldestVersion) {
|
void ConflictSet::setOldestVersion(int64_t oldestVersion) {
|
||||||
return impl->setOldestVersion(oldestVersion);
|
internal_setOldestVersion(impl, oldestVersion);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int64_t ConflictSet::getBytes() const { return internal_getBytes(impl); }
|
||||||
|
|
||||||
ConflictSet::ConflictSet(int64_t oldestVersion)
|
ConflictSet::ConflictSet(int64_t oldestVersion)
|
||||||
: impl(new (safe_malloc(sizeof(Impl))) Impl{oldestVersion}) {}
|
: impl(internal_create(oldestVersion)) {}
|
||||||
|
|
||||||
ConflictSet::~ConflictSet() {
|
ConflictSet::~ConflictSet() {
|
||||||
if (impl) {
|
if (impl) {
|
||||||
impl->~Impl();
|
internal_destroy(impl);
|
||||||
safe_free(impl);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2586,29 +2688,36 @@ extern "C" {
|
|||||||
__attribute__((__visibility__("default"))) void
|
__attribute__((__visibility__("default"))) void
|
||||||
ConflictSet_check(void *cs, const ConflictSet_ReadRange *reads,
|
ConflictSet_check(void *cs, const ConflictSet_ReadRange *reads,
|
||||||
ConflictSet_Result *results, int count) {
|
ConflictSet_Result *results, int count) {
|
||||||
((ConflictSet::Impl *)cs)->check(reads, results, count);
|
internal_check((ConflictSet::Impl *)cs, reads, results, count);
|
||||||
}
|
}
|
||||||
__attribute__((__visibility__("default"))) void
|
__attribute__((__visibility__("default"))) void
|
||||||
ConflictSet_addWrites(void *cs, const ConflictSet_WriteRange *writes, int count,
|
ConflictSet_addWrites(void *cs, const ConflictSet_WriteRange *writes, int count,
|
||||||
int64_t writeVersion) {
|
int64_t writeVersion) {
|
||||||
((ConflictSet::Impl *)cs)->addWrites(writes, count, writeVersion);
|
internal_addWrites((ConflictSet::Impl *)cs, writes, count, writeVersion);
|
||||||
}
|
}
|
||||||
__attribute__((__visibility__("default"))) void
|
__attribute__((__visibility__("default"))) void
|
||||||
ConflictSet_setOldestVersion(void *cs, int64_t oldestVersion) {
|
ConflictSet_setOldestVersion(void *cs, int64_t oldestVersion) {
|
||||||
((ConflictSet::Impl *)cs)->setOldestVersion(oldestVersion);
|
internal_setOldestVersion((ConflictSet::Impl *)cs, oldestVersion);
|
||||||
}
|
}
|
||||||
__attribute__((__visibility__("default"))) void *
|
__attribute__((__visibility__("default"))) void *
|
||||||
ConflictSet_create(int64_t oldestVersion) {
|
ConflictSet_create(int64_t oldestVersion) {
|
||||||
return new (safe_malloc(sizeof(ConflictSet::Impl)))
|
return internal_create(oldestVersion);
|
||||||
ConflictSet::Impl{oldestVersion};
|
|
||||||
}
|
}
|
||||||
__attribute__((__visibility__("default"))) void ConflictSet_destroy(void *cs) {
|
__attribute__((__visibility__("default"))) void ConflictSet_destroy(void *cs) {
|
||||||
using Impl = ConflictSet::Impl;
|
internal_destroy((ConflictSet::Impl *)cs);
|
||||||
((Impl *)cs)->~Impl();
|
}
|
||||||
safe_free(cs);
|
__attribute__((__visibility__("default"))) int64_t
|
||||||
|
ConflictSet_getBytes(void *cs) {
|
||||||
|
return internal_getBytes((ConflictSet::Impl *)cs);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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 {
|
namespace {
|
||||||
|
|
||||||
std::string getSearchPathPrintable(Node *n) {
|
std::string getSearchPathPrintable(Node *n) {
|
||||||
@@ -2821,8 +2930,8 @@ Iterator firstGeq(Node *n, std::string_view key) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool checkCorrectness(Node *node, int64_t oldestVersion,
|
[[maybe_unused]] bool checkCorrectness(Node *node, int64_t oldestVersion,
|
||||||
ConflictSet::Impl *impl) {
|
ConflictSet::Impl *impl) {
|
||||||
bool success = true;
|
bool success = true;
|
||||||
|
|
||||||
checkParentPointers(node, success);
|
checkParentPointers(node, success);
|
||||||
@@ -2835,10 +2944,6 @@ bool checkCorrectness(Node *node, int64_t oldestVersion,
|
|||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
namespace std {
|
|
||||||
void __throw_length_error(const char *) { __builtin_unreachable(); }
|
|
||||||
} // namespace std
|
|
||||||
|
|
||||||
#if SHOW_MEMORY
|
#if SHOW_MEMORY
|
||||||
|
|
||||||
int64_t nodeBytes = 0;
|
int64_t nodeBytes = 0;
|
||||||
@@ -2919,6 +3024,7 @@ void removeKey(Node *n) {
|
|||||||
|
|
||||||
struct __attribute__((visibility("default"))) PeakPrinter {
|
struct __attribute__((visibility("default"))) PeakPrinter {
|
||||||
~PeakPrinter() {
|
~PeakPrinter() {
|
||||||
|
printf("--- radix_tree ---\n");
|
||||||
printf("malloc bytes: %g\n", double(mallocBytes));
|
printf("malloc bytes: %g\n", double(mallocBytes));
|
||||||
printf("Peak malloc bytes: %g\n", double(peakMallocBytes));
|
printf("Peak malloc bytes: %g\n", double(peakMallocBytes));
|
||||||
printf("Node bytes: %g\n", double(nodeBytes));
|
printf("Node bytes: %g\n", double(nodeBytes));
|
||||||
|
@@ -2,15 +2,25 @@
|
|||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
|
#include <thread>
|
||||||
|
|
||||||
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size);
|
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size);
|
||||||
|
|
||||||
int main(int argc, char **argv) {
|
int main(int argc, char **argv) {
|
||||||
for (int i = 1; i < argc; ++i) {
|
auto doTest = [&]() {
|
||||||
std::ifstream t(argv[i], std::ios::binary);
|
for (int i = 1; i < argc; ++i) {
|
||||||
std::stringstream buffer;
|
std::ifstream t(argv[i], std::ios::binary);
|
||||||
buffer << t.rdbuf();
|
std::stringstream buffer;
|
||||||
auto str = buffer.str();
|
buffer << t.rdbuf();
|
||||||
LLVMFuzzerTestOneInput((const uint8_t *)str.data(), str.size());
|
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);
|
return impl->setOldestVersion(oldestVersion);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int64_t ConflictSet::getBytes() const { return -1; }
|
||||||
|
|
||||||
ConflictSet::ConflictSet(int64_t oldestVersion)
|
ConflictSet::ConflictSet(int64_t oldestVersion)
|
||||||
: impl(new (safe_malloc(sizeof(Impl))) Impl{oldestVersion}) {}
|
: impl(new(safe_malloc(sizeof(Impl))) Impl{oldestVersion}) {}
|
||||||
|
|
||||||
ConflictSet::~ConflictSet() {
|
ConflictSet::~ConflictSet() {
|
||||||
if (impl) {
|
if (impl) {
|
||||||
impl->~Impl();
|
impl->~Impl();
|
||||||
safe_free(impl);
|
safe_free(impl, sizeof(Impl));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -142,6 +144,11 @@ ConflictSet_create(int64_t oldestVersion) {
|
|||||||
__attribute__((__visibility__("default"))) void ConflictSet_destroy(void *cs) {
|
__attribute__((__visibility__("default"))) void ConflictSet_destroy(void *cs) {
|
||||||
using Impl = ConflictSet::Impl;
|
using Impl = ConflictSet::Impl;
|
||||||
((Impl *)cs)->~Impl();
|
((Impl *)cs)->~Impl();
|
||||||
safe_free(cs);
|
safe_free(cs, sizeof(Impl));
|
||||||
|
}
|
||||||
|
__attribute__((__visibility__("default"))) int64_t
|
||||||
|
ConflictSet_getBytes(void *cs) {
|
||||||
|
using Impl = ConflictSet::Impl;
|
||||||
|
return -1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
214
Internal.h
214
Internal.h
@@ -2,6 +2,8 @@
|
|||||||
|
|
||||||
#include "ConflictSet.h"
|
#include "ConflictSet.h"
|
||||||
|
|
||||||
|
using namespace weaselab;
|
||||||
|
|
||||||
#include <bit>
|
#include <bit>
|
||||||
#include <cassert>
|
#include <cassert>
|
||||||
#include <compare>
|
#include <compare>
|
||||||
@@ -10,10 +12,12 @@
|
|||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <inttypes.h>
|
#include <inttypes.h>
|
||||||
|
#include <latch>
|
||||||
#include <map>
|
#include <map>
|
||||||
#include <set>
|
#include <set>
|
||||||
#include <span>
|
#include <span>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <thread>
|
||||||
#include <unordered_set>
|
#include <unordered_set>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
@@ -54,45 +58,55 @@ operator<=>(const std::span<const uint8_t> &lhs,
|
|||||||
#if SHOW_MEMORY
|
#if SHOW_MEMORY
|
||||||
inline int64_t mallocBytes = 0;
|
inline int64_t mallocBytes = 0;
|
||||||
inline int64_t peakMallocBytes = 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
|
#endif
|
||||||
|
|
||||||
// malloc that aborts on OOM and thus always returns a non-null pointer. Must be
|
// malloc that aborts on OOM and thus always returns a non-null pointer. Must be
|
||||||
// paired with `safe_free`.
|
// paired with `safe_free`.
|
||||||
__attribute__((always_inline)) inline void *safe_malloc(size_t s) {
|
__attribute__((always_inline)) inline void *safe_malloc(size_t s) {
|
||||||
|
mallocBytesDelta += s;
|
||||||
#if SHOW_MEMORY
|
#if SHOW_MEMORY
|
||||||
mallocBytes += s;
|
mallocBytes += s;
|
||||||
if (mallocBytes > peakMallocBytes) {
|
if (mallocBytes > peakMallocBytes) {
|
||||||
peakMallocBytes = mallocBytes;
|
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
|
#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`.
|
// Must be paired with `safe_malloc`.
|
||||||
//
|
//
|
||||||
// There's nothing safer about this than free. Only called safe_free for
|
// There's nothing safer about this than free. Only called safe_free for
|
||||||
// symmetry with safe_malloc.
|
// 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
|
#if SHOW_MEMORY
|
||||||
size_t s;
|
|
||||||
memcpy(&s, (char *)p - kIntMallocHeaderSize, sizeof(s));
|
|
||||||
mallocBytes -= s;
|
mallocBytes -= s;
|
||||||
free((char *)p - kIntMallocHeaderSize);
|
|
||||||
#else
|
|
||||||
free(p);
|
|
||||||
#endif
|
#endif
|
||||||
|
#ifndef NDEBUG
|
||||||
|
(char *&)p -= kMallocHeaderSize;
|
||||||
|
size_t expected;
|
||||||
|
memcpy(&expected, p, sizeof(expected));
|
||||||
|
assert(s == expected);
|
||||||
|
#endif
|
||||||
|
free(p);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ==================== BEGIN ARENA IMPL ====================
|
// ==================== BEGIN ARENA IMPL ====================
|
||||||
@@ -138,6 +152,7 @@ inline void *operator new[](size_t size, std::align_val_t align,
|
|||||||
|
|
||||||
/// align must be a power of two
|
/// align must be a power of two
|
||||||
template <class T> T *align_up(T *t, size_t align) {
|
template <class T> T *align_up(T *t, size_t align) {
|
||||||
|
assert(std::popcount(align) == 1);
|
||||||
auto unaligned = uintptr_t(t);
|
auto unaligned = uintptr_t(t);
|
||||||
auto aligned = (unaligned + align - 1) & ~(align - 1);
|
auto aligned = (unaligned + align - 1) & ~(align - 1);
|
||||||
return reinterpret_cast<T *>(reinterpret_cast<char *>(t) + aligned -
|
return reinterpret_cast<T *>(reinterpret_cast<char *>(t) + aligned -
|
||||||
@@ -146,6 +161,7 @@ template <class T> T *align_up(T *t, size_t align) {
|
|||||||
|
|
||||||
/// align must be a power of two
|
/// align must be a power of two
|
||||||
constexpr inline int align_up(uint32_t unaligned, uint32_t align) {
|
constexpr inline int align_up(uint32_t unaligned, uint32_t align) {
|
||||||
|
assert(std::popcount(align) == 1);
|
||||||
return (unaligned + align - 1) & ~(align - 1);
|
return (unaligned + align - 1) & ~(align - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -161,12 +177,10 @@ struct Arena::ArenaImpl {
|
|||||||
uint8_t *begin() { return reinterpret_cast<uint8_t *>(this + 1); }
|
uint8_t *begin() { return reinterpret_cast<uint8_t *>(this + 1); }
|
||||||
};
|
};
|
||||||
|
|
||||||
static_assert(sizeof(Arena::ArenaImpl) == 16);
|
|
||||||
static_assert(alignof(Arena::ArenaImpl) == 8);
|
|
||||||
|
|
||||||
inline Arena::Arena(int initialSize) : impl(nullptr) {
|
inline Arena::Arena(int initialSize) : impl(nullptr) {
|
||||||
if (initialSize > 0) {
|
if (initialSize > 0) {
|
||||||
auto allocationSize = align_up(initialSize + sizeof(ArenaImpl), 16);
|
auto allocationSize =
|
||||||
|
align_up(initialSize + sizeof(ArenaImpl), alignof(ArenaImpl));
|
||||||
impl = (Arena::ArenaImpl *)safe_malloc(allocationSize);
|
impl = (Arena::ArenaImpl *)safe_malloc(allocationSize);
|
||||||
impl->prev = nullptr;
|
impl->prev = nullptr;
|
||||||
impl->capacity = allocationSize - sizeof(ArenaImpl);
|
impl->capacity = allocationSize - sizeof(ArenaImpl);
|
||||||
@@ -177,7 +191,7 @@ inline Arena::Arena(int initialSize) : impl(nullptr) {
|
|||||||
inline void onDestroy(Arena::ArenaImpl *impl) {
|
inline void onDestroy(Arena::ArenaImpl *impl) {
|
||||||
while (impl) {
|
while (impl) {
|
||||||
auto *prev = impl->prev;
|
auto *prev = impl->prev;
|
||||||
safe_free(impl);
|
safe_free(impl, sizeof(Arena::ArenaImpl) + impl->capacity);
|
||||||
impl = prev;
|
impl = prev;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -202,7 +216,7 @@ inline void *operator new(size_t size, std::align_val_t align, Arena &arena) {
|
|||||||
(arena.impl ? std::max<int>(sizeof(Arena::ArenaImpl),
|
(arena.impl ? std::max<int>(sizeof(Arena::ArenaImpl),
|
||||||
arena.impl->capacity * 2)
|
arena.impl->capacity * 2)
|
||||||
: 0)),
|
: 0)),
|
||||||
16);
|
alignof(Arena::ArenaImpl));
|
||||||
auto *impl = (Arena::ArenaImpl *)safe_malloc(allocationSize);
|
auto *impl = (Arena::ArenaImpl *)safe_malloc(allocationSize);
|
||||||
impl->prev = arena.impl;
|
impl->prev = arena.impl;
|
||||||
impl->capacity = allocationSize - sizeof(Arena::ArenaImpl);
|
impl->capacity = allocationSize - sizeof(Arena::ArenaImpl);
|
||||||
@@ -241,10 +255,65 @@ template <class T> struct ArenaAlloc {
|
|||||||
void deallocate(T *, size_t) noexcept {}
|
void deallocate(T *, size_t) noexcept {}
|
||||||
};
|
};
|
||||||
|
|
||||||
template <class T> using Vector = std::vector<T, ArenaAlloc<T>>;
|
template <class T> struct Vector {
|
||||||
template <class T> auto vector(Arena &arena) {
|
static_assert(std::is_trivially_destructible_v<T>);
|
||||||
return Vector<T>(ArenaAlloc<T>(&arena));
|
static_assert(std::is_trivially_copyable_v<T>);
|
||||||
}
|
|
||||||
|
explicit Vector(Arena *arena)
|
||||||
|
: arena(arena), t(nullptr), size_(0), capacity(0) {}
|
||||||
|
|
||||||
|
void append(std::span<const T> slice) {
|
||||||
|
if (size_ + int(slice.size()) > capacity) {
|
||||||
|
grow(std::max<int>(size_ + slice.size(), capacity * 2));
|
||||||
|
}
|
||||||
|
if (slice.size() > 0) {
|
||||||
|
memcpy(const_cast<std::remove_const_t<T> *>(t) + size_, slice.data(),
|
||||||
|
slice.size() * sizeof(T));
|
||||||
|
}
|
||||||
|
size_ += slice.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
void push_back(const T &t) { append(std::span<const T>(&t, 1)); }
|
||||||
|
|
||||||
|
T *begin() { return t; }
|
||||||
|
T *end() { return t + size_; }
|
||||||
|
T *data() { return t; }
|
||||||
|
T &back() {
|
||||||
|
assert(size_ > 0);
|
||||||
|
return t[size_ - 1];
|
||||||
|
}
|
||||||
|
T &operator[](int i) {
|
||||||
|
assert(i >= 0 && i < size_);
|
||||||
|
return t[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
void pop_back() {
|
||||||
|
assert(size_ > 0);
|
||||||
|
--size_;
|
||||||
|
}
|
||||||
|
|
||||||
|
int size() const { return size_; }
|
||||||
|
|
||||||
|
operator std::span<const T>() const { return std::span(t, size_); }
|
||||||
|
|
||||||
|
private:
|
||||||
|
void grow(int newCapacity) {
|
||||||
|
capacity = newCapacity;
|
||||||
|
auto old = std::span<const T>(*this);
|
||||||
|
t = (T *)new (std::align_val_t(alignof(T)), *arena)
|
||||||
|
uint8_t[capacity * sizeof(T)];
|
||||||
|
size_ = 0;
|
||||||
|
append(old);
|
||||||
|
}
|
||||||
|
|
||||||
|
Arena *arena;
|
||||||
|
T *t;
|
||||||
|
int size_;
|
||||||
|
int capacity;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <class T> auto vector(Arena &arena) { return Vector<T>(&arena); }
|
||||||
|
|
||||||
template <class T, class C> using Set = std::set<T, C, ArenaAlloc<T>>;
|
template <class T, class C> using Set = std::set<T, C, ArenaAlloc<T>>;
|
||||||
template <class T, class C = std::less<T>> auto set(Arena &arena) {
|
template <class T, class C = std::less<T>> auto set(Arena &arena) {
|
||||||
return Set<T, C>(ArenaAlloc<T>(&arena));
|
return Set<T, C>(ArenaAlloc<T>(&arena));
|
||||||
@@ -509,15 +578,18 @@ template <class ConflictSetImpl> struct TestDriver {
|
|||||||
explicit TestDriver(const uint8_t *data, size_t size)
|
explicit TestDriver(const uint8_t *data, size_t size)
|
||||||
: arbitrary({data, size}) {}
|
: arbitrary({data, size}) {}
|
||||||
|
|
||||||
int64_t writeVersion = 0;
|
int64_t writeVersion = 100;
|
||||||
int64_t oldestVersion = 0;
|
int64_t oldestVersion = 0;
|
||||||
ConflictSetImpl cs{oldestVersion};
|
ConflictSetImpl cs{oldestVersion};
|
||||||
ReferenceImpl refImpl{oldestVersion};
|
ReferenceImpl refImpl{oldestVersion};
|
||||||
|
|
||||||
constexpr static auto kMaxKeyLen = 8;
|
constexpr static auto kMaxKeySuffixLen = 8;
|
||||||
|
|
||||||
bool ok = true;
|
bool ok = true;
|
||||||
|
|
||||||
|
const int prefixLen = arbitrary.bounded(512);
|
||||||
|
const int prefixByte = arbitrary.randT<uint8_t>();
|
||||||
|
|
||||||
// Call until it returns true, for "done". Check internal invariants etc
|
// Call until it returns true, for "done". Check internal invariants etc
|
||||||
// between calls to next.
|
// between calls to next.
|
||||||
bool next() {
|
bool next() {
|
||||||
@@ -528,7 +600,7 @@ template <class ConflictSetImpl> struct TestDriver {
|
|||||||
{
|
{
|
||||||
int numPointWrites = arbitrary.bounded(100);
|
int numPointWrites = arbitrary.bounded(100);
|
||||||
int numRangeWrites = arbitrary.bounded(100);
|
int numRangeWrites = arbitrary.bounded(100);
|
||||||
int64_t v = ++writeVersion;
|
int64_t v = (writeVersion += arbitrary.bounded(10));
|
||||||
auto *writes =
|
auto *writes =
|
||||||
new (arena) ConflictSet::WriteRange[numPointWrites + numRangeWrites];
|
new (arena) ConflictSet::WriteRange[numPointWrites + numRangeWrites];
|
||||||
auto keys = set<std::string_view>(arena);
|
auto keys = set<std::string_view>(arena);
|
||||||
@@ -536,9 +608,10 @@ template <class ConflictSetImpl> struct TestDriver {
|
|||||||
if (!arbitrary.hasEntropy()) {
|
if (!arbitrary.hasEntropy()) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
int keyLen = arbitrary.bounded(kMaxKeyLen);
|
int keyLen = prefixLen + arbitrary.bounded(kMaxKeySuffixLen);
|
||||||
auto *begin = new (arena) uint8_t[keyLen];
|
auto *begin = new (arena) uint8_t[keyLen];
|
||||||
arbitrary.randomBytes(begin, keyLen);
|
memset(begin, prefixByte, prefixLen);
|
||||||
|
arbitrary.randomBytes(begin + prefixLen, keyLen - prefixLen);
|
||||||
keys.insert(std::string_view((const char *)begin, keyLen));
|
keys.insert(std::string_view((const char *)begin, keyLen));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -587,8 +660,8 @@ template <class ConflictSetImpl> struct TestDriver {
|
|||||||
|
|
||||||
refImpl.addWrites(writes, numPointWrites + numRangeWrites, v);
|
refImpl.addWrites(writes, numPointWrites + numRangeWrites, v);
|
||||||
|
|
||||||
oldestVersion = std::max<int64_t>(writeVersion - arbitrary.bounded(10),
|
oldestVersion =
|
||||||
oldestVersion);
|
std::min(writeVersion - 10, oldestVersion + arbitrary.bounded(10));
|
||||||
cs.setOldestVersion(oldestVersion);
|
cs.setOldestVersion(oldestVersion);
|
||||||
refImpl.setOldestVersion(oldestVersion);
|
refImpl.setOldestVersion(oldestVersion);
|
||||||
}
|
}
|
||||||
@@ -603,9 +676,10 @@ template <class ConflictSetImpl> struct TestDriver {
|
|||||||
if (!arbitrary.hasEntropy()) {
|
if (!arbitrary.hasEntropy()) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
int keyLen = arbitrary.bounded(kMaxKeyLen);
|
int keyLen = prefixLen + arbitrary.bounded(kMaxKeySuffixLen);
|
||||||
auto *begin = new (arena) uint8_t[keyLen];
|
auto *begin = new (arena) uint8_t[keyLen];
|
||||||
arbitrary.randomBytes(begin, keyLen);
|
memset(begin, prefixByte, prefixLen);
|
||||||
|
arbitrary.randomBytes(begin + prefixLen, keyLen - prefixLen);
|
||||||
keys.insert(std::string_view((const char *)begin, keyLen));
|
keys.insert(std::string_view((const char *)begin, keyLen));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -652,32 +726,60 @@ template <class ConflictSetImpl> struct TestDriver {
|
|||||||
auto *results2 =
|
auto *results2 =
|
||||||
new (arena) ConflictSet::Result[numPointReads + numRangeReads];
|
new (arena) ConflictSet::Result[numPointReads + numRangeReads];
|
||||||
|
|
||||||
|
#ifdef THREAD_TEST
|
||||||
|
auto *results3 =
|
||||||
|
new (arena) ConflictSet::Result[numPointReads + numRangeReads];
|
||||||
|
std::latch ready{1};
|
||||||
|
std::thread thread2{[&]() {
|
||||||
|
ready.count_down();
|
||||||
|
cs.check(reads, results3, numPointReads + numRangeReads);
|
||||||
|
}};
|
||||||
|
ready.wait();
|
||||||
|
#endif
|
||||||
|
|
||||||
CALLGRIND_START_INSTRUMENTATION;
|
CALLGRIND_START_INSTRUMENTATION;
|
||||||
cs.check(reads, results1, numPointReads + numRangeReads);
|
cs.check(reads, results1, numPointReads + numRangeReads);
|
||||||
CALLGRIND_STOP_INSTRUMENTATION;
|
CALLGRIND_STOP_INSTRUMENTATION;
|
||||||
|
|
||||||
refImpl.check(reads, results2, numPointReads + numRangeReads);
|
refImpl.check(reads, results2, numPointReads + numRangeReads);
|
||||||
for (int i = 0; i < numPointReads + numRangeReads; ++i) {
|
|
||||||
if (results1[i] != results2[i]) {
|
auto compareResults = [reads](ConflictSet::Result *results1,
|
||||||
if (reads[i].end.len == 0) {
|
ConflictSet::Result *results2, int count) {
|
||||||
fprintf(stderr,
|
for (int i = 0; i < count; ++i) {
|
||||||
"Expected %s, got %s for read of {%s} at version %" PRId64
|
if (results1[i] != results2[i]) {
|
||||||
"\n",
|
if (reads[i].end.len == 0) {
|
||||||
resultToStr(results2[i]), resultToStr(results1[i]),
|
fprintf(stderr,
|
||||||
printable(reads[i].begin).c_str(), reads[i].readVersion);
|
"Expected %s, got %s for read of {%s} at version %" PRId64
|
||||||
} else {
|
"\n",
|
||||||
fprintf(
|
resultToStr(results2[i]), resultToStr(results1[i]),
|
||||||
stderr,
|
printable(reads[i].begin).c_str(), reads[i].readVersion);
|
||||||
"Expected %s, got %s for read of [%s, %s) at version %" PRId64
|
} else {
|
||||||
"\n",
|
fprintf(
|
||||||
resultToStr(results2[i]), resultToStr(results1[i]),
|
stderr,
|
||||||
printable(reads[i].begin).c_str(),
|
"Expected %s, got %s for read of [%s, %s) at version %" PRId64
|
||||||
printable(reads[i].end).c_str(), reads[i].readVersion);
|
"\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;
|
return false;
|
||||||
}
|
}
|
||||||
|
14
Jenkinsfile
vendored
14
Jenkinsfile
vendored
@@ -48,6 +48,17 @@ pipeline {
|
|||||||
recordIssues(tools: [clang()])
|
recordIssues(tools: [clang()])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
stage('SIMD fallback') {
|
||||||
|
agent {
|
||||||
|
dockerfile {
|
||||||
|
args '-v /home/jenkins/ccache:/ccache'
|
||||||
|
reuseNode true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
steps {
|
||||||
|
CleanBuildAndTest("-DUSE_SIMD_FALLBACK=ON")
|
||||||
|
}
|
||||||
|
}
|
||||||
stage('Release [gcc]') {
|
stage('Release [gcc]') {
|
||||||
agent {
|
agent {
|
||||||
dockerfile {
|
dockerfile {
|
||||||
@@ -100,6 +111,9 @@ pipeline {
|
|||||||
gcovr -f ConflictSet.cpp --cobertura > build/coverage.xml
|
gcovr -f ConflictSet.cpp --cobertura > build/coverage.xml
|
||||||
'''
|
'''
|
||||||
cobertura autoUpdateHealth: false, autoUpdateStability: false, coberturaReportFile: 'build/coverage.xml', conditionalCoverageTargets: '70, 0, 0', failUnhealthy: false, failUnstable: false, lineCoverageTargets: '80, 0, 0', maxNumberOfBuilds: 0, methodCoverageTargets: '80, 0, 0', onlyStable: false, sourceEncoding: 'ASCII', zoomCoverageChart: false
|
cobertura autoUpdateHealth: false, autoUpdateStability: false, coberturaReportFile: 'build/coverage.xml', conditionalCoverageTargets: '70, 0, 0', failUnhealthy: false, failUnstable: false, lineCoverageTargets: '80, 0, 0', maxNumberOfBuilds: 0, methodCoverageTargets: '80, 0, 0', onlyStable: false, sourceEncoding: 'ASCII', zoomCoverageChart: false
|
||||||
|
sh '''
|
||||||
|
gcovr -f ConflictSet.cpp --fail-under-line 100 > /dev/null
|
||||||
|
'''
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
108
README.md
108
README.md
@@ -9,76 +9,76 @@ Hardware for all benchmarks is a mac m1 2020.
|
|||||||
## Skip list
|
## Skip list
|
||||||
|
|
||||||
```
|
```
|
||||||
New conflict set: 1.927 sec
|
New conflict set: 1.957 sec
|
||||||
0.649 Mtransactions/sec
|
0.639 Mtransactions/sec
|
||||||
2.595 Mkeys/sec
|
2.555 Mkeys/sec
|
||||||
Detect only: 1.838 sec
|
Detect only: 1.845 sec
|
||||||
0.680 Mtransactions/sec
|
0.678 Mtransactions/sec
|
||||||
2.721 Mkeys/sec
|
2.710 Mkeys/sec
|
||||||
Skiplist only: 1.256 sec
|
Skiplist only: 1.263 sec
|
||||||
0.995 Mtransactions/sec
|
0.990 Mtransactions/sec
|
||||||
3.981 Mkeys/sec
|
3.960 Mkeys/sec
|
||||||
Performance counters:
|
Performance counters:
|
||||||
Build: 0.0381
|
Build: 0.0546
|
||||||
Add: 0.0499
|
Add: 0.0563
|
||||||
Detect: 1.84
|
Detect: 1.84
|
||||||
D.Sort: 0.411
|
D.Sort: 0.412
|
||||||
D.Combine: 0.0141
|
D.Combine: 0.0141
|
||||||
D.CheckRead: 0.667
|
D.CheckRead: 0.671
|
||||||
D.CheckIntraBatch: 0.00673
|
D.CheckIntraBatch: 0.0068
|
||||||
D.MergeWrite: 0.589
|
D.MergeWrite: 0.592
|
||||||
D.RemoveBefore: 0.146
|
D.RemoveBefore: 0.146
|
||||||
```
|
```
|
||||||
|
|
||||||
## Radix tree (this implementation)
|
## Radix tree (this implementation)
|
||||||
|
|
||||||
```
|
```
|
||||||
New conflict set: 1.318 sec
|
New conflict set: 1.366 sec
|
||||||
0.949 Mtransactions/sec
|
0.915 Mtransactions/sec
|
||||||
3.795 Mkeys/sec
|
3.660 Mkeys/sec
|
||||||
Detect only: 1.202 sec
|
Detect only: 1.248 sec
|
||||||
1.040 Mtransactions/sec
|
1.002 Mtransactions/sec
|
||||||
4.160 Mkeys/sec
|
4.007 Mkeys/sec
|
||||||
Skiplist only: 0.542 sec
|
Skiplist only: 0.573 sec
|
||||||
2.307 Mtransactions/sec
|
2.182 Mtransactions/sec
|
||||||
9.227 Mkeys/sec
|
8.730 Mkeys/sec
|
||||||
Performance counters:
|
Performance counters:
|
||||||
Build: 0.0566
|
Build: 0.0594
|
||||||
Add: 0.058
|
Add: 0.0572
|
||||||
Detect: 1.2
|
Detect: 1.25
|
||||||
D.Sort: 0.411
|
D.Sort: 0.418
|
||||||
D.Combine: 0.0136
|
D.Combine: 0.0149
|
||||||
D.CheckRead: 0.22
|
D.CheckRead: 0.232
|
||||||
D.CheckIntraBatch: 0.00659
|
D.CheckIntraBatch: 0.0067
|
||||||
D.MergeWrite: 0.322
|
D.MergeWrite: 0.341
|
||||||
D.RemoveBefore: 0.226
|
D.RemoveBefore: 0.232
|
||||||
```
|
```
|
||||||
|
|
||||||
# Our benchmark
|
# Our benchmark
|
||||||
|
|
||||||
## Skip list
|
## Skip list
|
||||||
|
|
||||||
| ns/op | op/s | err% | total | benchmark
|
| ns/op | op/s | err% | total | benchmark |
|
||||||
|--------------------:|--------------------:|--------:|----------:|:----------
|
| -----: | -----------: | ---: | ----: | :---------------------------------- |
|
||||||
| 257.12 | 3,889,241.18 | 0.2% | 0.01 | `point reads`
|
| 246.99 | 4,048,700.59 | 0.2% | 0.01 | `point reads` |
|
||||||
| 276.38 | 3,618,145.21 | 0.3% | 0.01 | `prefix reads`
|
| 260.16 | 3,843,784.65 | 0.1% | 0.01 | `prefix reads` |
|
||||||
| 494.19 | 2,023,531.84 | 0.2% | 0.01 | `range reads`
|
| 493.35 | 2,026,953.19 | 0.1% | 0.01 | `range reads` |
|
||||||
| 451.22 | 2,216,229.54 | 1.3% | 0.01 | `point writes`
|
| 462.05 | 2,164,289.23 | 0.6% | 0.01 | `point writes` |
|
||||||
| 435.80 | 2,294,622.46 | 0.3% | 0.01 | `prefix writes`
|
| 448.19 | 2,231,205.25 | 0.9% | 0.01 | `prefix writes` |
|
||||||
| 246.67 | 4,053,999.27 | 4.2% | 0.02 | `range writes`
|
| 255.83 | 3,908,845.72 | 1.5% | 0.02 | `range writes` |
|
||||||
| 555.46 | 1,800,304.91 | 0.9% | 0.01 | `monotonic increasing point writes`
|
| 582.63 | 1,716,349.02 | 1.3% | 0.01 | `monotonic increasing point writes` |
|
||||||
|
|
||||||
## Radix tree (this implementation)
|
## Radix tree (this implementation)
|
||||||
|
|
||||||
| ns/op | op/s | err% | total | benchmark
|
| ns/op | op/s | err% | total | benchmark |
|
||||||
|--------------------:|--------------------:|--------:|----------:|:----------
|
| -----: | ------------: | ---: | ----: | :---------------------------------- |
|
||||||
| 19.40 | 51,554,711.61 | 0.2% | 0.01 | `point reads`
|
| 19.42 | 51,483,206.67 | 0.3% | 0.01 | `point reads` |
|
||||||
| 57.10 | 17,514,573.13 | 0.4% | 0.01 | `prefix reads`
|
| 58.43 | 17,115,612.57 | 0.1% | 0.01 | `prefix reads` |
|
||||||
| 215.65 | 4,637,096.77 | 0.4% | 0.01 | `range reads`
|
| 216.09 | 4,627,766.60 | 0.2% | 0.01 | `range reads` |
|
||||||
| 27.52 | 36,340,784.38 | 0.2% | 0.01 | `point writes`
|
| 28.35 | 35,267,567.72 | 0.2% | 0.01 | `point writes` |
|
||||||
| 42.16 | 23,720,515.40 | 0.7% | 0.01 | `prefix writes`
|
| 43.43 | 23,026,226.17 | 0.2% | 0.01 | `prefix writes` |
|
||||||
| 48.33 | 20,691,082.14 | 2.7% | 0.01 | `range writes`
|
| 50.00 | 20,000,000.00 | 0.0% | 0.01 | `range writes` |
|
||||||
| 87.93 | 11,372,164.55 | 2.5% | 0.01 | `monotonic increasing point writes`
|
| 92.38 | 10,824,863.69 | 4.1% | 0.01 | `monotonic increasing point writes` |
|
||||||
|
|
||||||
# "Real data" test
|
# "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
|
## skip list
|
||||||
|
|
||||||
```
|
```
|
||||||
Check: 11.3839 seconds, 328.404 MB/s, Add: 5.32878 seconds, 131.745 MB/s, Gc ratio: 45.5903%
|
Check: 11.3385 seconds, 329.718 MB/s, Add: 5.35612 seconds, 131.072 MB/s, Gc ratio: 45.7173%
|
||||||
```
|
```
|
||||||
|
|
||||||
## radix tree
|
## radix tree
|
||||||
|
|
||||||
```
|
```
|
||||||
Check: 2.55069 seconds, 1465.69 MB/s, Add: 2.08443 seconds, 336.801 MB/s, Gc ratio: 41.748%
|
Check: 2.48583 seconds, 1503.93 MB/s, Add: 2.12768 seconds, 329.954 MB/s, Gc ratio: 41.7943%
|
||||||
```
|
```
|
||||||
|
|
||||||
## hash table
|
## hash table
|
||||||
@@ -101,5 +101,5 @@ Check: 2.55069 seconds, 1465.69 MB/s, Add: 2.08443 seconds, 336.801 MB/s, Gc rat
|
|||||||
(The hash table implementation doesn't work on range queries, and its purpose is to provide an idea of how fast point queries can be)
|
(The hash table implementation doesn't work on range queries, and its purpose is to provide an idea of how fast point queries can be)
|
||||||
|
|
||||||
```
|
```
|
||||||
Check: 1.84205 seconds, 2029.54 MB/s, Add: 0.60281 seconds, 1164.61 MB/s, Gc ratio: 48.8159%
|
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 <ConflictSet.h>
|
||||||
|
|
||||||
|
#include <cerrno>
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
|
#include <cstdio>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <fcntl.h>
|
#include <fcntl.h>
|
||||||
|
#include <string_view>
|
||||||
#include <sys/mman.h>
|
#include <sys/mman.h>
|
||||||
#include <sys/stat.h>
|
#include <sys/stat.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
using namespace weaselab;
|
||||||
|
|
||||||
double now() {
|
double now() {
|
||||||
return std::chrono::duration_cast<std::chrono::nanoseconds>(
|
return std::chrono::duration_cast<std::chrono::nanoseconds>(
|
||||||
std::chrono::steady_clock::now().time_since_epoch())
|
std::chrono::steady_clock::now().time_since_epoch())
|
||||||
@@ -28,7 +33,7 @@ constexpr inline size_t rightAlign(size_t offset, size_t alignment) {
|
|||||||
|
|
||||||
int main(int argc, const char **argv) {
|
int main(int argc, const char **argv) {
|
||||||
// Use with this dataset https://snap.stanford.edu/data/memetracker9.html
|
// Use with this dataset https://snap.stanford.edu/data/memetracker9.html
|
||||||
// Preprocess the files with `sed -i '' '/^Q/d'`
|
// Preprocess the files with `sed -i'' '/^Q/d'`
|
||||||
|
|
||||||
double checkTime = 0;
|
double checkTime = 0;
|
||||||
double addTime = 0;
|
double addTime = 0;
|
||||||
@@ -40,6 +45,8 @@ int main(int argc, const char **argv) {
|
|||||||
int64_t version = 0;
|
int64_t version = 0;
|
||||||
double timer = 0;
|
double timer = 0;
|
||||||
|
|
||||||
|
int64_t peakMemory = 0;
|
||||||
|
|
||||||
for (int i = 1; i < argc; ++i) {
|
for (int i = 1; i < argc; ++i) {
|
||||||
int fd = open(argv[i], O_RDONLY);
|
int fd = open(argv[i], O_RDONLY);
|
||||||
struct stat st;
|
struct stat st;
|
||||||
@@ -109,6 +116,10 @@ int main(int argc, const char **argv) {
|
|||||||
write = {};
|
write = {};
|
||||||
reads.clear();
|
reads.clear();
|
||||||
|
|
||||||
|
if (cs.getBytes() > peakMemory) {
|
||||||
|
peakMemory = cs.getBytes();
|
||||||
|
}
|
||||||
|
|
||||||
timer = now();
|
timer = now();
|
||||||
cs.setOldestVersion(version - 10000);
|
cs.setOldestVersion(version - 10000);
|
||||||
gcTime += now() - timer;
|
gcTime += now() - timer;
|
||||||
@@ -118,8 +129,9 @@ int main(int argc, const char **argv) {
|
|||||||
close(fd);
|
close(fd);
|
||||||
}
|
}
|
||||||
|
|
||||||
printf(
|
printf("Check: %g seconds, %g MB/s, Add: %g seconds, %g MB/s, Gc ratio: "
|
||||||
"Check: %g seconds, %g MB/s, Add: %g seconds, %g MB/s, Gc ratio: %g%%\n",
|
"%g%%, Peak idle memory: %g\n",
|
||||||
checkTime, checkBytes / checkTime * 1e-6, addTime,
|
checkTime, checkBytes / checkTime * 1e-6, addTime,
|
||||||
addBytes / addTime * 1e-6, gcTime / (gcTime + addTime) * 1e2);
|
addBytes / addTime * 1e-6, gcTime / (gcTime + addTime) * 1e2,
|
||||||
}
|
double(peakMemory));
|
||||||
|
}
|
||||||
|
155
ScriptTest.cpp
155
ScriptTest.cpp
@@ -1,155 +0,0 @@
|
|||||||
#include "Internal.h"
|
|
||||||
#include <ConflictSet.h>
|
|
||||||
|
|
||||||
#include <chrono>
|
|
||||||
#include <cstring>
|
|
||||||
#include <fcntl.h>
|
|
||||||
#include <sys/mman.h>
|
|
||||||
#include <sys/stat.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
inline size_t getPageSize() {
|
|
||||||
static size_t kPageSize = sysconf(_SC_PAGESIZE);
|
|
||||||
return kPageSize;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Helper for rounding up to page size (or some other alignment)
|
|
||||||
constexpr inline size_t rightAlign(size_t offset, size_t alignment) {
|
|
||||||
return offset % alignment == 0 ? offset
|
|
||||||
: ((offset / alignment) + 1) * alignment;
|
|
||||||
}
|
|
||||||
|
|
||||||
using StringView = std::basic_string_view<uint8_t>;
|
|
||||||
|
|
||||||
inline StringView operator"" _v(const char *str, size_t size) {
|
|
||||||
return {reinterpret_cast<const uint8_t *>(str), size};
|
|
||||||
}
|
|
||||||
|
|
||||||
int main(int argc, const char **argv) {
|
|
||||||
|
|
||||||
ConflictSet cs{0};
|
|
||||||
ReferenceImpl ref{0};
|
|
||||||
|
|
||||||
for (int i = 1; i < argc; ++i) {
|
|
||||||
int fd = open(argv[i], O_RDONLY);
|
|
||||||
struct stat st;
|
|
||||||
if (fstat(fd, &st) == -1) {
|
|
||||||
int err = errno;
|
|
||||||
fprintf(stderr, "stat error %s - %s\n", argv[i], strerror(err));
|
|
||||||
fflush(stderr);
|
|
||||||
abort();
|
|
||||||
}
|
|
||||||
|
|
||||||
int64_t size = rightAlign(st.st_size, getPageSize());
|
|
||||||
const uint8_t *begin =
|
|
||||||
(uint8_t *)mmap(0, size, PROT_READ, MAP_PRIVATE, fd, 0);
|
|
||||||
madvise((void *)begin, size, MADV_SEQUENTIAL);
|
|
||||||
auto *const mapOriginal = begin;
|
|
||||||
const auto sizeOriginal = size;
|
|
||||||
|
|
||||||
StringView b;
|
|
||||||
StringView e;
|
|
||||||
int64_t v = 0;
|
|
||||||
int64_t lastWriteVersion = 0;
|
|
||||||
int64_t lastOldestVersion = 0;
|
|
||||||
std::vector<ConflictSet::WriteRange> writeRanges;
|
|
||||||
std::vector<ConflictSet::ReadRange> readRanges;
|
|
||||||
std::vector<ConflictSet::Result> results;
|
|
||||||
|
|
||||||
for (uint8_t *end = (uint8_t *)memchr(begin, '\n', size); end != nullptr;) {
|
|
||||||
StringView line{begin, static_cast<size_t>(end - begin)};
|
|
||||||
size -= end - begin + 1;
|
|
||||||
begin = end + 1;
|
|
||||||
end = (uint8_t *)memchr(begin, '\n', size);
|
|
||||||
|
|
||||||
if (line.starts_with("begin"_v)) {
|
|
||||||
b = line.substr("begin "_v.size(), line.size());
|
|
||||||
printf("b <- %.*s\n", int(b.size()), b.data());
|
|
||||||
} else if (line.starts_with("end"_v)) {
|
|
||||||
e = line.substr("end "_v.size(), line.size());
|
|
||||||
printf("e <- %.*s\n", int(e.size()), e.data());
|
|
||||||
} else if (line.starts_with("version"_v)) {
|
|
||||||
line = line.substr("version "_v.size(), line.size());
|
|
||||||
v = 0;
|
|
||||||
for (auto c : line) {
|
|
||||||
v = v * 10 + int(c) - int('0');
|
|
||||||
}
|
|
||||||
printf("v <- %" PRId64 "\n", v);
|
|
||||||
} else if (line.starts_with("pointread"_v)) {
|
|
||||||
printf("pointread\n");
|
|
||||||
ConflictSet::ReadRange r;
|
|
||||||
r.begin.p = b.data();
|
|
||||||
r.begin.len = b.size();
|
|
||||||
r.end.len = 0;
|
|
||||||
r.readVersion = v;
|
|
||||||
readRanges.push_back(r);
|
|
||||||
} else if (line.starts_with("pointwrite"_v)) {
|
|
||||||
printf("pointwrite\n");
|
|
||||||
assert(writeRanges.empty() ||
|
|
||||||
(writeRanges.back().end.len == 0 ? writeRanges.back().begin
|
|
||||||
: writeRanges.back().end) < b);
|
|
||||||
ConflictSet::WriteRange w;
|
|
||||||
w.begin.p = b.data();
|
|
||||||
w.begin.len = b.size();
|
|
||||||
w.end.len = 0;
|
|
||||||
writeRanges.push_back(w);
|
|
||||||
} else if (line.starts_with("rangeread"_v)) {
|
|
||||||
printf("rangeread\n");
|
|
||||||
ConflictSet::ReadRange r;
|
|
||||||
r.begin.p = b.data();
|
|
||||||
r.begin.len = b.size();
|
|
||||||
r.end.p = e.data();
|
|
||||||
r.end.len = e.size();
|
|
||||||
r.readVersion = v;
|
|
||||||
readRanges.push_back(r);
|
|
||||||
} else if (line.starts_with("rangewrite"_v)) {
|
|
||||||
printf("rangewrite\n");
|
|
||||||
assert(b < e);
|
|
||||||
assert(writeRanges.empty() ||
|
|
||||||
(writeRanges.back().end.len == 0 ? writeRanges.back().begin
|
|
||||||
: writeRanges.back().end) < b);
|
|
||||||
ConflictSet::WriteRange w;
|
|
||||||
w.begin.p = b.data();
|
|
||||||
w.begin.len = b.size();
|
|
||||||
w.end.p = e.data();
|
|
||||||
w.end.len = e.size();
|
|
||||||
writeRanges.push_back(w);
|
|
||||||
} else if (line.starts_with("check"_v)) {
|
|
||||||
printf("check\n");
|
|
||||||
Arena arena;
|
|
||||||
auto *expected = new (arena) ConflictSet::Result[readRanges.size()];
|
|
||||||
auto *actual = new (arena) ConflictSet::Result[readRanges.size()];
|
|
||||||
ref.check(readRanges.data(), expected, readRanges.size());
|
|
||||||
cs.check(readRanges.data(), actual, readRanges.size());
|
|
||||||
for (int i = 0; i < int(readRanges.size()); ++i) {
|
|
||||||
if (expected[i] != actual[i]) {
|
|
||||||
fprintf(stderr, "Expected %s, got %s at index %d\n",
|
|
||||||
resultToStr(expected[i]), resultToStr(actual[i]), i);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
readRanges = {};
|
|
||||||
} else if (line.starts_with("addwrites"_v)) {
|
|
||||||
printf("addwrites\n");
|
|
||||||
assert(v > lastWriteVersion);
|
|
||||||
lastWriteVersion = v;
|
|
||||||
cs.addWrites(writeRanges.data(), writeRanges.size(), v);
|
|
||||||
ref.addWrites(writeRanges.data(), writeRanges.size(), v);
|
|
||||||
writeRanges = {};
|
|
||||||
} else if (line.starts_with("setoldest"_v)) {
|
|
||||||
printf("setoldest\n");
|
|
||||||
assert(v > lastOldestVersion);
|
|
||||||
lastOldestVersion = v;
|
|
||||||
cs.setOldestVersion(v);
|
|
||||||
ref.setOldestVersion(v);
|
|
||||||
} else if (line.empty() || line.starts_with(";"_v)) {
|
|
||||||
// skip
|
|
||||||
} else {
|
|
||||||
printf("Unrecognized line: %.*s\n", int(line.size()), line.data());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
munmap((void *)mapOriginal, sizeOriginal);
|
|
||||||
close(fd);
|
|
||||||
}
|
|
||||||
}
|
|
96
SkipList.cpp
96
SkipList.cpp
@@ -16,6 +16,8 @@
|
|||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
|
*
|
||||||
|
* This source code is modified to compile outside of FoundationDB
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "ConflictSet.h"
|
#include "ConflictSet.h"
|
||||||
@@ -149,7 +151,7 @@ private:
|
|||||||
setMaxVersion(level, v);
|
setMaxVersion(level, v);
|
||||||
}
|
}
|
||||||
|
|
||||||
void destroy() { safe_free(this); }
|
void destroy() { safe_free(this, getNodeSize()); }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
int getNodeSize() const {
|
int getNodeSize() const {
|
||||||
@@ -268,13 +270,21 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
explicit SkipList(Version version = 0) {
|
explicit SkipList(Version version = 0) {
|
||||||
|
#if DEBUG_VERBOSE
|
||||||
|
fprintf(stderr, "skip_list: create\n");
|
||||||
|
#endif
|
||||||
header = Node::create(StringRef(), MaxLevels - 1);
|
header = Node::create(StringRef(), MaxLevels - 1);
|
||||||
for (int l = 0; l < MaxLevels; l++) {
|
for (int l = 0; l < MaxLevels; l++) {
|
||||||
header->setNext(l, nullptr);
|
header->setNext(l, nullptr);
|
||||||
header->setMaxVersion(l, version);
|
header->setMaxVersion(l, version);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
~SkipList() { destroy(); }
|
~SkipList() {
|
||||||
|
#if DEBUG_VERBOSE
|
||||||
|
fprintf(stderr, "skip_list: destroy\n");
|
||||||
|
#endif
|
||||||
|
destroy();
|
||||||
|
}
|
||||||
SkipList(SkipList &&other) noexcept : header(other.header) {
|
SkipList(SkipList &&other) noexcept : header(other.header) {
|
||||||
other.header = nullptr;
|
other.header = nullptr;
|
||||||
}
|
}
|
||||||
@@ -603,6 +613,10 @@ struct __attribute__((visibility("hidden"))) ConflictSet::Impl {
|
|||||||
for (int s = stripes - 1; s >= 0; s--) {
|
for (int s = stripes - 1; s >= 0; s--) {
|
||||||
for (int i = 0; i * 2 < ss; ++i) {
|
for (int i = 0; i * 2 < ss; ++i) {
|
||||||
const auto &w = writes[s * stripeSize / 2 + i];
|
const auto &w = writes[s * stripeSize / 2 + i];
|
||||||
|
#if DEBUG_VERBOSE
|
||||||
|
printf("Write begin: %s\n", printable(w.begin).c_str());
|
||||||
|
fflush(stdout);
|
||||||
|
#endif
|
||||||
values[i * 2] = {w.begin.p, size_t(w.begin.len)};
|
values[i * 2] = {w.begin.p, size_t(w.begin.len)};
|
||||||
values[i * 2 + 1] = w.end.len > 0
|
values[i * 2 + 1] = w.end.len > 0
|
||||||
? StringRef{w.end.p, size_t(w.end.len)}
|
? StringRef{w.end.p, size_t(w.end.len)}
|
||||||
@@ -627,6 +641,8 @@ struct __attribute__((visibility("hidden"))) ConflictSet::Impl {
|
|||||||
removalArena, {finger.getValue().data(), finger.getValue().size()});
|
removalArena, {finger.getValue().data(), finger.getValue().size()});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int64_t totalBytes = 0;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
int64_t keyUpdates = 10;
|
int64_t keyUpdates = 10;
|
||||||
Arena removalArena;
|
Arena removalArena;
|
||||||
@@ -635,27 +651,73 @@ private:
|
|||||||
SkipList skipList;
|
SkipList skipList;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Internal entry points. Public entry points should just delegate to these
|
||||||
|
|
||||||
|
void internal_check(ConflictSet::Impl *impl,
|
||||||
|
const ConflictSet::ReadRange *reads,
|
||||||
|
ConflictSet::Result *results, int count) {
|
||||||
|
impl->check(reads, results, count);
|
||||||
|
}
|
||||||
|
void internal_addWrites(ConflictSet::Impl *impl,
|
||||||
|
const ConflictSet::WriteRange *writes, int count,
|
||||||
|
int64_t writeVersion) {
|
||||||
|
mallocBytesDelta = 0;
|
||||||
|
impl->addWrites(writes, count, writeVersion);
|
||||||
|
impl->totalBytes += mallocBytesDelta;
|
||||||
|
#if SHOW_MEMORY
|
||||||
|
if (impl->totalBytes != mallocBytes) {
|
||||||
|
abort();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void internal_setOldestVersion(ConflictSet::Impl *impl, int64_t oldestVersion) {
|
||||||
|
mallocBytesDelta = 0;
|
||||||
|
impl->setOldestVersion(oldestVersion);
|
||||||
|
impl->totalBytes += mallocBytesDelta;
|
||||||
|
#if SHOW_MEMORY
|
||||||
|
if (impl->totalBytes != mallocBytes) {
|
||||||
|
abort();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
ConflictSet::Impl *internal_create(int64_t oldestVersion) {
|
||||||
|
mallocBytesDelta = 0;
|
||||||
|
auto *result = new (safe_malloc(sizeof(ConflictSet::Impl)))
|
||||||
|
ConflictSet::Impl{oldestVersion};
|
||||||
|
result->totalBytes += mallocBytesDelta;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
void internal_destroy(ConflictSet::Impl *impl) {
|
||||||
|
impl->~Impl();
|
||||||
|
safe_free(impl, sizeof(ConflictSet::Impl));
|
||||||
|
}
|
||||||
|
|
||||||
|
int64_t internal_getBytes(ConflictSet::Impl *impl) { return impl->totalBytes; }
|
||||||
|
|
||||||
void ConflictSet::check(const ReadRange *reads, Result *results,
|
void ConflictSet::check(const ReadRange *reads, Result *results,
|
||||||
int count) const {
|
int count) const {
|
||||||
return impl->check(reads, results, count);
|
internal_check(impl, reads, results, count);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ConflictSet::addWrites(const WriteRange *writes, int count,
|
void ConflictSet::addWrites(const WriteRange *writes, int count,
|
||||||
int64_t writeVersion) {
|
int64_t writeVersion) {
|
||||||
return impl->addWrites(writes, count, writeVersion);
|
internal_addWrites(impl, writes, count, writeVersion);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ConflictSet::setOldestVersion(int64_t oldestVersion) {
|
void ConflictSet::setOldestVersion(int64_t oldestVersion) {
|
||||||
return impl->setOldestVersion(oldestVersion);
|
internal_setOldestVersion(impl, oldestVersion);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int64_t ConflictSet::getBytes() const { return internal_getBytes(impl); }
|
||||||
|
|
||||||
ConflictSet::ConflictSet(int64_t oldestVersion)
|
ConflictSet::ConflictSet(int64_t oldestVersion)
|
||||||
: impl(new (safe_malloc(sizeof(Impl))) Impl{oldestVersion}) {}
|
: impl(internal_create(oldestVersion)) {}
|
||||||
|
|
||||||
ConflictSet::~ConflictSet() {
|
ConflictSet::~ConflictSet() {
|
||||||
if (impl) {
|
if (impl) {
|
||||||
impl->~Impl();
|
internal_destroy(impl);
|
||||||
safe_free(impl);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -676,32 +738,34 @@ extern "C" {
|
|||||||
__attribute__((__visibility__("default"))) void
|
__attribute__((__visibility__("default"))) void
|
||||||
ConflictSet_check(void *cs, const ConflictSet_ReadRange *reads,
|
ConflictSet_check(void *cs, const ConflictSet_ReadRange *reads,
|
||||||
ConflictSet_Result *results, int count) {
|
ConflictSet_Result *results, int count) {
|
||||||
((ConflictSet::Impl *)cs)->check(reads, results, count);
|
internal_check((ConflictSet::Impl *)cs, reads, results, count);
|
||||||
}
|
}
|
||||||
__attribute__((__visibility__("default"))) void
|
__attribute__((__visibility__("default"))) void
|
||||||
ConflictSet_addWrites(void *cs, const ConflictSet_WriteRange *writes, int count,
|
ConflictSet_addWrites(void *cs, const ConflictSet_WriteRange *writes, int count,
|
||||||
int64_t writeVersion) {
|
int64_t writeVersion) {
|
||||||
((ConflictSet::Impl *)cs)->addWrites(writes, count, writeVersion);
|
internal_addWrites((ConflictSet::Impl *)cs, writes, count, writeVersion);
|
||||||
}
|
}
|
||||||
__attribute__((__visibility__("default"))) void
|
__attribute__((__visibility__("default"))) void
|
||||||
ConflictSet_setOldestVersion(void *cs, int64_t oldestVersion) {
|
ConflictSet_setOldestVersion(void *cs, int64_t oldestVersion) {
|
||||||
((ConflictSet::Impl *)cs)->setOldestVersion(oldestVersion);
|
internal_setOldestVersion((ConflictSet::Impl *)cs, oldestVersion);
|
||||||
}
|
}
|
||||||
__attribute__((__visibility__("default"))) void *
|
__attribute__((__visibility__("default"))) void *
|
||||||
ConflictSet_create(int64_t oldestVersion) {
|
ConflictSet_create(int64_t oldestVersion) {
|
||||||
return new (safe_malloc(sizeof(ConflictSet::Impl)))
|
return internal_create(oldestVersion);
|
||||||
ConflictSet::Impl{oldestVersion};
|
|
||||||
}
|
}
|
||||||
__attribute__((__visibility__("default"))) void ConflictSet_destroy(void *cs) {
|
__attribute__((__visibility__("default"))) void ConflictSet_destroy(void *cs) {
|
||||||
using Impl = ConflictSet::Impl;
|
internal_destroy((ConflictSet::Impl *)cs);
|
||||||
((Impl *)cs)->~Impl();
|
}
|
||||||
safe_free(cs);
|
__attribute__((__visibility__("default"))) int64_t
|
||||||
|
ConflictSet_getBytes(void *cs) {
|
||||||
|
return internal_getBytes((ConflictSet::Impl *)cs);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#if SHOW_MEMORY
|
#if SHOW_MEMORY
|
||||||
struct __attribute__((visibility("default"))) PeakPrinter {
|
struct __attribute__((visibility("default"))) PeakPrinter {
|
||||||
~PeakPrinter() {
|
~PeakPrinter() {
|
||||||
|
printf("--- skip_list ---\n");
|
||||||
printf("malloc bytes: %g\n", double(mallocBytes));
|
printf("malloc bytes: %g\n", double(mallocBytes));
|
||||||
printf("Peak malloc bytes: %g\n", double(peakMallocBytes));
|
printf("Peak malloc bytes: %g\n", double(peakMallocBytes));
|
||||||
}
|
}
|
||||||
|
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
|
8
apple-symbol-imports.txt
Normal file
8
apple-symbol-imports.txt
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
__tlv_bootstrap
|
||||||
|
_abort
|
||||||
|
_bzero
|
||||||
|
_free
|
||||||
|
_malloc
|
||||||
|
_memcpy
|
||||||
|
_memmove
|
||||||
|
dyld_stub_binder
|
136
conflict_set.py
Normal file
136
conflict_set.py
Normal file
@@ -0,0 +1,136 @@
|
|||||||
|
import ctypes
|
||||||
|
import enum
|
||||||
|
import os
|
||||||
|
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
|
||||||
|
class _Key(ctypes.Structure):
|
||||||
|
_fields_ = [("p", ctypes.POINTER(ctypes.c_ubyte)), ("len", ctypes.c_int)]
|
||||||
|
|
||||||
|
|
||||||
|
class ReadRange(ctypes.Structure):
|
||||||
|
_fields_ = [
|
||||||
|
("begin", _Key),
|
||||||
|
("end", _Key),
|
||||||
|
("readVersion", ctypes.c_int64),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class WriteRange(ctypes.Structure):
|
||||||
|
_fields_ = [("begin", _Key), ("end", _Key)]
|
||||||
|
|
||||||
|
|
||||||
|
class Result(enum.Enum):
|
||||||
|
COMMIT = 0
|
||||||
|
CONFLICT = 1
|
||||||
|
TOO_OLD = 2
|
||||||
|
|
||||||
|
|
||||||
|
def write(begin: bytes, end: Optional[bytes] = None) -> WriteRange:
|
||||||
|
b = (ctypes.c_ubyte * len(begin)).from_buffer(bytearray(begin))
|
||||||
|
|
||||||
|
if end is None:
|
||||||
|
e = (ctypes.c_ubyte * 0)()
|
||||||
|
else:
|
||||||
|
e = (ctypes.c_ubyte * len(end)).from_buffer(bytearray(end))
|
||||||
|
return WriteRange(_Key(b, len(b)), _Key(e, len(e)))
|
||||||
|
|
||||||
|
|
||||||
|
def read(version: int, begin: bytes, end: Optional[bytes] = None) -> ReadRange:
|
||||||
|
b = (ctypes.c_ubyte * len(begin)).from_buffer(bytearray(begin))
|
||||||
|
if end is None:
|
||||||
|
e = (ctypes.c_ubyte * 0)()
|
||||||
|
else:
|
||||||
|
e = (ctypes.c_ubyte * len(end)).from_buffer(bytearray(end))
|
||||||
|
return ReadRange(_Key(b, len(b)), _Key(e, len(e)), version)
|
||||||
|
|
||||||
|
|
||||||
|
class ConflictSet:
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
version: int = 0,
|
||||||
|
build_dir: Optional[str] = None,
|
||||||
|
implementation: Optional[str] = None,
|
||||||
|
) -> None:
|
||||||
|
self._lib = None
|
||||||
|
if build_dir is None:
|
||||||
|
build_dir = os.path.dirname(__file__) + "/build"
|
||||||
|
if implementation is None:
|
||||||
|
implementation = "radix_tree"
|
||||||
|
for f in (
|
||||||
|
build_dir + "/" + implementation + "/libconflict-set.so.0",
|
||||||
|
os.path.dirname(__file__)
|
||||||
|
+ "/build/"
|
||||||
|
+ implementation
|
||||||
|
+ "/libconflict-set.0.dylib",
|
||||||
|
):
|
||||||
|
try:
|
||||||
|
self._lib = ctypes.cdll.LoadLibrary(f)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if self._lib is None:
|
||||||
|
import sys
|
||||||
|
|
||||||
|
print(
|
||||||
|
"Could not find libconflict-set implementation " + implementation,
|
||||||
|
file=sys.stderr,
|
||||||
|
)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
self._lib.ConflictSet_create.argtypes = (ctypes.c_int64,)
|
||||||
|
self._lib.ConflictSet_create.restype = ctypes.c_void_p
|
||||||
|
|
||||||
|
self._lib.ConflictSet_check.argtypes = (
|
||||||
|
ctypes.c_void_p,
|
||||||
|
ctypes.POINTER(ReadRange),
|
||||||
|
ctypes.POINTER(ctypes.c_int),
|
||||||
|
ctypes.c_int,
|
||||||
|
)
|
||||||
|
|
||||||
|
self._lib.ConflictSet_addWrites.argtypes = (
|
||||||
|
ctypes.c_void_p,
|
||||||
|
ctypes.POINTER(WriteRange),
|
||||||
|
ctypes.c_int,
|
||||||
|
ctypes.c_int64,
|
||||||
|
)
|
||||||
|
|
||||||
|
self._lib.ConflictSet_setOldestVersion.argtypes = (
|
||||||
|
ctypes.c_void_p,
|
||||||
|
ctypes.c_int64,
|
||||||
|
)
|
||||||
|
|
||||||
|
self._lib.ConflictSet_destroy.argtypes = (ctypes.c_void_p,)
|
||||||
|
|
||||||
|
self._lib.ConflictSet_getBytes.argtypes = (ctypes.c_void_p,)
|
||||||
|
self._lib.ConflictSet_getBytes.restype = ctypes.c_int64
|
||||||
|
|
||||||
|
self.p = self._lib.ConflictSet_create(version)
|
||||||
|
|
||||||
|
def addWrites(self, version: int, *writes: WriteRange):
|
||||||
|
self._lib.ConflictSet_addWrites(
|
||||||
|
self.p, (WriteRange * len(writes))(*writes), len(writes), version
|
||||||
|
)
|
||||||
|
|
||||||
|
def check(self, *reads: ReadRange) -> list[Result]:
|
||||||
|
r = (ctypes.c_int * len(reads))()
|
||||||
|
self._lib.ConflictSet_check(self.p, *reads, r, 1)
|
||||||
|
return [Result(x) for x in r]
|
||||||
|
|
||||||
|
def setOldestVersion(self, version: int) -> None:
|
||||||
|
self._lib.ConflictSet_setOldestVersion(self.p, version)
|
||||||
|
|
||||||
|
def getBytes(self) -> int:
|
||||||
|
return self._lib.ConflictSet_getBytes(self.p)
|
||||||
|
|
||||||
|
def __enter__(self):
|
||||||
|
return self
|
||||||
|
|
||||||
|
def close(self) -> None:
|
||||||
|
if self.p is not None:
|
||||||
|
self._lib.ConflictSet_destroy(self.p)
|
||||||
|
self.p = None
|
||||||
|
|
||||||
|
def __exit__(self, exception_type, exception_value, exception_traceback):
|
||||||
|
self.close()
|
@@ -7,6 +7,7 @@ int main(void) {
|
|||||||
ConflictSet_WriteRange w;
|
ConflictSet_WriteRange w;
|
||||||
ConflictSet_Result result;
|
ConflictSet_Result result;
|
||||||
ConflictSet_ReadRange r;
|
ConflictSet_ReadRange r;
|
||||||
|
int64_t bytes;
|
||||||
w.begin.p = (const uint8_t *)"0000";
|
w.begin.p = (const uint8_t *)"0000";
|
||||||
w.begin.len = 4;
|
w.begin.len = 4;
|
||||||
w.end.len = 0;
|
w.end.len = 0;
|
||||||
@@ -17,6 +18,8 @@ int main(void) {
|
|||||||
r.readVersion = 0;
|
r.readVersion = 0;
|
||||||
ConflictSet_check(cs, &r, &result, 1);
|
ConflictSet_check(cs, &r, &result, 1);
|
||||||
assert(result == ConflictSet_Conflict);
|
assert(result == ConflictSet_Conflict);
|
||||||
|
bytes = ConflictSet_getBytes(cs);
|
||||||
|
assert(bytes > 0);
|
||||||
ConflictSet_destroy(cs);
|
ConflictSet_destroy(cs);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
@@ -2,6 +2,8 @@
|
|||||||
|
|
||||||
#include <cassert>
|
#include <cassert>
|
||||||
|
|
||||||
|
using namespace weaselab;
|
||||||
|
|
||||||
int main(void) {
|
int main(void) {
|
||||||
ConflictSet cs(0);
|
ConflictSet cs(0);
|
||||||
ConflictSet::WriteRange w;
|
ConflictSet::WriteRange w;
|
||||||
@@ -17,4 +19,6 @@ int main(void) {
|
|||||||
r.readVersion = 0;
|
r.readVersion = 0;
|
||||||
cs.check(&r, &result, 1);
|
cs.check(&r, &result, 1);
|
||||||
assert(result == ConflictSet::Conflict);
|
assert(result == ConflictSet::Conflict);
|
||||||
|
int64_t bytes = cs.getBytes();
|
||||||
|
assert(bytes > 0);
|
||||||
}
|
}
|
||||||
|
BIN
corpus/000a5093ba8c8c19af59c6f443ea8e9b415e58f5
Normal file
BIN
corpus/000a5093ba8c8c19af59c6f443ea8e9b415e58f5
Normal file
Binary file not shown.
BIN
corpus/008b07ecb87642a6c791131a190c7891cafd1c14
Normal file
BIN
corpus/008b07ecb87642a6c791131a190c7891cafd1c14
Normal file
Binary file not shown.
Binary file not shown.
BIN
corpus/00e5ea02654895749b3440f0ce504774c3aa664a
Normal file
BIN
corpus/00e5ea02654895749b3440f0ce504774c3aa664a
Normal file
Binary file not shown.
BIN
corpus/0175cef3b48b83d773525ffb3b85878429b69101
Normal file
BIN
corpus/0175cef3b48b83d773525ffb3b85878429b69101
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
corpus/02661fd8027859ed879f0d9913a82e6b9b53489b
Normal file
BIN
corpus/02661fd8027859ed879f0d9913a82e6b9b53489b
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
corpus/05580fb0eb2423ab9f61ae521d7cf200372096bb
Normal file
BIN
corpus/05580fb0eb2423ab9f61ae521d7cf200372096bb
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
corpus/06ad0c45c9fdedd87759c71a2452a2c1b1f6135d
Normal file
BIN
corpus/06ad0c45c9fdedd87759c71a2452a2c1b1f6135d
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
corpus/0dec40d6aac2a6d87eb6d18113d8f6f13b151d16
Normal file
BIN
corpus/0dec40d6aac2a6d87eb6d18113d8f6f13b151d16
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
corpus/11c19694ede01d08aa5867c3774a588ef4f414ab
Normal file
BIN
corpus/11c19694ede01d08aa5867c3774a588ef4f414ab
Normal file
Binary file not shown.
BIN
corpus/11ddf24e96c1fd8f98ca4d7ddd7849d0c8bfc15d
Normal file
BIN
corpus/11ddf24e96c1fd8f98ca4d7ddd7849d0c8bfc15d
Normal file
Binary file not shown.
BIN
corpus/11f861f2821ad4adb141cd7ede7071cdc1f61ead
Normal file
BIN
corpus/11f861f2821ad4adb141cd7ede7071cdc1f61ead
Normal file
Binary file not shown.
BIN
corpus/1290b3a44b74acc722ca4354e7bec49aeb893d53
Normal file
BIN
corpus/1290b3a44b74acc722ca4354e7bec49aeb893d53
Normal file
Binary file not shown.
BIN
corpus/12e9c6221a24b0103536cf314e0e8334613c1701
Normal file
BIN
corpus/12e9c6221a24b0103536cf314e0e8334613c1701
Normal file
Binary file not shown.
Binary file not shown.
BIN
corpus/145a5922b70a94e31d2767c1b23c36ea8eefda4b
Normal file
BIN
corpus/145a5922b70a94e31d2767c1b23c36ea8eefda4b
Normal file
Binary file not shown.
BIN
corpus/146f749b4d82039493bd41b8c575f889274e70f3
Normal file
BIN
corpus/146f749b4d82039493bd41b8c575f889274e70f3
Normal file
Binary file not shown.
BIN
corpus/14877d995b1f1df6797cd5deb184b64a7351e10b
Normal file
BIN
corpus/14877d995b1f1df6797cd5deb184b64a7351e10b
Normal file
Binary file not shown.
BIN
corpus/1575adc1dcd6884a47a6362e52e7a463ac36fad3
Normal file
BIN
corpus/1575adc1dcd6884a47a6362e52e7a463ac36fad3
Normal file
Binary file not shown.
Binary file not shown.
BIN
corpus/15fb6bf1af4df50cf0280586eb7e6745f09864a0
Normal file
BIN
corpus/15fb6bf1af4df50cf0280586eb7e6745f09864a0
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
corpus/18c756fc7f6d725849d3f673722fa75de198e421
Normal file
BIN
corpus/18c756fc7f6d725849d3f673722fa75de198e421
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
corpus/1a7effd2f21a435035d4ffe9d546c17851d80d3f
Normal file
BIN
corpus/1a7effd2f21a435035d4ffe9d546c17851d80d3f
Normal file
Binary file not shown.
Binary file not shown.
BIN
corpus/1b8a5ae93daf3a194087cc9747295352d7bb906b
Normal file
BIN
corpus/1b8a5ae93daf3a194087cc9747295352d7bb906b
Normal file
Binary file not shown.
Binary file not shown.
BIN
corpus/1d75cd919eb6add4a6650dc52bdf10aa54ad987d
Normal file
BIN
corpus/1d75cd919eb6add4a6650dc52bdf10aa54ad987d
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.6 KiB |
BIN
corpus/1de5b529f1ce967f1da3d8c8985eb75e7a8b3534
Normal file
BIN
corpus/1de5b529f1ce967f1da3d8c8985eb75e7a8b3534
Normal file
Binary file not shown.
BIN
corpus/1e0cec76ca498b4f323f77edf8f6a8944cea89f8
Normal file
BIN
corpus/1e0cec76ca498b4f323f77edf8f6a8944cea89f8
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
corpus/1ff354d2fa8fbc3b23301c529505e3b1aa89ed4b
Normal file
BIN
corpus/1ff354d2fa8fbc3b23301c529505e3b1aa89ed4b
Normal file
Binary file not shown.
BIN
corpus/200eec43ec83525a1aac898481945c1d2f55b243
Normal file
BIN
corpus/200eec43ec83525a1aac898481945c1d2f55b243
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
corpus/213304f57979361a2d0dd870036fa44ec30059d2
Normal file
BIN
corpus/213304f57979361a2d0dd870036fa44ec30059d2
Normal file
Binary file not shown.
BIN
corpus/214cdbf9514e5ba221551a5009361af30d35c4a1
Normal file
BIN
corpus/214cdbf9514e5ba221551a5009361af30d35c4a1
Normal file
Binary file not shown.
BIN
corpus/214ddabae4ae41511de39d819ba5891cee0062a3
Normal file
BIN
corpus/214ddabae4ae41511de39d819ba5891cee0062a3
Normal file
Binary file not shown.
Binary file not shown.
BIN
corpus/229c04439f0b81c74e6e0b58b133eb1c9fc646ab
Normal file
BIN
corpus/229c04439f0b81c74e6e0b58b133eb1c9fc646ab
Normal file
Binary file not shown.
BIN
corpus/22a189746ee03dcb4cb7bb41ae7cbf9668924033
Normal file
BIN
corpus/22a189746ee03dcb4cb7bb41ae7cbf9668924033
Normal file
Binary file not shown.
BIN
corpus/2308bcb0bf0c368b3a7b97d1da97ce2e57954935
Normal file
BIN
corpus/2308bcb0bf0c368b3a7b97d1da97ce2e57954935
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
corpus/241004b6ce0c24476c0df19dde962576f8873bd2
Normal file
BIN
corpus/241004b6ce0c24476c0df19dde962576f8873bd2
Normal file
Binary file not shown.
BIN
corpus/25e117f74e336bebb4ff10b81369a6a03581354d
Normal file
BIN
corpus/25e117f74e336bebb4ff10b81369a6a03581354d
Normal file
Binary file not shown.
BIN
corpus/2602771d5747649955cd614331e9e7fa3ada09a5
Normal file
BIN
corpus/2602771d5747649955cd614331e9e7fa3ada09a5
Normal file
Binary file not shown.
BIN
corpus/260540a4a43aa8b3fb307a8b50c5840a174f8de0
Normal file
BIN
corpus/260540a4a43aa8b3fb307a8b50c5840a174f8de0
Normal file
Binary file not shown.
BIN
corpus/267cdc20728767ffce359541fdc4fae742995f5a
Normal file
BIN
corpus/267cdc20728767ffce359541fdc4fae742995f5a
Normal file
Binary file not shown.
Binary file not shown.
BIN
corpus/26a3648f0212ef596d78e4766bf248ffd9763891
Normal file
BIN
corpus/26a3648f0212ef596d78e4766bf248ffd9763891
Normal file
Binary file not shown.
Binary file not shown.
BIN
corpus/2867386732f6a374d3437ba985c836d070554f6a
Normal file
BIN
corpus/2867386732f6a374d3437ba985c836d070554f6a
Normal file
Binary file not shown.
BIN
corpus/288f8d59abbc514dee77e06cd1fae02ad689ae48
Normal file
BIN
corpus/288f8d59abbc514dee77e06cd1fae02ad689ae48
Normal file
Binary file not shown.
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