Compare commits
239 Commits
351ff3df3b
...
v0.0.8
Author | SHA1 | Date | |
---|---|---|---|
60cb274a15 | |||
687bc9c935 | |||
d50bb8bc80 | |||
f19b403f19 | |||
34cd210907 | |||
1a5da9e899 | |||
8ba9b04d8c | |||
d895be36d2 | |||
65f8462e88 | |||
46e01af027 | |||
c9d0d72684 | |||
9046dc5a8f | |||
e2927bf0fa | |||
75a2b8d06c | |||
76df63a9d7 | |||
9c5b38b09a | |||
7142dab7ae | |||
3db3d975fc | |||
982b31af34 | |||
cc716ef16b | |||
88bcc7b75c | |||
3e6be6bd83 | |||
e59fee39c7 | |||
3e2c8310bb | |||
8264f1342d | |||
5d7e9c6f85 | |||
cdf42fcb34 | |||
cbe40b5dba | |||
a04e81b3ff | |||
0be97a34b6 | |||
68ab9a9f08 | |||
01488880ef | |||
bb84792cff | |||
1f421e95ff | |||
66bd799f05 | |||
2646d5eaf1 | |||
0367ba9856 | |||
9dec45317e | |||
a68ad5dd17 | |||
8e3eacb54f | |||
0184e1d7f6 | |||
c52d50f4f9 | |||
447da11d59 | |||
daa8e02d4f | |||
fd3ea2c2a8 | |||
0b839b9d7e | |||
11a022dcf7 | |||
94da4c72a5 | |||
461e07822a | |||
75499543e7 | |||
81f44d352f | |||
45da8fb996 | |||
4958a4cced | |||
587874841f | |||
648b0b9238 | |||
d3f4afa167 | |||
f762add4d6 | |||
b311e5f1f0 | |||
ff81890921 | |||
0e96177f5c | |||
efb0e52a0a | |||
2df7000090 | |||
5378a06c39 | |||
12c6ed2568 | |||
a2bf839b19 | |||
c065b185ae | |||
639518bed4 | |||
7de983cc15 | |||
1b4b61ddc6 | |||
bff7b85de2 | |||
9108ee209a | |||
f8bf1c6eb4 | |||
4da2a01614 | |||
bb0e654040 | |||
cce7d29410 | |||
13f8d3fa8a | |||
02866a8cae | |||
fa86d3e707 | |||
7d1d1d7b2a | |||
789ecc29b3 | |||
08f2998a85 | |||
c882d7663d | |||
bfea4384ba | |||
6520e3d734 | |||
23ace8aac5 | |||
62e35de320 | |||
22e4ab01a1 | |||
b3aeed0caa | |||
5f3833e965 | |||
8b1cd9c052 | |||
bb9bc3d7b5 | |||
89b3354a80 | |||
488c723726 | |||
76d0785b33 | |||
add0af11ad | |||
2c0adf4a8b | |||
e8ac78cce6 | |||
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 | |||
cb4c2b7e1e | |||
ef9b789745 | |||
edd7bcaa1e | |||
be8ac879c5 | |||
83c7f66d67 | |||
a5710b8282 | |||
c31eebd5de | |||
ddeb059968 | |||
5a0bcf9a5a | |||
97717cec86 | |||
6a13c43a78 | |||
c6c438bae2 | |||
7d4f832b43 | |||
5b0c3c2428 | |||
f2b5e9b0bf | |||
8e0e65dac6 | |||
5aab76847a | |||
1a51aa00e5 | |||
3975bada0c | |||
a5330b6e23 | |||
2e246ec6a4 | |||
6d7e3c9849 | |||
671da5d096 | |||
303b368fc5 | |||
9f5a68e2c0 | |||
dfbb3ce5f1 | |||
e7719b6e0b | |||
83fedf1f9e | |||
8556caf360 | |||
9d13ca84f5 | |||
a79436ee9b | |||
e9c8537cf2 | |||
5b988efe6f | |||
e35d698b21 | |||
30496d14e7 | |||
eb93157ddf | |||
9cafef8bbb | |||
6f81580953 | |||
429fe5baed | |||
a0451e4423 | |||
a9b3d3d1c9 | |||
b817e3c749 | |||
ee36bda8f8 | |||
a8f4bd91c8 | |||
35086ee66a | |||
0f795cf163 | |||
a07c93ffff | |||
c68f563017 | |||
6b6a9bace9 | |||
3cb0765fdd |
2
.clangd
2
.clangd
@@ -1,2 +1,2 @@
|
||||
CompileFlags:
|
||||
Add: [-DENABLE_MAIN, -UNDEBUG, -DENABLE_FUZZ, -fexceptions]
|
||||
Add: [-DENABLE_MAIN, -UNDEBUG, -DENABLE_FUZZ, -DTHREAD_TEST, -fexceptions]
|
||||
|
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,2 +1,3 @@
|
||||
.cache
|
||||
__pycache__
|
||||
build
|
||||
|
@@ -1,11 +1,11 @@
|
||||
repos:
|
||||
- repo: https://github.com/pre-commit/mirrors-clang-format
|
||||
rev: b111689e7b5cba60be3c62d5db2bd1357f4d36ca
|
||||
rev: 6d365699efc33b1b432eab5b4ae331a19e1857de # frozen: v18.1.2
|
||||
hooks:
|
||||
- id: clang-format
|
||||
exclude: ".*third_party/.*"
|
||||
- repo: https://github.com/cheshirekow/cmake-format-precommit
|
||||
rev: e2c2116d86a80e72e7146a06e68b7c228afc6319
|
||||
rev: e2c2116d86a80e72e7146a06e68b7c228afc6319 # frozen: v0.6.13
|
||||
hooks:
|
||||
- id: cmake-format
|
||||
- repo: local
|
||||
@@ -13,7 +13,7 @@ repos:
|
||||
- id: debug verbose check
|
||||
name: disallow checking in DEBUG_VERBOSE=1
|
||||
description: disallow checking in DEBUG_VERBOSE=1
|
||||
entry: '^#define DEBUG_VERBOSE 1$'
|
||||
entry: "^#define DEBUG_VERBOSE 1$"
|
||||
language: pygrep
|
||||
types: [c++]
|
||||
- repo: local
|
||||
@@ -21,6 +21,14 @@ repos:
|
||||
- id: debug verbose check
|
||||
name: disallow checking in SHOW_MEMORY=1
|
||||
description: disallow checking in SHOW_MEMORY=1
|
||||
entry: '^#define SHOW_MEMORY 1$'
|
||||
entry: "^#define SHOW_MEMORY 1$"
|
||||
language: pygrep
|
||||
types: [c++]
|
||||
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
|
||||
|
109
Bench.cpp
109
Bench.cpp
@@ -34,7 +34,7 @@ ConflictSet::ReadRange singleton(Arena &arena, std::span<const uint8_t> key) {
|
||||
std::span<uint8_t>(new (arena) uint8_t[key.size() + 1], key.size() + 1);
|
||||
memcpy(r.data(), key.data(), key.size());
|
||||
r[key.size()] = 0;
|
||||
return {key.data(), int(key.size()), r.data(), int(r.size())};
|
||||
return {{key.data(), int(key.size())}, {r.data(), int(r.size())}, 0};
|
||||
}
|
||||
|
||||
ConflictSet::ReadRange prefixRange(Arena &arena, std::span<const uint8_t> key) {
|
||||
@@ -52,7 +52,7 @@ ConflictSet::ReadRange prefixRange(Arena &arena, std::span<const uint8_t> key) {
|
||||
auto r = std::span<uint8_t>(new (arena) uint8_t[index + 1], index + 1);
|
||||
memcpy(r.data(), key.data(), index + 1);
|
||||
r[r.size() - 1]++;
|
||||
return {key.data(), int(key.size()), r.data(), int(r.size())};
|
||||
return {{key.data(), int(key.size())}, {r.data(), int(r.size())}, 0};
|
||||
}
|
||||
|
||||
void benchConflictSet() {
|
||||
@@ -258,4 +258,107 @@ void benchConflictSet() {
|
||||
}
|
||||
}
|
||||
|
||||
int main(void) { benchConflictSet(); }
|
||||
constexpr int kKeyLenForWorstCase = 50;
|
||||
|
||||
ConflictSet worstCaseConflictSetForRadixRangeRead(int cardinality) {
|
||||
ConflictSet cs{0};
|
||||
|
||||
for (int i = 0; i < kKeyLenForWorstCase; ++i) {
|
||||
for (int j = 0; j < cardinality; ++j) {
|
||||
auto b = std::vector<uint8_t>(i, 0);
|
||||
b.push_back(j);
|
||||
auto e = std::vector<uint8_t>(i, 255);
|
||||
e.push_back(255 - j);
|
||||
weaselab::ConflictSet::WriteRange w[] = {{
|
||||
{b.data(), int(b.size())},
|
||||
{nullptr, 0},
|
||||
},
|
||||
{
|
||||
{e.data(), int(e.size())},
|
||||
{nullptr, 0},
|
||||
}};
|
||||
std::sort(std::begin(w), std::end(w),
|
||||
[](const auto &lhs, const auto &rhs) {
|
||||
int cl = std::min(lhs.begin.len, rhs.begin.len);
|
||||
if (cl > 0) {
|
||||
int c = memcmp(lhs.begin.p, rhs.begin.p, cl);
|
||||
if (c != 0) {
|
||||
return c < 0;
|
||||
}
|
||||
}
|
||||
return lhs.begin.len < rhs.begin.len;
|
||||
});
|
||||
cs.addWrites(w, sizeof(w) / sizeof(w[0]), 0);
|
||||
}
|
||||
}
|
||||
|
||||
// Defeat short-circuiting on the left
|
||||
{
|
||||
auto k = std::vector<uint8_t>(kKeyLenForWorstCase, 0);
|
||||
weaselab::ConflictSet::WriteRange w[] = {
|
||||
{
|
||||
{k.data(), int(k.size())},
|
||||
{nullptr, 0},
|
||||
},
|
||||
};
|
||||
cs.addWrites(w, sizeof(w) / sizeof(w[0]), 1);
|
||||
}
|
||||
|
||||
// Defeat short-circuiting on the right
|
||||
{
|
||||
auto k = std::vector<uint8_t>(kKeyLenForWorstCase, 255);
|
||||
weaselab::ConflictSet::WriteRange w[] = {
|
||||
{
|
||||
{k.data(), int(k.size())},
|
||||
{nullptr, 0},
|
||||
},
|
||||
};
|
||||
cs.addWrites(w, sizeof(w) / sizeof(w[0]), 1);
|
||||
}
|
||||
|
||||
return cs;
|
||||
}
|
||||
|
||||
void benchWorstCaseForRadixRangeRead() {
|
||||
ankerl::nanobench::Bench bench;
|
||||
|
||||
std::unique_ptr<ConflictSet> cs[256];
|
||||
for (int i = 0; i < 256; ++i) {
|
||||
cs[i] =
|
||||
std::make_unique<ConflictSet>(worstCaseConflictSetForRadixRangeRead(i));
|
||||
}
|
||||
|
||||
auto begin = std::vector<uint8_t>(kKeyLenForWorstCase - 1, 0);
|
||||
begin.push_back(1);
|
||||
auto end = std::vector<uint8_t>(kKeyLenForWorstCase - 1, 255);
|
||||
end.push_back(254);
|
||||
|
||||
weaselab::ConflictSet::Result result;
|
||||
weaselab::ConflictSet::ReadRange r{
|
||||
{begin.data(), int(begin.size())}, {end.data(), int(end.size())}, 0};
|
||||
|
||||
bench.run("worst case for radix tree", [&]() {
|
||||
for (int i = 0; i < 256; ++i) {
|
||||
result = weaselab::ConflictSet::TooOld;
|
||||
cs[i]->check(&r, &result, 1);
|
||||
if (result != weaselab::ConflictSet::Commit) {
|
||||
abort();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// for (int i = 0; i < 256; ++i) {
|
||||
// bench.run("worst case for radix tree, span " + std::to_string(i), [&]() {
|
||||
// result = weaselab::ConflictSet::TooOld;
|
||||
// cs[i]->check(&r, &result, 1);
|
||||
// if (result != weaselab::ConflictSet::Commit) {
|
||||
// abort();
|
||||
// }
|
||||
// });
|
||||
// }
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
benchConflictSet();
|
||||
benchWorstCaseForRadixRangeRead();
|
||||
}
|
||||
|
330
CMakeLists.txt
330
CMakeLists.txt
@@ -1,15 +1,30 @@
|
||||
cmake_minimum_required(VERSION 3.18)
|
||||
project(
|
||||
conflict_set
|
||||
VERSION 0.0.1
|
||||
conflict-set
|
||||
VERSION 0.0.8
|
||||
DESCRIPTION
|
||||
"A data structure for optimistic concurrency control on ranges of bitwise-lexicographically-ordered keys."
|
||||
HOMEPAGE_URL "https://git.weaselab.dev/weaselab/conflict-set"
|
||||
LANGUAGES C CXX)
|
||||
set(CMAKE_CXX_STANDARD 20)
|
||||
|
||||
file(WRITE ${CMAKE_CURRENT_BINARY_DIR}/version.txt ${PROJECT_VERSION})
|
||||
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/version.txt.in
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/paper/version.txt)
|
||||
|
||||
include(CMakePushCheckState)
|
||||
include(CheckCXXCompilerFlag)
|
||||
include(CheckIncludeFileCXX)
|
||||
include(CheckCXXSourceCompiles)
|
||||
|
||||
set(DEFAULT_BUILD_TYPE "Release")
|
||||
|
||||
if(EMSCRIPTEN OR CMAKE_SYSTEM_NAME STREQUAL WASI)
|
||||
set(WASM ON)
|
||||
else()
|
||||
set(WASM OFF)
|
||||
endif()
|
||||
|
||||
if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
|
||||
message(
|
||||
STATUS
|
||||
@@ -22,16 +37,34 @@ if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
|
||||
"MinSizeRel" "RelWithDebInfo")
|
||||
endif()
|
||||
|
||||
add_compile_options(-fdata-sections -ffunction-sections -fstrict-enums)
|
||||
add_compile_options(-fdata-sections -ffunction-sections -Wswitch-enum
|
||||
-Werror=switch-enum -fPIC)
|
||||
|
||||
set(full_relro_flags "-pie;LINKER:-z,relro,-z,now,-z,noexecstack")
|
||||
cmake_push_check_state()
|
||||
list(APPEND CMAKE_REQUIRED_LINK_OPTIONS ${full_relro_flags})
|
||||
check_cxx_source_compiles("int main(){}" HAS_FULL_RELRO FAIL_REGEX "warning:")
|
||||
if(HAS_FULL_RELRO)
|
||||
add_link_options(${full_relro_flags})
|
||||
endif()
|
||||
cmake_pop_check_state()
|
||||
|
||||
set(version_script_flags
|
||||
LINKER:--version-script=${CMAKE_CURRENT_SOURCE_DIR}/linker.map)
|
||||
cmake_push_check_state()
|
||||
list(APPEND CMAKE_REQUIRED_LINK_OPTIONS ${version_script_flags})
|
||||
check_cxx_source_compiles("int main(){}" HAS_VERSION_SCRIPT FAIL_REGEX
|
||||
"warning:")
|
||||
cmake_pop_check_state()
|
||||
|
||||
option(USE_SIMD_FALLBACK
|
||||
"Use fallback implementations of functions that use SIMD" OFF)
|
||||
|
||||
# This is encouraged according to
|
||||
# https://valgrind.org/docs/manual/manual-core-adv.html#manual-core-adv.clientreq
|
||||
include_directories(SYSTEM ${CMAKE_SOURCE_DIR}/third_party/valgrind)
|
||||
include_directories(SYSTEM ${CMAKE_CURRENT_SOURCE_DIR}/third_party/valgrind)
|
||||
|
||||
if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
|
||||
add_compile_options(-Wno-maybe-uninitialized
|
||||
$<$<COMPILE_LANGUAGE:CXX>:-Wno-invalid-offsetof>)
|
||||
endif()
|
||||
add_compile_options($<$<COMPILE_LANGUAGE:CXX>:-Wno-invalid-offsetof>)
|
||||
|
||||
if(APPLE)
|
||||
add_link_options(-Wl,-dead_strip)
|
||||
@@ -39,89 +72,137 @@ else()
|
||||
add_link_options(-Wl,--gc-sections)
|
||||
endif()
|
||||
|
||||
include(CheckIncludeFileCXX)
|
||||
include(CMakePushCheckState)
|
||||
|
||||
cmake_push_check_state()
|
||||
list(APPEND CMAKE_REQUIRED_FLAGS -mavx)
|
||||
check_include_file_cxx("immintrin.h" HAS_AVX)
|
||||
if(HAS_AVX)
|
||||
add_compile_options(-mavx)
|
||||
add_compile_definitions(HAS_AVX)
|
||||
if(EMSCRIPTEN)
|
||||
# https://github.com/emscripten-core/emscripten/issues/15377#issuecomment-1285167486
|
||||
add_link_options(-lnodefs.js -lnoderawfs.js)
|
||||
add_link_options(-s ALLOW_MEMORY_GROWTH)
|
||||
endif()
|
||||
cmake_pop_check_state()
|
||||
|
||||
check_include_file_cxx("arm_neon.h" HAS_ARM_NEON)
|
||||
if(HAS_ARM_NEON)
|
||||
add_compile_definitions(HAS_ARM_NEON)
|
||||
if(NOT USE_SIMD_FALLBACK)
|
||||
cmake_push_check_state()
|
||||
list(APPEND CMAKE_REQUIRED_FLAGS -msimd128)
|
||||
check_include_file_cxx("wasm_simd128.h" HAS_WASM_SIMD)
|
||||
if(HAS_WASM_SIMD)
|
||||
add_compile_options(-msimd128)
|
||||
add_compile_definitions(HAS_WASM_SIMD)
|
||||
endif()
|
||||
cmake_pop_check_state()
|
||||
|
||||
cmake_push_check_state()
|
||||
list(APPEND CMAKE_REQUIRED_FLAGS -mavx)
|
||||
check_include_file_cxx("immintrin.h" HAS_AVX)
|
||||
if(HAS_AVX)
|
||||
add_compile_options(-mavx)
|
||||
add_compile_definitions(HAS_AVX)
|
||||
endif()
|
||||
cmake_pop_check_state()
|
||||
|
||||
check_include_file_cxx("arm_neon.h" HAS_ARM_NEON)
|
||||
if(HAS_ARM_NEON)
|
||||
add_compile_definitions(HAS_ARM_NEON)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
set(CMAKE_CXX_IMPLICIT_LINK_LIBRARIES "")
|
||||
|
||||
add_library(${PROJECT_NAME}_object OBJECT ConflictSet.cpp)
|
||||
target_compile_options(${PROJECT_NAME}_object PRIVATE -fPIC -fno-exceptions
|
||||
add_library(${PROJECT_NAME}-object OBJECT ConflictSet.cpp)
|
||||
target_compile_options(${PROJECT_NAME}-object PRIVATE -fno-exceptions
|
||||
-fvisibility=hidden)
|
||||
target_include_directories(${PROJECT_NAME}_object
|
||||
PRIVATE ${CMAKE_SOURCE_DIR}/include)
|
||||
target_include_directories(${PROJECT_NAME}-object
|
||||
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(
|
||||
${PROJECT_NAME} PROPERTIES LIBRARY_OUTPUT_DIRECTORY
|
||||
"${CMAKE_BINARY_DIR}/radix_tree")
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/radix_tree")
|
||||
if(NOT CMAKE_BUILD_TYPE STREQUAL Debug)
|
||||
set_target_properties(${PROJECT_NAME} PROPERTIES LINKER_LANGUAGE C)
|
||||
endif()
|
||||
|
||||
if(NOT APPLE)
|
||||
target_link_options(${PROJECT_NAME} PRIVATE
|
||||
LINKER:--version-script=${CMAKE_SOURCE_DIR}/linker.map)
|
||||
if(HAS_VERSION_SCRIPT)
|
||||
target_link_options(
|
||||
${PROJECT_NAME} PRIVATE
|
||||
LINKER:--version-script=${CMAKE_CURRENT_SOURCE_DIR}/linker.map)
|
||||
endif()
|
||||
|
||||
add_library(${PROJECT_NAME}_static STATIC
|
||||
$<TARGET_OBJECTS:${PROJECT_NAME}_object>)
|
||||
add_library(${PROJECT_NAME}-static STATIC
|
||||
$<TARGET_OBJECTS:${PROJECT_NAME}-object>)
|
||||
if(NOT CMAKE_BUILD_TYPE STREQUAL Debug)
|
||||
set_target_properties(${PROJECT_NAME}_static PROPERTIES LINKER_LANGUAGE C)
|
||||
set_target_properties(${PROJECT_NAME}-static PROPERTIES LINKER_LANGUAGE C)
|
||||
endif()
|
||||
|
||||
if(NOT APPLE AND CMAKE_OBJCOPY)
|
||||
if(APPLE)
|
||||
add_custom_command(
|
||||
TARGET conflict_set_static
|
||||
TARGET ${PROJECT_NAME}-static
|
||||
PRE_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
|
||||
COMMAND
|
||||
${CMAKE_OBJCOPY} --keep-global-symbols=${CMAKE_SOURCE_DIR}/symbols.txt
|
||||
$<TARGET_FILE:${PROJECT_NAME}_static>)
|
||||
${CMAKE_OBJCOPY}
|
||||
--keep-global-symbols=${CMAKE_CURRENT_SOURCE_DIR}/symbol-exports.txt
|
||||
$<TARGET_FILE:${PROJECT_NAME}-static> || echo
|
||||
"Proceeding with all symbols global in static library")
|
||||
endif()
|
||||
|
||||
set(TEST_FLAGS -Wall -Wextra -Wunreachable-code -Wpedantic -UNDEBUG)
|
||||
|
||||
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
|
||||
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})
|
||||
set(TEST_FLAGS -Wall -Wextra -Wunreachable-code -Wpedantic -UNDEBUG)
|
||||
|
||||
# Shared library version of a std::unordered_map-based conflict set (point
|
||||
# queries only)
|
||||
add_library(hash_table SHARED HashTable.cpp)
|
||||
target_compile_options(hash_table PRIVATE -fPIC -fno-exceptions
|
||||
-fvisibility=hidden)
|
||||
target_include_directories(hash_table PUBLIC ${CMAKE_SOURCE_DIR}/include)
|
||||
set_target_properties(hash_table PROPERTIES LIBRARY_OUTPUT_DIRECTORY
|
||||
"${CMAKE_BINARY_DIR}/hash_table")
|
||||
set_target_properties(hash_table PROPERTIES OUTPUT_NAME ${PROJECT_NAME})
|
||||
set_target_properties(
|
||||
hash_table PROPERTIES VERSION ${PROJECT_VERSION} SOVERSION
|
||||
${PROJECT_VERSION_MAJOR})
|
||||
# corpus tests, which are tests curated by libfuzzer. The goal is to get broad
|
||||
# coverage with a small number of tests.
|
||||
|
||||
file(GLOB CORPUS_TESTS ${CMAKE_CURRENT_SOURCE_DIR}/corpus/*)
|
||||
|
||||
# extra testing that relies on shared libraries, which aren't available with
|
||||
# wasm
|
||||
if(NOT WASM)
|
||||
# Shared library version of FoundationDB's skip list implementation
|
||||
add_library(skip_list SHARED SkipList.cpp)
|
||||
target_compile_options(skip_list PRIVATE -fno-exceptions
|
||||
-fvisibility=hidden)
|
||||
target_include_directories(skip_list
|
||||
PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include)
|
||||
set_target_properties(
|
||||
skip_list PROPERTIES LIBRARY_OUTPUT_DIRECTORY
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/skip_list")
|
||||
set_target_properties(skip_list PROPERTIES OUTPUT_NAME ${PROJECT_NAME})
|
||||
set_target_properties(
|
||||
skip_list PROPERTIES VERSION ${PROJECT_VERSION} SOVERSION
|
||||
${PROJECT_VERSION_MAJOR})
|
||||
|
||||
# Shared library version of a std::unordered_map-based conflict set (point
|
||||
# queries only)
|
||||
add_library(hash_table SHARED HashTable.cpp)
|
||||
target_compile_options(hash_table PRIVATE -fno-exceptions
|
||||
-fvisibility=hidden)
|
||||
target_include_directories(hash_table
|
||||
PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include)
|
||||
set_target_properties(
|
||||
hash_table PROPERTIES LIBRARY_OUTPUT_DIRECTORY
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/hash_table")
|
||||
set_target_properties(hash_table PROPERTIES OUTPUT_NAME ${PROJECT_NAME})
|
||||
set_target_properties(
|
||||
hash_table PROPERTIES VERSION ${PROJECT_VERSION} SOVERSION
|
||||
${PROJECT_VERSION_MAJOR})
|
||||
|
||||
add_executable(driver_skip_list TestDriver.cpp)
|
||||
target_compile_options(driver_skip_list PRIVATE ${TEST_FLAGS})
|
||||
target_link_libraries(driver_skip_list PRIVATE skip_list)
|
||||
|
||||
foreach(TEST ${CORPUS_TESTS})
|
||||
get_filename_component(hash ${TEST} NAME)
|
||||
add_test(NAME skip_list_${hash} COMMAND driver_skip_list ${TEST})
|
||||
endforeach()
|
||||
endif()
|
||||
|
||||
# ad hoc testing
|
||||
add_executable(conflict_set_main ConflictSet.cpp)
|
||||
target_include_directories(conflict_set_main
|
||||
PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include)
|
||||
@@ -148,12 +229,13 @@ if(BUILD_TESTING)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# corpus tests
|
||||
|
||||
file(GLOB CORPUS_TESTS ${CMAKE_SOURCE_DIR}/corpus/*)
|
||||
|
||||
# whitebox tests
|
||||
add_executable(fuzz_driver ConflictSet.cpp FuzzTestDriver.cpp)
|
||||
target_compile_options(fuzz_driver PRIVATE ${TEST_FLAGS})
|
||||
if(NOT CMAKE_CROSSCOMPILING)
|
||||
target_compile_options(fuzz_driver PRIVATE -fsanitize=address,undefined)
|
||||
target_link_options(fuzz_driver PRIVATE -fsanitize=address,undefined)
|
||||
endif()
|
||||
target_compile_definitions(fuzz_driver PRIVATE ENABLE_FUZZ)
|
||||
target_include_directories(fuzz_driver
|
||||
PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include)
|
||||
@@ -162,13 +244,51 @@ if(BUILD_TESTING)
|
||||
add_test(NAME conflict_set_fuzz_${hash} COMMAND fuzz_driver ${TEST})
|
||||
endforeach()
|
||||
|
||||
# tsan tests
|
||||
if(NOT CMAKE_CROSSCOMPILING AND NOT CMAKE_BUILD_TYPE STREQUAL Debug)
|
||||
add_executable(tsan_driver ConflictSet.cpp FuzzTestDriver.cpp)
|
||||
target_compile_options(tsan_driver PRIVATE ${TEST_FLAGS} -fsanitize=thread)
|
||||
target_link_options(tsan_driver PRIVATE -fsanitize=thread)
|
||||
target_compile_definitions(tsan_driver PRIVATE ENABLE_FUZZ THREAD_TEST)
|
||||
target_include_directories(tsan_driver
|
||||
PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include)
|
||||
foreach(TEST ${CORPUS_TESTS})
|
||||
get_filename_component(hash ${TEST} NAME)
|
||||
add_test(NAME conflict_set_tsan_${hash} COMMAND tsan_driver ${TEST})
|
||||
endforeach()
|
||||
endif()
|
||||
|
||||
# blackbox tests
|
||||
add_executable(driver TestDriver.cpp)
|
||||
target_compile_options(driver PRIVATE ${TEST_FLAGS})
|
||||
target_link_libraries(driver PRIVATE ${PROJECT_NAME})
|
||||
foreach(TEST ${CORPUS_TESTS})
|
||||
get_filename_component(hash ${TEST} NAME)
|
||||
add_test(NAME conflict_set_blackbox_${hash} COMMAND driver ${TEST})
|
||||
endforeach()
|
||||
|
||||
add_executable(driver_skip_list TestDriver.cpp)
|
||||
target_compile_options(driver_skip_list PRIVATE ${TEST_FLAGS})
|
||||
target_link_libraries(driver_skip_list PRIVATE skip_list)
|
||||
# scripted tests. Written manually to fill in anything libfuzzer couldn't
|
||||
# find.
|
||||
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)
|
||||
if(VALGRIND_EXE AND NOT CMAKE_CROSSCOMPILING)
|
||||
@@ -177,12 +297,6 @@ if(BUILD_TESTING)
|
||||
$<TARGET_FILE:driver> ${CORPUS_TESTS})
|
||||
endif()
|
||||
|
||||
foreach(TEST ${CORPUS_TESTS})
|
||||
get_filename_component(hash ${TEST} NAME)
|
||||
add_test(NAME conflict_set_blackbox_${hash} COMMAND driver ${TEST})
|
||||
add_test(NAME skip_list_${hash} COMMAND driver_skip_list ${TEST})
|
||||
endforeach()
|
||||
|
||||
# api smoke tests
|
||||
|
||||
# c90
|
||||
@@ -203,31 +317,46 @@ if(BUILD_TESTING)
|
||||
PROPERTIES CXX_STANDARD_REQUIRED ON)
|
||||
add_test(NAME conflict_set_cxx_api_test COMMAND conflict_set_cxx_api_test)
|
||||
|
||||
if(NOT APPLE AND NOT CMAKE_BUILD_TYPE STREQUAL Debug)
|
||||
# symbol visibility tests
|
||||
if(NOT WASM AND NOT CMAKE_BUILD_TYPE STREQUAL Debug)
|
||||
if(APPLE)
|
||||
set(symbol_exports ${CMAKE_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(
|
||||
NAME conflict_set_shared_symbols
|
||||
COMMAND ${CMAKE_SOURCE_DIR}/test_symbols.sh
|
||||
$<TARGET_FILE:${PROJECT_NAME}> ${CMAKE_SOURCE_DIR}/symbols.txt)
|
||||
COMMAND
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/test_symbols.sh
|
||||
$<TARGET_FILE:${PROJECT_NAME}> ${symbol_exports} ${symbol_imports})
|
||||
add_test(
|
||||
NAME conflict_set_static_symbols
|
||||
COMMAND
|
||||
${CMAKE_SOURCE_DIR}/test_symbols.sh
|
||||
$<TARGET_FILE:${PROJECT_NAME}_static> ${CMAKE_SOURCE_DIR}/symbols.txt)
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/test_symbols.sh
|
||||
$<TARGET_FILE:${PROJECT_NAME}-static> ${symbol_exports}
|
||||
${symbol_imports})
|
||||
endif()
|
||||
|
||||
# bench
|
||||
|
||||
add_executable(conflict_set_bench Bench.cpp)
|
||||
target_link_libraries(conflict_set_bench PRIVATE ${PROJECT_NAME})
|
||||
set_target_properties(conflict_set_bench PROPERTIES SKIP_BUILD_RPATH ON)
|
||||
|
||||
add_executable(real_data_bench RealDataBench.cpp)
|
||||
target_link_libraries(real_data_bench PRIVATE ${PROJECT_NAME})
|
||||
set_target_properties(real_data_bench PROPERTIES SKIP_BUILD_RPATH ON)
|
||||
endif()
|
||||
|
||||
# packaging
|
||||
|
||||
set(CPACK_PACKAGE_CONTACT andrew@weaselab.dev)
|
||||
set(CMAKE_INSTALL_DEFAULT_COMPONENT_NAME all)
|
||||
|
||||
set(CPACK_PACKAGE_VENDOR "Weaselab")
|
||||
set(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_CURRENT_SOURCE_DIR}/LICENSE")
|
||||
@@ -237,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_SPEC_INSTALL_POST "/bin/true") # avoid stripping
|
||||
set(CPACK_RPM_PACKAGE_LICENSE "Apache 2.0")
|
||||
set(CPACK_RPM_FILE_NAME RPM-DEFAULT)
|
||||
|
||||
# deb
|
||||
set(CPACK_DEBIAN_FILE_NAME DEB-DEFAULT)
|
||||
# see *-imports.txt - dependency versions need to be synced with symbol versions
|
||||
if(CMAKE_SYSTEM_PROCESSOR STREQUAL aarch64)
|
||||
set(CPACK_DEBIAN_PACKAGE_DEPENDS "libc6 (>= 2.17)")
|
||||
else()
|
||||
set(CPACK_DEBIAN_PACKAGE_DEPENDS "libc6 (>= 2.14)")
|
||||
endif()
|
||||
|
||||
# macos
|
||||
set(CMAKE_OSX_DEPLOYMENT_TARGET 11.0)
|
||||
if(APPLE)
|
||||
find_program(PANDOC_EXE pandoc)
|
||||
if(PANDOC_EXE)
|
||||
execute_process(COMMAND ${PANDOC_EXE} ${CMAKE_CURRENT_SOURCE_DIR}/README.md
|
||||
-o ${CMAKE_CURRENT_BINARY_DIR}/README.txt)
|
||||
set(CPACK_RESOURCE_FILE_README ${CMAKE_CURRENT_BINARY_DIR}/README.txt)
|
||||
endif()
|
||||
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/LICENSE
|
||||
${CMAKE_CURRENT_BINARY_DIR}/LICENSE.txt COPYONLY)
|
||||
set(CPACK_RESOURCE_FILE_LICENSE ${CMAKE_CURRENT_BINARY_DIR}/LICENSE.txt)
|
||||
endif()
|
||||
|
||||
include(CPack)
|
||||
|
||||
@@ -248,7 +401,7 @@ target_include_directories(
|
||||
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_NAME}>)
|
||||
|
||||
target_include_directories(
|
||||
${PROJECT_NAME}_static
|
||||
${PROJECT_NAME}-static
|
||||
PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
|
||||
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_NAME}>)
|
||||
|
||||
@@ -257,13 +410,16 @@ set_target_properties(
|
||||
SOVERSION ${PROJECT_VERSION_MAJOR})
|
||||
|
||||
install(
|
||||
TARGETS ${PROJECT_NAME} ${PROJECT_NAME}_static
|
||||
EXPORT ConflictSetConfig
|
||||
TARGETS ${PROJECT_NAME} ${PROJECT_NAME}-static
|
||||
EXPORT ${PROJECT_NAME}Config
|
||||
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
|
||||
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
|
||||
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
|
||||
|
||||
install(DIRECTORY include/
|
||||
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_NAME})
|
||||
|
||||
install(EXPORT ConflictSetConfig
|
||||
DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/ConflictSet/cmake)
|
||||
install(EXPORT ${PROJECT_NAME}Config
|
||||
DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/${PROJECT_NAME}/cmake)
|
||||
|
||||
cpack_add_component(all)
|
||||
|
2960
ConflictSet.cpp
2960
ConflictSet.cpp
File diff suppressed because it is too large
Load Diff
@@ -2,15 +2,25 @@
|
||||
#include <cstdint>
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
#include <thread>
|
||||
|
||||
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size);
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
for (int i = 1; i < argc; ++i) {
|
||||
std::ifstream t(argv[i], std::ios::binary);
|
||||
std::stringstream buffer;
|
||||
buffer << t.rdbuf();
|
||||
auto str = buffer.str();
|
||||
LLVMFuzzerTestOneInput((const uint8_t *)str.data(), str.size());
|
||||
}
|
||||
auto doTest = [&]() {
|
||||
for (int i = 1; i < argc; ++i) {
|
||||
std::ifstream t(argv[i], std::ios::binary);
|
||||
std::stringstream buffer;
|
||||
buffer << t.rdbuf();
|
||||
auto str = buffer.str();
|
||||
LLVMFuzzerTestOneInput((const uint8_t *)str.data(), str.size());
|
||||
}
|
||||
};
|
||||
#ifdef THREAD_TEST
|
||||
std::thread thread2{doTest};
|
||||
#endif
|
||||
doTest();
|
||||
#ifdef THREAD_TEST
|
||||
thread2.join();
|
||||
#endif
|
||||
}
|
||||
|
@@ -96,13 +96,15 @@ void ConflictSet::setOldestVersion(int64_t oldestVersion) {
|
||||
return impl->setOldestVersion(oldestVersion);
|
||||
}
|
||||
|
||||
int64_t ConflictSet::getBytes() const { return -1; }
|
||||
|
||||
ConflictSet::ConflictSet(int64_t oldestVersion)
|
||||
: impl(new (safe_malloc(sizeof(Impl))) Impl{oldestVersion}) {}
|
||||
: impl(new(safe_malloc(sizeof(Impl))) Impl{oldestVersion}) {}
|
||||
|
||||
ConflictSet::~ConflictSet() {
|
||||
if (impl) {
|
||||
impl->~Impl();
|
||||
free(impl);
|
||||
safe_free(impl, sizeof(Impl));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -142,6 +144,11 @@ ConflictSet_create(int64_t oldestVersion) {
|
||||
__attribute__((__visibility__("default"))) void ConflictSet_destroy(void *cs) {
|
||||
using Impl = ConflictSet::Impl;
|
||||
((Impl *)cs)->~Impl();
|
||||
free(cs);
|
||||
safe_free(cs, sizeof(Impl));
|
||||
}
|
||||
__attribute__((__visibility__("default"))) int64_t
|
||||
ConflictSet_getBytes(void *cs) {
|
||||
using Impl = ConflictSet::Impl;
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
359
Internal.h
359
Internal.h
@@ -2,6 +2,8 @@
|
||||
|
||||
#include "ConflictSet.h"
|
||||
|
||||
using namespace weaselab;
|
||||
|
||||
#include <bit>
|
||||
#include <cassert>
|
||||
#include <compare>
|
||||
@@ -10,10 +12,12 @@
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <inttypes.h>
|
||||
#include <latch>
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <span>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <unordered_set>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
@@ -35,16 +39,74 @@ operator<=>(const std::span<const uint8_t> &lhs,
|
||||
return lhs.size() <=> rhs.size();
|
||||
}
|
||||
|
||||
[[nodiscard]] inline auto operator<=>(const std::span<const uint8_t> &lhs,
|
||||
const ConflictSet::Key &rhs) noexcept {
|
||||
int cl = std::min<int>(lhs.size(), rhs.len);
|
||||
if (cl > 0) {
|
||||
if (auto c = memcmp(lhs.data(), rhs.p, cl) <=> 0; c != 0) {
|
||||
return c;
|
||||
}
|
||||
}
|
||||
return lhs.size() <=> size_t(rhs.len);
|
||||
}
|
||||
|
||||
// This header contains code that we want to reuse outside of ConflictSet.cpp or
|
||||
// want to exclude from coverage since it's only testing related.
|
||||
|
||||
// GCOVR_EXCL_START
|
||||
|
||||
#if SHOW_MEMORY
|
||||
inline int64_t mallocBytes = 0;
|
||||
inline int64_t peakMallocBytes = 0;
|
||||
#endif
|
||||
|
||||
inline thread_local int64_t mallocBytesDelta = 0;
|
||||
|
||||
#ifndef NDEBUG
|
||||
constexpr auto kMallocHeaderSize = 16;
|
||||
#endif
|
||||
|
||||
// malloc that aborts on OOM and thus always returns a non-null pointer. Must be
|
||||
// paired with `safe_free`.
|
||||
__attribute__((always_inline)) inline void *safe_malloc(size_t s) {
|
||||
if (void *p = malloc(s)) {
|
||||
return p;
|
||||
mallocBytesDelta += s;
|
||||
#if SHOW_MEMORY
|
||||
mallocBytes += s;
|
||||
if (mallocBytes > peakMallocBytes) {
|
||||
peakMallocBytes = mallocBytes;
|
||||
}
|
||||
abort();
|
||||
#endif
|
||||
void *p = malloc(s
|
||||
#ifndef NDEBUG
|
||||
+ kMallocHeaderSize
|
||||
#endif
|
||||
);
|
||||
if (p == nullptr) {
|
||||
abort();
|
||||
}
|
||||
#ifndef NDEBUG
|
||||
memcpy(p, &s, sizeof(s));
|
||||
(char *&)p += kMallocHeaderSize;
|
||||
#endif
|
||||
return p;
|
||||
}
|
||||
|
||||
// Must be paired with `safe_malloc`.
|
||||
//
|
||||
// There's nothing safer about this than free. Only called safe_free for
|
||||
// symmetry with safe_malloc.
|
||||
__attribute__((always_inline)) inline void safe_free(void *p, size_t s) {
|
||||
mallocBytesDelta -= s;
|
||||
#if SHOW_MEMORY
|
||||
mallocBytes -= s;
|
||||
#endif
|
||||
#ifndef NDEBUG
|
||||
(char *&)p -= kMallocHeaderSize;
|
||||
size_t expected;
|
||||
memcpy(&expected, p, sizeof(expected));
|
||||
assert(s == expected);
|
||||
#endif
|
||||
free(p);
|
||||
}
|
||||
|
||||
// ==================== BEGIN ARENA IMPL ====================
|
||||
@@ -90,6 +152,7 @@ inline void *operator new[](size_t size, std::align_val_t align,
|
||||
|
||||
/// align must be a power of two
|
||||
template <class T> T *align_up(T *t, size_t align) {
|
||||
assert(std::popcount(align) == 1);
|
||||
auto unaligned = uintptr_t(t);
|
||||
auto aligned = (unaligned + align - 1) & ~(align - 1);
|
||||
return reinterpret_cast<T *>(reinterpret_cast<char *>(t) + aligned -
|
||||
@@ -98,6 +161,7 @@ template <class T> T *align_up(T *t, size_t align) {
|
||||
|
||||
/// align must be a power of two
|
||||
constexpr inline int align_up(uint32_t unaligned, uint32_t align) {
|
||||
assert(std::popcount(align) == 1);
|
||||
return (unaligned + align - 1) & ~(align - 1);
|
||||
}
|
||||
|
||||
@@ -113,12 +177,10 @@ struct Arena::ArenaImpl {
|
||||
uint8_t *begin() { return reinterpret_cast<uint8_t *>(this + 1); }
|
||||
};
|
||||
|
||||
static_assert(sizeof(Arena::ArenaImpl) == 16);
|
||||
static_assert(alignof(Arena::ArenaImpl) == 8);
|
||||
|
||||
inline Arena::Arena(int initialSize) : impl(nullptr) {
|
||||
if (initialSize > 0) {
|
||||
auto allocationSize = align_up(initialSize + sizeof(ArenaImpl), 16);
|
||||
auto allocationSize =
|
||||
align_up(initialSize + sizeof(ArenaImpl), alignof(ArenaImpl));
|
||||
impl = (Arena::ArenaImpl *)safe_malloc(allocationSize);
|
||||
impl->prev = nullptr;
|
||||
impl->capacity = allocationSize - sizeof(ArenaImpl);
|
||||
@@ -129,7 +191,7 @@ inline Arena::Arena(int initialSize) : impl(nullptr) {
|
||||
inline void onDestroy(Arena::ArenaImpl *impl) {
|
||||
while (impl) {
|
||||
auto *prev = impl->prev;
|
||||
free(impl);
|
||||
safe_free(impl, sizeof(Arena::ArenaImpl) + impl->capacity);
|
||||
impl = prev;
|
||||
}
|
||||
}
|
||||
@@ -154,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->capacity * 2)
|
||||
: 0)),
|
||||
16);
|
||||
alignof(Arena::ArenaImpl));
|
||||
auto *impl = (Arena::ArenaImpl *)safe_malloc(allocationSize);
|
||||
impl->prev = arena.impl;
|
||||
impl->capacity = allocationSize - sizeof(Arena::ArenaImpl);
|
||||
@@ -193,10 +255,65 @@ template <class T> struct ArenaAlloc {
|
||||
void deallocate(T *, size_t) noexcept {}
|
||||
};
|
||||
|
||||
template <class T> using Vector = std::vector<T, ArenaAlloc<T>>;
|
||||
template <class T> auto vector(Arena &arena) {
|
||||
return Vector<T>(ArenaAlloc<T>(&arena));
|
||||
}
|
||||
template <class T> struct Vector {
|
||||
static_assert(std::is_trivially_destructible_v<T>);
|
||||
static_assert(std::is_trivially_copyable_v<T>);
|
||||
|
||||
explicit Vector(Arena *arena)
|
||||
: arena(arena), t(nullptr), size_(0), capacity(0) {}
|
||||
|
||||
void append(std::span<const T> slice) {
|
||||
if (size_ + int(slice.size()) > capacity) {
|
||||
grow(std::max<int>(size_ + slice.size(), capacity * 2));
|
||||
}
|
||||
if (slice.size() > 0) {
|
||||
memcpy(const_cast<std::remove_const_t<T> *>(t) + size_, slice.data(),
|
||||
slice.size() * sizeof(T));
|
||||
}
|
||||
size_ += slice.size();
|
||||
}
|
||||
|
||||
void push_back(const T &t) { append(std::span<const T>(&t, 1)); }
|
||||
|
||||
T *begin() { return t; }
|
||||
T *end() { return t + size_; }
|
||||
T *data() { return t; }
|
||||
T &back() {
|
||||
assert(size_ > 0);
|
||||
return t[size_ - 1];
|
||||
}
|
||||
T &operator[](int i) {
|
||||
assert(i >= 0 && i < size_);
|
||||
return t[i];
|
||||
}
|
||||
|
||||
void pop_back() {
|
||||
assert(size_ > 0);
|
||||
--size_;
|
||||
}
|
||||
|
||||
int size() const { return size_; }
|
||||
|
||||
operator std::span<const T>() const { return std::span(t, size_); }
|
||||
|
||||
private:
|
||||
void grow(int newCapacity) {
|
||||
capacity = newCapacity;
|
||||
auto old = std::span<const T>(*this);
|
||||
t = (T *)new (std::align_val_t(alignof(T)), *arena)
|
||||
uint8_t[capacity * sizeof(T)];
|
||||
size_ = 0;
|
||||
append(old);
|
||||
}
|
||||
|
||||
Arena *arena;
|
||||
T *t;
|
||||
int size_;
|
||||
int capacity;
|
||||
};
|
||||
|
||||
template <class T> auto vector(Arena &arena) { return Vector<T>(&arena); }
|
||||
|
||||
template <class T, class C> using Set = std::set<T, C, ArenaAlloc<T>>;
|
||||
template <class T, class C = std::less<T>> auto set(Arena &arena) {
|
||||
return Set<T, C>(ArenaAlloc<T>(&arena));
|
||||
@@ -349,42 +466,16 @@ inline uint32_t Arbitrary::bounded(uint32_t s) {
|
||||
|
||||
// ==================== END ARBITRARY IMPL ====================
|
||||
|
||||
// ==================== BEGIN UTILITIES IMPL ====================
|
||||
|
||||
// Call Stepwise::step for each element of remaining until it returns true.
|
||||
// Applies a permutation to `remaining` as a side effect.
|
||||
template <class Stepwise> void runInterleaved(std::span<Stepwise> remaining) {
|
||||
while (remaining.size() > 0) {
|
||||
for (int i = 0; i < int(remaining.size());) {
|
||||
bool done = remaining[i].step();
|
||||
if (done) {
|
||||
if (i != int(remaining.size()) - 1) {
|
||||
using std::swap;
|
||||
swap(remaining[i], remaining.back());
|
||||
}
|
||||
remaining = remaining.subspan(0, remaining.size() - 1);
|
||||
} else {
|
||||
++i;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template <class Stepwise> void runSequential(std::span<Stepwise> remaining) {
|
||||
for (auto &r : remaining) {
|
||||
while (!r.step()) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct ReferenceImpl {
|
||||
explicit ReferenceImpl(int64_t oldestVersion) : oldestVersion(oldestVersion) {
|
||||
explicit ReferenceImpl(int64_t oldestVersion)
|
||||
: oldestVersion(oldestVersion), newestVersion(oldestVersion) {
|
||||
writeVersionMap[""] = oldestVersion;
|
||||
}
|
||||
void check(const ConflictSet::ReadRange *reads, ConflictSet::Result *results,
|
||||
int count) const {
|
||||
for (int i = 0; i < count; ++i) {
|
||||
if (reads[i].readVersion < oldestVersion) {
|
||||
if (reads[i].readVersion < oldestVersion ||
|
||||
reads[i].readVersion < newestVersion - 2e9) {
|
||||
results[i] = ConflictSet::TooOld;
|
||||
continue;
|
||||
}
|
||||
@@ -406,6 +497,8 @@ struct ReferenceImpl {
|
||||
}
|
||||
void addWrites(const ConflictSet::WriteRange *writes, int count,
|
||||
int64_t writeVersion) {
|
||||
assert(writeVersion >= newestVersion);
|
||||
newestVersion = writeVersion;
|
||||
for (int i = 0; i < count; ++i) {
|
||||
auto begin =
|
||||
std::string((const char *)writes[i].begin.p, writes[i].begin.len);
|
||||
@@ -425,11 +518,12 @@ struct ReferenceImpl {
|
||||
}
|
||||
|
||||
void setOldestVersion(int64_t oldestVersion) {
|
||||
assert(oldestVersion >= oldestVersion);
|
||||
assert(oldestVersion >= this->oldestVersion);
|
||||
this->oldestVersion = oldestVersion;
|
||||
}
|
||||
|
||||
int64_t oldestVersion;
|
||||
int64_t newestVersion;
|
||||
std::map<std::string, int64_t> writeVersionMap;
|
||||
};
|
||||
|
||||
@@ -470,6 +564,18 @@ inline std::string printable(std::span<const uint8_t> key) {
|
||||
return printable(std::string_view((const char *)key.data(), key.size()));
|
||||
}
|
||||
|
||||
inline const char *resultToStr(ConflictSet::Result r) {
|
||||
switch (r) {
|
||||
case ConflictSet::Commit:
|
||||
return "commit";
|
||||
case ConflictSet::Conflict:
|
||||
return "conflict";
|
||||
case ConflictSet::TooOld:
|
||||
return "too old";
|
||||
}
|
||||
abort();
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
template <class ConflictSetImpl> struct TestDriver {
|
||||
@@ -477,30 +583,22 @@ template <class ConflictSetImpl> struct TestDriver {
|
||||
explicit TestDriver(const uint8_t *data, size_t size)
|
||||
: arbitrary({data, size}) {}
|
||||
|
||||
int64_t writeVersion = 0;
|
||||
int64_t oldestVersion = 0;
|
||||
int64_t oldestVersion = arbitrary.next();
|
||||
int64_t writeVersion = oldestVersion;
|
||||
ConflictSetImpl cs{oldestVersion};
|
||||
ReferenceImpl refImpl{oldestVersion};
|
||||
|
||||
constexpr static auto kMaxKeyLen = 128;
|
||||
constexpr static auto kMaxKeySuffixLen = 8;
|
||||
|
||||
bool ok = true;
|
||||
|
||||
static const char *resultToStr(ConflictSet::Result r) {
|
||||
switch (r) {
|
||||
case ConflictSet::Commit:
|
||||
return "commit";
|
||||
case ConflictSet::Conflict:
|
||||
return "conflict";
|
||||
case ConflictSet::TooOld:
|
||||
return "too old";
|
||||
}
|
||||
abort();
|
||||
}
|
||||
const int prefixLen = arbitrary.bounded(512);
|
||||
const int prefixByte = arbitrary.randT<uint8_t>();
|
||||
|
||||
// Call until it returns true, for "done". Check internal invariants etc
|
||||
// between calls to next.
|
||||
bool next() {
|
||||
assert(cs.getBytes() >= 0);
|
||||
if (!arbitrary.hasEntropy()) {
|
||||
return true;
|
||||
}
|
||||
@@ -508,7 +606,8 @@ template <class ConflictSetImpl> struct TestDriver {
|
||||
{
|
||||
int numPointWrites = arbitrary.bounded(100);
|
||||
int numRangeWrites = arbitrary.bounded(100);
|
||||
int64_t v = ++writeVersion;
|
||||
int64_t v = (writeVersion += arbitrary.bounded(10) ? arbitrary.bounded(10)
|
||||
: arbitrary.next());
|
||||
auto *writes =
|
||||
new (arena) ConflictSet::WriteRange[numPointWrites + numRangeWrites];
|
||||
auto keys = set<std::string_view>(arena);
|
||||
@@ -516,9 +615,10 @@ template <class ConflictSetImpl> struct TestDriver {
|
||||
if (!arbitrary.hasEntropy()) {
|
||||
return true;
|
||||
}
|
||||
int keyLen = arbitrary.bounded(kMaxKeyLen);
|
||||
int keyLen = prefixLen + arbitrary.bounded(kMaxKeySuffixLen);
|
||||
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));
|
||||
}
|
||||
|
||||
@@ -549,33 +649,79 @@ template <class ConflictSetImpl> struct TestDriver {
|
||||
}
|
||||
#if DEBUG_VERBOSE && !defined(NDEBUG)
|
||||
if (writes[i].end.len == 0) {
|
||||
fprintf(stderr, "Write: {%s} -> %" PRId64 "\n",
|
||||
printable(writes[i].begin).c_str(), writeVersion);
|
||||
fprintf(stderr, "Write: {%s}\n", printable(writes[i].begin).c_str());
|
||||
} else {
|
||||
fprintf(stderr, "Write: [%s, %s) -> %" PRId64 "\n",
|
||||
fprintf(stderr, "Write: [%s, %s)\n",
|
||||
printable(writes[i].begin).c_str(),
|
||||
printable(writes[i].end).c_str(), writeVersion);
|
||||
printable(writes[i].end).c_str());
|
||||
}
|
||||
#endif
|
||||
}
|
||||
assert(iter == keys.end());
|
||||
assert(i == numPointWrites + numRangeWrites);
|
||||
|
||||
#if DEBUG_VERBOSE && !defined(NDEBUG)
|
||||
fprintf(stderr, "Write @ %" PRId64 "\n", v);
|
||||
#endif
|
||||
|
||||
// Test non-canonical writes
|
||||
if (numPointWrites > 0) {
|
||||
int overlaps = arbitrary.bounded(numPointWrites);
|
||||
for (int i = 0; i < numPointWrites + numRangeWrites && overlaps > 0;
|
||||
++i) {
|
||||
if (writes[i].end.len == 0) {
|
||||
int keyLen = prefixLen + arbitrary.bounded(kMaxKeySuffixLen);
|
||||
auto *begin = new (arena) uint8_t[keyLen];
|
||||
memset(begin, prefixByte, prefixLen);
|
||||
arbitrary.randomBytes(begin + prefixLen, keyLen - prefixLen);
|
||||
writes[i].end.len = keyLen;
|
||||
writes[i].end.p = begin;
|
||||
auto c =
|
||||
std::span<const uint8_t>(writes[i].begin.p,
|
||||
writes[i].begin.len) <=>
|
||||
std::span<const uint8_t>(writes[i].end.p, writes[i].end.len);
|
||||
if (c > 0) {
|
||||
using std::swap;
|
||||
swap(writes[i].begin, writes[i].end);
|
||||
} else if (c == 0) {
|
||||
// It's a point write after all, I guess
|
||||
writes[i].end.len = 0;
|
||||
}
|
||||
--overlaps;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (arbitrary.bounded(2)) {
|
||||
// Shuffle writes
|
||||
for (int i = numPointWrites + numRangeWrites - 1; i > 0; --i) {
|
||||
int j = arbitrary.bounded(i + 1);
|
||||
if (i != j) {
|
||||
using std::swap;
|
||||
swap(writes[i], writes[j]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CALLGRIND_START_INSTRUMENTATION;
|
||||
cs.addWrites(writes, numPointWrites + numRangeWrites, v);
|
||||
CALLGRIND_STOP_INSTRUMENTATION;
|
||||
|
||||
refImpl.addWrites(writes, numPointWrites + numRangeWrites, v);
|
||||
|
||||
oldestVersion = std::max<int64_t>(writeVersion - arbitrary.bounded(10),
|
||||
oldestVersion);
|
||||
oldestVersion +=
|
||||
arbitrary.bounded(10) ? arbitrary.bounded(10) : arbitrary.next();
|
||||
oldestVersion = std::min(oldestVersion, writeVersion);
|
||||
cs.setOldestVersion(oldestVersion);
|
||||
refImpl.setOldestVersion(oldestVersion);
|
||||
}
|
||||
{
|
||||
int numPointReads = arbitrary.bounded(100);
|
||||
int numRangeReads = arbitrary.bounded(100);
|
||||
int64_t v = std::max<int64_t>(writeVersion - arbitrary.bounded(10), 0);
|
||||
|
||||
int64_t v = std::max<int64_t>(writeVersion - (arbitrary.bounded(10)
|
||||
? arbitrary.bounded(10)
|
||||
: arbitrary.next()),
|
||||
0);
|
||||
auto *reads =
|
||||
new (arena) ConflictSet::ReadRange[numPointReads + numRangeReads];
|
||||
auto keys = set<std::string_view>(arena);
|
||||
@@ -583,9 +729,10 @@ template <class ConflictSetImpl> struct TestDriver {
|
||||
if (!arbitrary.hasEntropy()) {
|
||||
return true;
|
||||
}
|
||||
int keyLen = arbitrary.bounded(kMaxKeyLen);
|
||||
int keyLen = prefixLen + arbitrary.bounded(kMaxKeySuffixLen);
|
||||
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));
|
||||
}
|
||||
|
||||
@@ -616,12 +763,12 @@ template <class ConflictSetImpl> struct TestDriver {
|
||||
reads[i].readVersion = v;
|
||||
#if DEBUG_VERBOSE && !defined(NDEBUG)
|
||||
if (reads[i].end.len == 0) {
|
||||
fprintf(stderr, "Read: {%s} @ %d\n",
|
||||
printable(reads[i].begin).c_str(), int(reads[i].readVersion));
|
||||
fprintf(stderr, "Read: {%s} @ %" PRId64 "\n",
|
||||
printable(reads[i].begin).c_str(), reads[i].readVersion);
|
||||
} else {
|
||||
fprintf(stderr, "Read: [%s, %s) @ %d\n",
|
||||
fprintf(stderr, "Read: [%s, %s) @ %" PRId64 "\n",
|
||||
printable(reads[i].begin).c_str(),
|
||||
printable(reads[i].end).c_str(), int(reads[i].readVersion));
|
||||
printable(reads[i].end).c_str(), reads[i].readVersion);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@@ -632,32 +779,60 @@ template <class ConflictSetImpl> struct TestDriver {
|
||||
auto *results2 =
|
||||
new (arena) ConflictSet::Result[numPointReads + numRangeReads];
|
||||
|
||||
#ifdef THREAD_TEST
|
||||
auto *results3 =
|
||||
new (arena) ConflictSet::Result[numPointReads + numRangeReads];
|
||||
std::latch ready{1};
|
||||
std::thread thread2{[&]() {
|
||||
ready.count_down();
|
||||
cs.check(reads, results3, numPointReads + numRangeReads);
|
||||
}};
|
||||
ready.wait();
|
||||
#endif
|
||||
|
||||
CALLGRIND_START_INSTRUMENTATION;
|
||||
cs.check(reads, results1, numPointReads + numRangeReads);
|
||||
CALLGRIND_STOP_INSTRUMENTATION;
|
||||
|
||||
refImpl.check(reads, results2, numPointReads + numRangeReads);
|
||||
for (int i = 0; i < numPointReads + numRangeReads; ++i) {
|
||||
if (results1[i] != results2[i]) {
|
||||
if (reads[i].end.len == 0) {
|
||||
fprintf(stderr,
|
||||
"Expected %s, got %s for read of {%s} at version %" PRId64
|
||||
"\n",
|
||||
resultToStr(results2[i]), resultToStr(results1[i]),
|
||||
printable(reads[i].begin).c_str(), reads[i].readVersion);
|
||||
} else {
|
||||
fprintf(
|
||||
stderr,
|
||||
"Expected %s, got %s for read of [%s, %s) at version %" PRId64
|
||||
"\n",
|
||||
resultToStr(results2[i]), resultToStr(results1[i]),
|
||||
printable(reads[i].begin).c_str(),
|
||||
printable(reads[i].end).c_str(), reads[i].readVersion);
|
||||
|
||||
auto compareResults = [reads](ConflictSet::Result *results1,
|
||||
ConflictSet::Result *results2, int count) {
|
||||
for (int i = 0; i < count; ++i) {
|
||||
if (results1[i] != results2[i]) {
|
||||
if (reads[i].end.len == 0) {
|
||||
fprintf(stderr,
|
||||
"Expected %s, got %s for read of {%s} at version %" PRId64
|
||||
"\n",
|
||||
resultToStr(results2[i]), resultToStr(results1[i]),
|
||||
printable(reads[i].begin).c_str(), reads[i].readVersion);
|
||||
} else {
|
||||
fprintf(
|
||||
stderr,
|
||||
"Expected %s, got %s for read of [%s, %s) at version %" PRId64
|
||||
"\n",
|
||||
resultToStr(results2[i]), resultToStr(results1[i]),
|
||||
printable(reads[i].begin).c_str(),
|
||||
printable(reads[i].end).c_str(), reads[i].readVersion);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
ok = false;
|
||||
return true;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
if (!compareResults(results1, results2, numPointReads + numRangeReads)) {
|
||||
ok = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
#ifdef THREAD_TEST
|
||||
thread2.join();
|
||||
if (!compareResults(results3, results2, numPointReads + numRangeReads)) {
|
||||
ok = false;
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
24
Jenkinsfile
vendored
24
Jenkinsfile
vendored
@@ -48,6 +48,17 @@ pipeline {
|
||||
recordIssues(tools: [clang()])
|
||||
}
|
||||
}
|
||||
stage('SIMD fallback') {
|
||||
agent {
|
||||
dockerfile {
|
||||
args '-v /home/jenkins/ccache:/ccache'
|
||||
reuseNode true
|
||||
}
|
||||
}
|
||||
steps {
|
||||
CleanBuildAndTest("-DUSE_SIMD_FALLBACK=ON")
|
||||
}
|
||||
}
|
||||
stage('Release [gcc]') {
|
||||
agent {
|
||||
dockerfile {
|
||||
@@ -56,7 +67,8 @@ pipeline {
|
||||
}
|
||||
}
|
||||
steps {
|
||||
CleanBuildAndTest("-DCMAKE_C_COMPILER=gcc -DCMAKE_CXX_COMPILER=g++ -DCMAKE_BUILD_TYPE=Release")
|
||||
CleanBuildAndTest("-DCMAKE_C_COMPILER=gcc -DCMAKE_CXX_COMPILER=g++ -DCMAKE_CXX_FLAGS=-DNVALGRIND")
|
||||
recordIssues(tools: [gcc()])
|
||||
sh '''
|
||||
cd build
|
||||
cpack -G DEB
|
||||
@@ -77,7 +89,7 @@ pipeline {
|
||||
}
|
||||
}
|
||||
steps {
|
||||
CleanBuildAndTest("-DCMAKE_TOOLCHAIN_FILE=../aarch64-toolchain.cmake")
|
||||
CleanBuildAndTest("-DCMAKE_TOOLCHAIN_FILE=../aarch64-toolchain.cmake -DCMAKE_CXX_FLAGS=-DNVALGRIND")
|
||||
sh '''
|
||||
cd build
|
||||
cpack -G DEB
|
||||
@@ -96,9 +108,13 @@ pipeline {
|
||||
steps {
|
||||
CleanBuildAndTest("-DCMAKE_C_COMPILER=gcc -DCMAKE_CXX_COMPILER=g++ -DCMAKE_C_FLAGS=--coverage -DCMAKE_CXX_FLAGS=--coverage -DCMAKE_BUILD_TYPE=Debug")
|
||||
sh '''
|
||||
gcovr --exclude '.*third_party.*' --cobertura > build/coverage.xml
|
||||
gcovr -f ConflictSet.cpp --cobertura > build/coverage.xml
|
||||
'''
|
||||
recordCoverage qualityGates: [[criticality: 'NOTE', metric: 'MODULE']], tools: [[parser: 'COBERTURA', pattern: 'build/coverage.xml']]
|
||||
sh '''
|
||||
# Suppress again, because we haven't dealt with function multi-versioning for x86 yet
|
||||
# gcovr -f ConflictSet.cpp --fail-under-line 100 > /dev/null
|
||||
'''
|
||||
cobertura autoUpdateHealth: false, autoUpdateStability: false, coberturaReportFile: 'build/coverage.xml', conditionalCoverageTargets: '70, 0, 0', failUnhealthy: false, failUnstable: false, lineCoverageTargets: '80, 0, 0', maxNumberOfBuilds: 0, methodCoverageTargets: '80, 0, 0', onlyStable: false, sourceEncoding: 'ASCII', zoomCoverageChart: false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
112
README.md
112
README.md
@@ -2,54 +2,56 @@ A data structure for optimistic concurrency control on ranges of bitwise-lexicog
|
||||
|
||||
Intended to replace FoundationDB's skip list.
|
||||
|
||||
Hardware for all benchmarks is a mac m1 2020.
|
||||
|
||||
# FoundationDB's benchmark
|
||||
|
||||
## Skip list
|
||||
|
||||
```
|
||||
New conflict set: 2.404 sec
|
||||
0.520 Mtransactions/sec
|
||||
2.080 Mkeys/sec
|
||||
Detect only: 2.266 sec
|
||||
0.552 Mtransactions/sec
|
||||
2.207 Mkeys/sec
|
||||
Skiplist only: 1.594 sec
|
||||
0.784 Mtransactions/sec
|
||||
3.137 Mkeys/sec
|
||||
New conflict set: 1.957 sec
|
||||
0.639 Mtransactions/sec
|
||||
2.555 Mkeys/sec
|
||||
Detect only: 1.845 sec
|
||||
0.678 Mtransactions/sec
|
||||
2.710 Mkeys/sec
|
||||
Skiplist only: 1.263 sec
|
||||
0.990 Mtransactions/sec
|
||||
3.960 Mkeys/sec
|
||||
Performance counters:
|
||||
Build: 0.071
|
||||
Add: 0.0641
|
||||
Detect: 2.27
|
||||
D.Sort: 0.44
|
||||
D.Combine: 0.018
|
||||
D.CheckRead: 0.855
|
||||
D.CheckIntraBatch: 0.00903
|
||||
D.MergeWrite: 0.739
|
||||
D.RemoveBefore: 0.201
|
||||
Build: 0.0546
|
||||
Add: 0.0563
|
||||
Detect: 1.84
|
||||
D.Sort: 0.412
|
||||
D.Combine: 0.0141
|
||||
D.CheckRead: 0.671
|
||||
D.CheckIntraBatch: 0.0068
|
||||
D.MergeWrite: 0.592
|
||||
D.RemoveBefore: 0.146
|
||||
```
|
||||
|
||||
## Radix tree (this implementation)
|
||||
|
||||
```
|
||||
New conflict set: 1.743 sec
|
||||
0.717 Mtransactions/sec
|
||||
2.869 Mkeys/sec
|
||||
Detect only: 1.611 sec
|
||||
0.776 Mtransactions/sec
|
||||
3.103 Mkeys/sec
|
||||
Skiplist only: 0.919 sec
|
||||
1.360 Mtransactions/sec
|
||||
5.440 Mkeys/sec
|
||||
New conflict set: 1.366 sec
|
||||
0.915 Mtransactions/sec
|
||||
3.660 Mkeys/sec
|
||||
Detect only: 1.248 sec
|
||||
1.002 Mtransactions/sec
|
||||
4.007 Mkeys/sec
|
||||
Skiplist only: 0.573 sec
|
||||
2.182 Mtransactions/sec
|
||||
8.730 Mkeys/sec
|
||||
Performance counters:
|
||||
Build: 0.0657
|
||||
Add: 0.0628
|
||||
Detect: 1.61
|
||||
D.Sort: 0.442
|
||||
D.Combine: 0.0178
|
||||
D.CheckRead: 0.395
|
||||
D.CheckIntraBatch: 0.00776
|
||||
D.MergeWrite: 0.524
|
||||
D.RemoveBefore: 0.221
|
||||
Build: 0.0594
|
||||
Add: 0.0572
|
||||
Detect: 1.25
|
||||
D.Sort: 0.418
|
||||
D.Combine: 0.0149
|
||||
D.CheckRead: 0.232
|
||||
D.CheckIntraBatch: 0.0067
|
||||
D.MergeWrite: 0.341
|
||||
D.RemoveBefore: 0.232
|
||||
```
|
||||
|
||||
# Our benchmark
|
||||
@@ -58,25 +60,27 @@ Performance counters:
|
||||
|
||||
| ns/op | op/s | err% | total | benchmark
|
||||
|--------------------:|--------------------:|--------:|----------:|:----------
|
||||
| 270.07 | 3,702,706.03 | 0.4% | 0.01 | `point reads`
|
||||
| 285.76 | 3,499,437.03 | 1.5% | 0.01 | `prefix reads`
|
||||
| 532.54 | 1,877,794.90 | 0.7% | 0.01 | `range reads`
|
||||
| 528.50 | 1,892,132.94 | 0.7% | 0.01 | `point writes`
|
||||
| 516.53 | 1,935,978.22 | 0.9% | 0.01 | `prefix writes`
|
||||
| 303.34 | 3,296,630.84 | 3.6% | 0.05 | `range writes`
|
||||
| 502.88 | 1,988,553.24 | 2.0% | 0.01 | `monotonic increasing point writes`
|
||||
| 245.99 | 4,065,232.81 | 0.3% | 0.01 | `point reads`
|
||||
| 265.93 | 3,760,430.49 | 0.2% | 0.01 | `prefix reads`
|
||||
| 485.30 | 2,060,569.50 | 0.2% | 0.01 | `range reads`
|
||||
| 449.60 | 2,224,195.17 | 0.4% | 0.01 | `point writes`
|
||||
| 441.76 | 2,263,688.18 | 1.1% | 0.01 | `prefix writes`
|
||||
| 245.42 | 4,074,647.54 | 2.4% | 0.02 | `range writes`
|
||||
| 572.80 | 1,745,810.06 | 1.3% | 0.01 | `monotonic increasing point writes`
|
||||
| 154,819.33 | 6,459.14 | 0.9% | 0.01 | `worst case for radix tree`
|
||||
|
||||
## Radix tree (this implementation)
|
||||
|
||||
| ns/op | op/s | err% | total | benchmark
|
||||
|--------------------:|--------------------:|--------:|----------:|:----------
|
||||
| 14.52 | 68,850,842.99 | 1.2% | 0.01 | `point reads`
|
||||
| 60.89 | 16,422,538.22 | 1.5% | 0.01 | `prefix reads`
|
||||
| 226.89 | 4,407,362.98 | 0.5% | 0.01 | `range reads`
|
||||
| 22.99 | 43,498,198.49 | 0.2% | 0.01 | `point writes`
|
||||
| 50.51 | 19,799,864.54 | 1.0% | 0.01 | `prefix writes`
|
||||
| 82.50 | 12,121,212.12 | 2.6% | 0.03 | `range writes`
|
||||
| 119.94 | 8,337,354.54 | 2.1% | 0.01 | `monotonic increasing point writes`
|
||||
| 19.17 | 52,163,930.66 | 0.1% | 0.01 | `point reads`
|
||||
| 23.68 | 42,224,388.21 | 0.7% | 0.01 | `prefix reads`
|
||||
| 63.30 | 15,797,506.06 | 0.9% | 0.01 | `range reads`
|
||||
| 29.66 | 33,720,994.74 | 0.3% | 0.01 | `point writes`
|
||||
| 43.50 | 22,987,781.25 | 1.0% | 0.01 | `prefix writes`
|
||||
| 50.00 | 20,000,000.00 | 0.8% | 0.01 | `range writes`
|
||||
| 103.25 | 9,684,786.47 | 2.9% | 0.01 | `monotonic increasing point writes`
|
||||
| 1,181,500.00 | 846.38 | 2.3% | 0.01 | `worst case for radix tree`
|
||||
|
||||
# "Real data" test
|
||||
|
||||
@@ -85,13 +89,13 @@ Point queries only, best of three runs. Gc ratio is the ratio of time spent doin
|
||||
## skip list
|
||||
|
||||
```
|
||||
Check: 12.7863 seconds, 292.384 MB/s, Add: 19.8276 seconds, 35.4071 MB/s, Gc ratio: 23.5314%
|
||||
Check: 11.3385 seconds, 329.718 MB/s, Add: 5.35612 seconds, 131.072 MB/s, Gc ratio: 45.7173%
|
||||
```
|
||||
|
||||
## radix tree
|
||||
|
||||
```
|
||||
Check: 3.60187 seconds, 1037.94 MB/s, Add: 3.03958 seconds, 230.966 MB/s, Gc ratio: 52.3876%
|
||||
Check: 2.48583 seconds, 1503.93 MB/s, Add: 2.12768 seconds, 329.954 MB/s, Gc ratio: 41.7943%
|
||||
```
|
||||
|
||||
## hash table
|
||||
@@ -99,5 +103,5 @@ Check: 3.60187 seconds, 1037.94 MB/s, Add: 3.03958 seconds, 230.966 MB/s, Gc rat
|
||||
(The hash table implementation doesn't work on range queries, and its purpose is to provide an idea of how fast point queries can be)
|
||||
|
||||
```
|
||||
Check: 2.15925 seconds, 1731.4 MB/s, Add: 1.08519 seconds, 646.926 MB/s, Gc ratio: 52.1526%
|
||||
```
|
||||
Check: 1.83386 seconds, 2038.6 MB/s, Add: 0.601411 seconds, 1167.32 MB/s, Gc ratio: 48.9776%
|
||||
```
|
||||
|
@@ -1,13 +1,18 @@
|
||||
#include <ConflictSet.h>
|
||||
|
||||
#include <cerrno>
|
||||
#include <chrono>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <fcntl.h>
|
||||
#include <string_view>
|
||||
#include <sys/mman.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
#include <vector>
|
||||
|
||||
using namespace weaselab;
|
||||
|
||||
double now() {
|
||||
return std::chrono::duration_cast<std::chrono::nanoseconds>(
|
||||
std::chrono::steady_clock::now().time_since_epoch())
|
||||
@@ -28,7 +33,7 @@ constexpr inline size_t rightAlign(size_t offset, size_t alignment) {
|
||||
|
||||
int main(int argc, const char **argv) {
|
||||
// Use with this dataset https://snap.stanford.edu/data/memetracker9.html
|
||||
// Preprocess the files with `sed -i '' '/^Q/d'`
|
||||
// Preprocess the files with `sed -i'' '/^Q/d'`
|
||||
|
||||
double checkTime = 0;
|
||||
double addTime = 0;
|
||||
@@ -40,6 +45,8 @@ int main(int argc, const char **argv) {
|
||||
int64_t version = 0;
|
||||
double timer = 0;
|
||||
|
||||
int64_t peakMemory = 0;
|
||||
|
||||
for (int i = 1; i < argc; ++i) {
|
||||
int fd = open(argv[i], O_RDONLY);
|
||||
struct stat st;
|
||||
@@ -109,6 +116,10 @@ int main(int argc, const char **argv) {
|
||||
write = {};
|
||||
reads.clear();
|
||||
|
||||
if (cs.getBytes() > peakMemory) {
|
||||
peakMemory = cs.getBytes();
|
||||
}
|
||||
|
||||
timer = now();
|
||||
cs.setOldestVersion(version - 10000);
|
||||
gcTime += now() - timer;
|
||||
@@ -118,8 +129,9 @@ int main(int argc, const char **argv) {
|
||||
close(fd);
|
||||
}
|
||||
|
||||
printf(
|
||||
"Check: %g seconds, %g MB/s, Add: %g seconds, %g MB/s, Gc ratio: %g%%\n",
|
||||
checkTime, checkBytes / checkTime * 1e-6, addTime,
|
||||
addBytes / addTime * 1e-6, gcTime / (gcTime + addTime) * 1e2);
|
||||
}
|
||||
printf("Check: %g seconds, %g MB/s, Add: %g seconds, %g MB/s, Gc ratio: "
|
||||
"%g%%, Peak idle memory: %g\n",
|
||||
checkTime, checkBytes / checkTime * 1e-6, addTime,
|
||||
addBytes / addTime * 1e-6, gcTime / (gcTime + addTime) * 1e2,
|
||||
double(peakMemory));
|
||||
}
|
||||
|
292
SkipList.cpp
292
SkipList.cpp
@@ -16,10 +16,14 @@
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
* This source code is modified to compile outside of FoundationDB
|
||||
*/
|
||||
|
||||
#include "ConflictSet.h"
|
||||
#include "Internal.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <span>
|
||||
|
||||
std::span<const uint8_t> keyAfter(Arena &arena, std::span<const uint8_t> key) {
|
||||
@@ -50,6 +54,135 @@ struct KeyRangeRef {
|
||||
: begin(begin), end(keyAfter(arena, begin)) {}
|
||||
};
|
||||
|
||||
struct KeyInfo {
|
||||
StringRef key;
|
||||
bool begin;
|
||||
bool write;
|
||||
|
||||
KeyInfo() = default;
|
||||
KeyInfo(StringRef key, bool begin, bool write)
|
||||
: key(key), begin(begin), write(write) {}
|
||||
};
|
||||
|
||||
force_inline int extra_ordering(const KeyInfo &ki) {
|
||||
return ki.begin * 2 + (ki.write ^ ki.begin);
|
||||
}
|
||||
|
||||
// returns true if done with string
|
||||
force_inline bool getCharacter(const KeyInfo &ki, int character,
|
||||
int &outputCharacter) {
|
||||
// normal case
|
||||
if (character < ki.key.size()) {
|
||||
outputCharacter = 5 + ki.key.begin()[character];
|
||||
return false;
|
||||
}
|
||||
|
||||
// termination
|
||||
if (character == ki.key.size()) {
|
||||
outputCharacter = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (character == ki.key.size() + 1) {
|
||||
// end/begin+read/write relative sorting
|
||||
outputCharacter = extra_ordering(ki);
|
||||
return false;
|
||||
}
|
||||
|
||||
outputCharacter = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool operator<(const KeyInfo &lhs, const KeyInfo &rhs) {
|
||||
int i = std::min(lhs.key.size(), rhs.key.size());
|
||||
int c = memcmp(lhs.key.data(), rhs.key.data(), i);
|
||||
if (c != 0)
|
||||
return c < 0;
|
||||
|
||||
// Always sort shorter keys before longer keys.
|
||||
if (lhs.key.size() < rhs.key.size()) {
|
||||
return true;
|
||||
}
|
||||
if (lhs.key.size() > rhs.key.size()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// When the keys are the same length, use the extra ordering constraint.
|
||||
return extra_ordering(lhs) < extra_ordering(rhs);
|
||||
}
|
||||
|
||||
bool operator==(const KeyInfo &lhs, const KeyInfo &rhs) {
|
||||
return !(lhs < rhs || rhs < lhs);
|
||||
}
|
||||
|
||||
void swapSort(std::vector<KeyInfo> &points, int a, int b) {
|
||||
if (points[b] < points[a]) {
|
||||
KeyInfo temp;
|
||||
temp = points[a];
|
||||
points[a] = points[b];
|
||||
points[b] = temp;
|
||||
}
|
||||
}
|
||||
|
||||
struct SortTask {
|
||||
int begin;
|
||||
int size;
|
||||
int character;
|
||||
SortTask(int begin, int size, int character)
|
||||
: begin(begin), size(size), character(character) {}
|
||||
};
|
||||
|
||||
void sortPoints(std::vector<KeyInfo> &points) {
|
||||
std::vector<SortTask> tasks;
|
||||
std::vector<KeyInfo> newPoints;
|
||||
std::vector<int> counts;
|
||||
|
||||
tasks.emplace_back(0, points.size(), 0);
|
||||
|
||||
while (tasks.size()) {
|
||||
SortTask st = tasks.back();
|
||||
tasks.pop_back();
|
||||
|
||||
if (st.size < 10) {
|
||||
std::sort(points.begin() + st.begin, points.begin() + st.begin + st.size);
|
||||
continue;
|
||||
}
|
||||
|
||||
newPoints.resize(st.size);
|
||||
counts.assign(256 + 5, 0);
|
||||
|
||||
// get counts
|
||||
int c;
|
||||
bool allDone = true;
|
||||
for (int i = st.begin; i < st.begin + st.size; i++) {
|
||||
allDone &= getCharacter(points[i], st.character, c);
|
||||
counts[c]++;
|
||||
}
|
||||
if (allDone)
|
||||
continue;
|
||||
|
||||
// calculate offsets from counts and build next level of tasks
|
||||
int total = 0;
|
||||
for (int i = 0; i < counts.size(); i++) {
|
||||
int temp = counts[i];
|
||||
if (temp > 1)
|
||||
tasks.emplace_back(st.begin + total, temp, st.character + 1);
|
||||
counts[i] = total;
|
||||
total += temp;
|
||||
}
|
||||
|
||||
// put in their places
|
||||
for (int i = st.begin; i < st.begin + st.size; i++) {
|
||||
getCharacter(points[i], st.character, c);
|
||||
newPoints[counts[c]++] = points[i];
|
||||
}
|
||||
|
||||
// copy back into original points array
|
||||
for (int i = 0; i < st.size; i++)
|
||||
points[st.begin + i] = newPoints[i];
|
||||
}
|
||||
}
|
||||
|
||||
static thread_local uint32_t g_seed = 0;
|
||||
|
||||
static inline int skfastrand() {
|
||||
@@ -149,7 +282,7 @@ private:
|
||||
setMaxVersion(level, v);
|
||||
}
|
||||
|
||||
void destroy() { free(this); }
|
||||
void destroy() { safe_free(this, getNodeSize()); }
|
||||
|
||||
private:
|
||||
int getNodeSize() const {
|
||||
@@ -268,13 +401,21 @@ public:
|
||||
}
|
||||
|
||||
explicit SkipList(Version version = 0) {
|
||||
#if DEBUG_VERBOSE
|
||||
fprintf(stderr, "skip_list: create\n");
|
||||
#endif
|
||||
header = Node::create(StringRef(), MaxLevels - 1);
|
||||
for (int l = 0; l < MaxLevels; l++) {
|
||||
header->setNext(l, nullptr);
|
||||
header->setMaxVersion(l, version);
|
||||
}
|
||||
}
|
||||
~SkipList() { destroy(); }
|
||||
~SkipList() {
|
||||
#if DEBUG_VERBOSE
|
||||
fprintf(stderr, "skip_list: destroy\n");
|
||||
#endif
|
||||
destroy();
|
||||
}
|
||||
SkipList(SkipList &&other) noexcept : header(other.header) {
|
||||
other.header = nullptr;
|
||||
}
|
||||
@@ -567,7 +708,8 @@ struct SkipListConflictSet {};
|
||||
|
||||
struct __attribute__((visibility("hidden"))) ConflictSet::Impl {
|
||||
Impl(int64_t oldestVersion)
|
||||
: oldestVersion(oldestVersion), skipList(oldestVersion) {}
|
||||
: oldestVersion(oldestVersion), newestVersion(oldestVersion),
|
||||
skipList(oldestVersion) {}
|
||||
void check(const ConflictSet::ReadRange *reads, ConflictSet::Result *results,
|
||||
int count) const {
|
||||
Arena arena;
|
||||
@@ -582,7 +724,8 @@ struct __attribute__((visibility("hidden"))) ConflictSet::Impl {
|
||||
}
|
||||
skipList.detectConflicts(ranges, count, results);
|
||||
for (int i = 0; i < count; ++i) {
|
||||
if (reads[i].readVersion < oldestVersion) {
|
||||
if (reads[i].readVersion < oldestVersion ||
|
||||
reads[i].readVersion < newestVersion - 2e9) {
|
||||
results[i] = TooOld;
|
||||
}
|
||||
}
|
||||
@@ -590,8 +733,40 @@ struct __attribute__((visibility("hidden"))) ConflictSet::Impl {
|
||||
|
||||
void addWrites(const ConflictSet::WriteRange *writes, int count,
|
||||
int64_t writeVersion) {
|
||||
auto points = std::vector<KeyInfo>(count * 2);
|
||||
Arena arena;
|
||||
const int stringCount = count * 2;
|
||||
|
||||
for (int r = 0; r < count; r++) {
|
||||
points.emplace_back(StringRef(writes[r].begin.p, writes[r].begin.len),
|
||||
true, true);
|
||||
points.emplace_back(
|
||||
writes[r].end.len > 0
|
||||
? StringRef{writes[r].end.p, size_t(writes[r].end.len)}
|
||||
: keyAfter(arena, points.back().key),
|
||||
false, true);
|
||||
}
|
||||
|
||||
sortPoints(points);
|
||||
|
||||
int activeWriteCount = 0;
|
||||
std::vector<std::pair<StringRef, StringRef>> combinedWriteConflictRanges;
|
||||
for (const KeyInfo &point : points) {
|
||||
if (point.write) {
|
||||
if (point.begin) {
|
||||
activeWriteCount++;
|
||||
if (activeWriteCount == 1)
|
||||
combinedWriteConflictRanges.emplace_back(point.key, StringRef());
|
||||
} else /*if (point.end)*/ {
|
||||
activeWriteCount--;
|
||||
if (activeWriteCount == 0)
|
||||
combinedWriteConflictRanges.back().second = point.key;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
assert(writeVersion >= newestVersion);
|
||||
newestVersion = writeVersion;
|
||||
const int stringCount = combinedWriteConflictRanges.size() * 2;
|
||||
|
||||
const int stripeSize = 16;
|
||||
SkipList::Finger fingers[stripeSize];
|
||||
@@ -602,12 +777,14 @@ struct __attribute__((visibility("hidden"))) ConflictSet::Impl {
|
||||
int ss = stringCount - (stripes - 1) * stripeSize;
|
||||
for (int s = stripes - 1; s >= 0; s--) {
|
||||
for (int i = 0; i * 2 < ss; ++i) {
|
||||
const auto &w = writes[s * stripeSize / 2 + i];
|
||||
values[i * 2] = {w.begin.p, size_t(w.begin.len)};
|
||||
values[i * 2 + 1] = w.end.len > 0
|
||||
? StringRef{w.end.p, size_t(w.end.len)}
|
||||
: keyAfter(arena, values[i * 2]);
|
||||
keyUpdates += 2;
|
||||
const auto &w = combinedWriteConflictRanges[s * stripeSize / 2 + i];
|
||||
#if DEBUG_VERBOSE
|
||||
printf("Write begin: %s\n", printable(w.begin).c_str());
|
||||
fflush(stdout);
|
||||
#endif
|
||||
values[i * 2] = w.first;
|
||||
values[i * 2 + 1] = w.second;
|
||||
keyUpdates += 3;
|
||||
}
|
||||
skipList.find(values, fingers, temp, ss);
|
||||
skipList.addConflictRanges(fingers, ss / 2, writeVersion);
|
||||
@@ -616,46 +793,96 @@ struct __attribute__((visibility("hidden"))) ConflictSet::Impl {
|
||||
}
|
||||
|
||||
void setOldestVersion(int64_t oldestVersion) {
|
||||
assert(oldestVersion >= this->oldestVersion);
|
||||
this->oldestVersion = oldestVersion;
|
||||
SkipList::Finger finger;
|
||||
int temp;
|
||||
std::span<const uint8_t> key = removalKey;
|
||||
skipList.find(&key, &finger, &temp, 1);
|
||||
skipList.removeBefore(oldestVersion, finger, std::exchange(keyUpdates, 0));
|
||||
skipList.removeBefore(oldestVersion, finger, std::exchange(keyUpdates, 10));
|
||||
removalArena = Arena();
|
||||
removalKey = copyToArena(
|
||||
removalArena, {finger.getValue().data(), finger.getValue().size()});
|
||||
}
|
||||
|
||||
int64_t totalBytes = 0;
|
||||
|
||||
private:
|
||||
int64_t keyUpdates = 0;
|
||||
int64_t keyUpdates = 10;
|
||||
Arena removalArena;
|
||||
std::span<const uint8_t> removalKey;
|
||||
int64_t oldestVersion;
|
||||
int64_t newestVersion;
|
||||
SkipList skipList;
|
||||
};
|
||||
|
||||
// Internal entry points. Public entry points should just delegate to these
|
||||
|
||||
void internal_check(ConflictSet::Impl *impl,
|
||||
const ConflictSet::ReadRange *reads,
|
||||
ConflictSet::Result *results, int count) {
|
||||
impl->check(reads, results, count);
|
||||
}
|
||||
void internal_addWrites(ConflictSet::Impl *impl,
|
||||
const ConflictSet::WriteRange *writes, int count,
|
||||
int64_t writeVersion) {
|
||||
mallocBytesDelta = 0;
|
||||
impl->addWrites(writes, count, writeVersion);
|
||||
impl->totalBytes += mallocBytesDelta;
|
||||
#if SHOW_MEMORY
|
||||
if (impl->totalBytes != mallocBytes) {
|
||||
abort();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void internal_setOldestVersion(ConflictSet::Impl *impl, int64_t oldestVersion) {
|
||||
mallocBytesDelta = 0;
|
||||
impl->setOldestVersion(oldestVersion);
|
||||
impl->totalBytes += mallocBytesDelta;
|
||||
#if SHOW_MEMORY
|
||||
if (impl->totalBytes != mallocBytes) {
|
||||
abort();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
ConflictSet::Impl *internal_create(int64_t oldestVersion) {
|
||||
mallocBytesDelta = 0;
|
||||
auto *result = new (safe_malloc(sizeof(ConflictSet::Impl)))
|
||||
ConflictSet::Impl{oldestVersion};
|
||||
result->totalBytes += mallocBytesDelta;
|
||||
return result;
|
||||
}
|
||||
|
||||
void internal_destroy(ConflictSet::Impl *impl) {
|
||||
impl->~Impl();
|
||||
safe_free(impl, sizeof(ConflictSet::Impl));
|
||||
}
|
||||
|
||||
int64_t internal_getBytes(ConflictSet::Impl *impl) { return impl->totalBytes; }
|
||||
|
||||
void ConflictSet::check(const ReadRange *reads, Result *results,
|
||||
int count) const {
|
||||
return impl->check(reads, results, count);
|
||||
internal_check(impl, reads, results, count);
|
||||
}
|
||||
|
||||
void ConflictSet::addWrites(const WriteRange *writes, int count,
|
||||
int64_t writeVersion) {
|
||||
return impl->addWrites(writes, count, writeVersion);
|
||||
internal_addWrites(impl, writes, count, writeVersion);
|
||||
}
|
||||
|
||||
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)
|
||||
: impl(new (safe_malloc(sizeof(Impl))) Impl{oldestVersion}) {}
|
||||
: impl(internal_create(oldestVersion)) {}
|
||||
|
||||
ConflictSet::~ConflictSet() {
|
||||
if (impl) {
|
||||
impl->~Impl();
|
||||
free(impl);
|
||||
internal_destroy(impl);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -676,25 +903,36 @@ extern "C" {
|
||||
__attribute__((__visibility__("default"))) void
|
||||
ConflictSet_check(void *cs, const ConflictSet_ReadRange *reads,
|
||||
ConflictSet_Result *results, int count) {
|
||||
((ConflictSet::Impl *)cs)->check(reads, results, count);
|
||||
internal_check((ConflictSet::Impl *)cs, reads, results, count);
|
||||
}
|
||||
__attribute__((__visibility__("default"))) void
|
||||
ConflictSet_addWrites(void *cs, const ConflictSet_WriteRange *writes, int count,
|
||||
int64_t writeVersion) {
|
||||
((ConflictSet::Impl *)cs)->addWrites(writes, count, writeVersion);
|
||||
internal_addWrites((ConflictSet::Impl *)cs, writes, count, writeVersion);
|
||||
}
|
||||
__attribute__((__visibility__("default"))) void
|
||||
ConflictSet_setOldestVersion(void *cs, int64_t oldestVersion) {
|
||||
((ConflictSet::Impl *)cs)->setOldestVersion(oldestVersion);
|
||||
internal_setOldestVersion((ConflictSet::Impl *)cs, oldestVersion);
|
||||
}
|
||||
__attribute__((__visibility__("default"))) void *
|
||||
ConflictSet_create(int64_t oldestVersion) {
|
||||
return new (safe_malloc(sizeof(ConflictSet::Impl)))
|
||||
ConflictSet::Impl{oldestVersion};
|
||||
return internal_create(oldestVersion);
|
||||
}
|
||||
__attribute__((__visibility__("default"))) void ConflictSet_destroy(void *cs) {
|
||||
using Impl = ConflictSet::Impl;
|
||||
((Impl *)cs)->~Impl();
|
||||
free(cs);
|
||||
internal_destroy((ConflictSet::Impl *)cs);
|
||||
}
|
||||
__attribute__((__visibility__("default"))) int64_t
|
||||
ConflictSet_getBytes(void *cs) {
|
||||
return internal_getBytes((ConflictSet::Impl *)cs);
|
||||
}
|
||||
}
|
||||
|
||||
#if SHOW_MEMORY
|
||||
struct __attribute__((visibility("default"))) PeakPrinter {
|
||||
~PeakPrinter() {
|
||||
printf("--- skip_list ---\n");
|
||||
printf("malloc bytes: %g\n", double(mallocBytes));
|
||||
printf("Peak malloc bytes: %g\n", double(peakMallocBytes));
|
||||
}
|
||||
} peakPrinter;
|
||||
#endif
|
@@ -5,7 +5,6 @@
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
for (int i = 1; i < argc; ++i) {
|
||||
printf("Running: %s\n", argv[i]);
|
||||
std::ifstream t(argv[i], std::ios::binary);
|
||||
std::stringstream buffer;
|
||||
buffer << t.rdbuf();
|
||||
|
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
|
10
apple-symbol-imports.txt
Normal file
10
apple-symbol-imports.txt
Normal file
@@ -0,0 +1,10 @@
|
||||
___stack_chk_fail
|
||||
___stack_chk_guard
|
||||
__tlv_bootstrap
|
||||
_abort
|
||||
_bzero
|
||||
_free
|
||||
_malloc
|
||||
_memcpy
|
||||
_memmove
|
||||
dyld_stub_binder
|
138
conflict_set.py
Normal file
138
conflict_set.py
Normal file
@@ -0,0 +1,138 @@
|
||||
import ctypes
|
||||
import enum
|
||||
import os
|
||||
|
||||
from typing import Optional
|
||||
|
||||
|
||||
class _Key(ctypes.Structure):
|
||||
_fields_ = [("p", ctypes.POINTER(ctypes.c_ubyte)), ("len", ctypes.c_int)]
|
||||
|
||||
|
||||
class ReadRange(ctypes.Structure):
|
||||
_fields_ = [
|
||||
("begin", _Key),
|
||||
("end", _Key),
|
||||
("readVersion", ctypes.c_int64),
|
||||
]
|
||||
|
||||
|
||||
class WriteRange(ctypes.Structure):
|
||||
_fields_ = [("begin", _Key), ("end", _Key)]
|
||||
|
||||
|
||||
class Result(enum.Enum):
|
||||
COMMIT = 0
|
||||
CONFLICT = 1
|
||||
TOO_OLD = 2
|
||||
|
||||
|
||||
def write(begin: bytes, end: Optional[bytes] = None) -> WriteRange:
|
||||
b = (ctypes.c_ubyte * len(begin)).from_buffer(bytearray(begin))
|
||||
|
||||
if end is None:
|
||||
e = (ctypes.c_ubyte * 0)()
|
||||
else:
|
||||
e = (ctypes.c_ubyte * len(end)).from_buffer(bytearray(end))
|
||||
return WriteRange(_Key(b, len(b)), _Key(e, len(e)))
|
||||
|
||||
|
||||
def read(version: int, begin: bytes, end: Optional[bytes] = None) -> ReadRange:
|
||||
b = (ctypes.c_ubyte * len(begin)).from_buffer(bytearray(begin))
|
||||
if end is None:
|
||||
e = (ctypes.c_ubyte * 0)()
|
||||
else:
|
||||
e = (ctypes.c_ubyte * len(end)).from_buffer(bytearray(end))
|
||||
return ReadRange(_Key(b, len(b)), _Key(e, len(e)), version)
|
||||
|
||||
|
||||
class ConflictSet:
|
||||
def __init__(
|
||||
self,
|
||||
version: int = 0,
|
||||
build_dir: Optional[str] = None,
|
||||
implementation: Optional[str] = None,
|
||||
) -> None:
|
||||
self._lib = None
|
||||
if build_dir is None:
|
||||
build_dir = os.path.dirname(__file__) + "/build"
|
||||
if implementation is None:
|
||||
implementation = "radix_tree"
|
||||
for f in (
|
||||
build_dir + "/" + implementation + "/libconflict-set.so.0",
|
||||
os.path.dirname(__file__)
|
||||
+ "/build/"
|
||||
+ implementation
|
||||
+ "/libconflict-set.0.dylib",
|
||||
):
|
||||
try:
|
||||
self._lib = ctypes.cdll.LoadLibrary(f)
|
||||
except:
|
||||
pass
|
||||
|
||||
if self._lib is None:
|
||||
import sys
|
||||
|
||||
print(
|
||||
"Could not find libconflict-set implementation " + implementation,
|
||||
file=sys.stderr,
|
||||
)
|
||||
sys.exit(1)
|
||||
|
||||
self._lib.ConflictSet_create.argtypes = (ctypes.c_int64,)
|
||||
self._lib.ConflictSet_create.restype = ctypes.c_void_p
|
||||
|
||||
self._lib.ConflictSet_check.argtypes = (
|
||||
ctypes.c_void_p,
|
||||
ctypes.POINTER(ReadRange),
|
||||
ctypes.POINTER(ctypes.c_int),
|
||||
ctypes.c_int,
|
||||
)
|
||||
|
||||
self._lib.ConflictSet_addWrites.argtypes = (
|
||||
ctypes.c_void_p,
|
||||
ctypes.POINTER(WriteRange),
|
||||
ctypes.c_int,
|
||||
ctypes.c_int64,
|
||||
)
|
||||
|
||||
self._lib.ConflictSet_setOldestVersion.argtypes = (
|
||||
ctypes.c_void_p,
|
||||
ctypes.c_int64,
|
||||
)
|
||||
|
||||
self._lib.ConflictSet_destroy.argtypes = (ctypes.c_void_p,)
|
||||
|
||||
self._lib.ConflictSet_getBytes.argtypes = (ctypes.c_void_p,)
|
||||
self._lib.ConflictSet_getBytes.restype = ctypes.c_int64
|
||||
|
||||
self.p = self._lib.ConflictSet_create(version)
|
||||
|
||||
def addWrites(self, version: int, *writes: WriteRange):
|
||||
self._lib.ConflictSet_addWrites(
|
||||
self.p, (WriteRange * len(writes))(*writes), len(writes), version
|
||||
)
|
||||
|
||||
def check(self, *reads: ReadRange) -> list[Result]:
|
||||
r = (ctypes.c_int * len(reads))()
|
||||
self._lib.ConflictSet_check(
|
||||
self.p, (ReadRange * len(reads))(*reads), r, len(reads)
|
||||
)
|
||||
return [Result(x) for x in r]
|
||||
|
||||
def setOldestVersion(self, version: int) -> None:
|
||||
self._lib.ConflictSet_setOldestVersion(self.p, version)
|
||||
|
||||
def getBytes(self) -> int:
|
||||
return self._lib.ConflictSet_getBytes(self.p)
|
||||
|
||||
def __enter__(self):
|
||||
return self
|
||||
|
||||
def close(self) -> None:
|
||||
if self.p is not None:
|
||||
self._lib.ConflictSet_destroy(self.p)
|
||||
self.p = None
|
||||
|
||||
def __exit__(self, exception_type, exception_value, exception_traceback):
|
||||
self.close()
|
@@ -7,6 +7,7 @@ int main(void) {
|
||||
ConflictSet_WriteRange w;
|
||||
ConflictSet_Result result;
|
||||
ConflictSet_ReadRange r;
|
||||
int64_t bytes;
|
||||
w.begin.p = (const uint8_t *)"0000";
|
||||
w.begin.len = 4;
|
||||
w.end.len = 0;
|
||||
@@ -17,6 +18,8 @@ int main(void) {
|
||||
r.readVersion = 0;
|
||||
ConflictSet_check(cs, &r, &result, 1);
|
||||
assert(result == ConflictSet_Conflict);
|
||||
bytes = ConflictSet_getBytes(cs);
|
||||
assert(bytes > 0);
|
||||
ConflictSet_destroy(cs);
|
||||
return 0;
|
||||
}
|
||||
|
@@ -2,6 +2,8 @@
|
||||
|
||||
#include <cassert>
|
||||
|
||||
using namespace weaselab;
|
||||
|
||||
int main(void) {
|
||||
ConflictSet cs(0);
|
||||
ConflictSet::WriteRange w;
|
||||
@@ -17,4 +19,6 @@ int main(void) {
|
||||
r.readVersion = 0;
|
||||
cs.check(&r, &result, 1);
|
||||
assert(result == ConflictSet::Conflict);
|
||||
int64_t bytes = cs.getBytes();
|
||||
assert(bytes > 0);
|
||||
}
|
||||
|
BIN
corpus/005e2b0059b0261bc2288a5843a31e098d31013b
Normal file
BIN
corpus/005e2b0059b0261bc2288a5843a31e098d31013b
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
corpus/0164498b5d5fbc2a3a5979deec1a0446c0e1abb6
Normal file
BIN
corpus/0164498b5d5fbc2a3a5979deec1a0446c0e1abb6
Normal file
Binary file not shown.
Binary file not shown.
BIN
corpus/01cab80c8efd804c267cc9242a12a4dac2959f98
Normal file
BIN
corpus/01cab80c8efd804c267cc9242a12a4dac2959f98
Normal file
Binary file not shown.
BIN
corpus/027cb1a49430f1677a7bb0510841e2078c69a40e
Normal file
BIN
corpus/027cb1a49430f1677a7bb0510841e2078c69a40e
Normal file
Binary file not shown.
BIN
corpus/037a852532d83bba4b2a366b1c2e88902ec43a62
Normal file
BIN
corpus/037a852532d83bba4b2a366b1c2e88902ec43a62
Normal file
Binary file not shown.
BIN
corpus/04cddf0d0e2f0466d26efa1595a76858bcde4c94
Normal file
BIN
corpus/04cddf0d0e2f0466d26efa1595a76858bcde4c94
Normal file
Binary file not shown.
@@ -1 +0,0 @@
|
||||
:
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
corpus/076b27f409c8bda741fb719e5d10681e5ae1db31
Normal file
BIN
corpus/076b27f409c8bda741fb719e5d10681e5ae1db31
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
corpus/091f1883731c2f0ba9d3705b44bfcd6dd3bf88db
Normal file
BIN
corpus/091f1883731c2f0ba9d3705b44bfcd6dd3bf88db
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
corpus/0ae06dc325d95c19967ac97b3de10c2fc8983b1b
Normal file
BIN
corpus/0ae06dc325d95c19967ac97b3de10c2fc8983b1b
Normal file
Binary file not shown.
BIN
corpus/0b626de7d1730e4a677757381713ba32ddbc943c
Normal file
BIN
corpus/0b626de7d1730e4a677757381713ba32ddbc943c
Normal file
Binary file not shown.
BIN
corpus/0b6558613333c201962d579fad084b280bd96aa7
Normal file
BIN
corpus/0b6558613333c201962d579fad084b280bd96aa7
Normal file
Binary file not shown.
BIN
corpus/0b82dea314f067dc8fd7b52459c1b855c784fde4
Normal file
BIN
corpus/0b82dea314f067dc8fd7b52459c1b855c784fde4
Normal file
Binary file not shown.
BIN
corpus/0bc38a2aff322bfcf5ca402f996ba12f8daf31d1
Normal file
BIN
corpus/0bc38a2aff322bfcf5ca402f996ba12f8daf31d1
Normal file
Binary file not shown.
Binary file not shown.
BIN
corpus/0cabceb6692bc5ad27cffc1f00d412e6c1b0afc0
Normal file
BIN
corpus/0cabceb6692bc5ad27cffc1f00d412e6c1b0afc0
Normal file
Binary file not shown.
BIN
corpus/0cb1c7a2c5ad6f089cd3b2d48c658974aa338b2c
Normal file
BIN
corpus/0cb1c7a2c5ad6f089cd3b2d48c658974aa338b2c
Normal file
Binary file not shown.
Binary file not shown.
BIN
corpus/0da6dc7ca616a5de655f860192079e4859382371
Normal file
BIN
corpus/0da6dc7ca616a5de655f860192079e4859382371
Normal file
Binary file not shown.
Binary file not shown.
BIN
corpus/0ef85a238153205a34565c63f4ad6c373a90b73e
Normal file
BIN
corpus/0ef85a238153205a34565c63f4ad6c373a90b73e
Normal file
Binary file not shown.
BIN
corpus/0f045e5e1a36ee449803f31d0ec334fb1218cc33
Normal file
BIN
corpus/0f045e5e1a36ee449803f31d0ec334fb1218cc33
Normal file
Binary file not shown.
BIN
corpus/0f2e401e0fe0e1d6267142355cc156ee7d2c3c87
Normal file
BIN
corpus/0f2e401e0fe0e1d6267142355cc156ee7d2c3c87
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
corpus/0fc692696f94afbb5d2027bc12fa3dc4a19ac3a9
Normal file
BIN
corpus/0fc692696f94afbb5d2027bc12fa3dc4a19ac3a9
Normal file
Binary file not shown.
Binary file not shown.
BIN
corpus/113ddcf047b2f8c3684853b0f086ead6f056fac2
Normal file
BIN
corpus/113ddcf047b2f8c3684853b0f086ead6f056fac2
Normal file
Binary file not shown.
BIN
corpus/11510f8502ab47f7d57cc205f3d6af50f36eb98c
Normal file
BIN
corpus/11510f8502ab47f7d57cc205f3d6af50f36eb98c
Normal file
Binary file not shown.
BIN
corpus/11919ca53b7efc88c8501dddde8fad916197f54c
Normal file
BIN
corpus/11919ca53b7efc88c8501dddde8fad916197f54c
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
corpus/12a815319620aa136dd77ceb9f6389fd74765f8d
Normal file
BIN
corpus/12a815319620aa136dd77ceb9f6389fd74765f8d
Normal file
Binary file not shown.
BIN
corpus/133c0de112bdcb419b188a1fe8574941517764e8
Normal file
BIN
corpus/133c0de112bdcb419b188a1fe8574941517764e8
Normal file
Binary file not shown.
BIN
corpus/135debe2b0cde6a9d63eb07116d7c85c8dbcc268
Normal file
BIN
corpus/135debe2b0cde6a9d63eb07116d7c85c8dbcc268
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
corpus/14ba3bc0137f0791498781c534a4a81c41e5f565
Normal file
BIN
corpus/14ba3bc0137f0791498781c534a4a81c41e5f565
Normal file
Binary file not shown.
Binary file not shown.
BIN
corpus/1534c7f0f2bac8015e9df650deb62fe383c58440
Normal file
BIN
corpus/1534c7f0f2bac8015e9df650deb62fe383c58440
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
corpus/1681caabc3d5e8d79b4dd79a465a3678bc6ab498
Normal file
BIN
corpus/1681caabc3d5e8d79b4dd79a465a3678bc6ab498
Normal file
Binary file not shown.
Binary file not shown.
BIN
corpus/16da9b15792f7c488d3d9f8354199cc512cd012f
Normal file
BIN
corpus/16da9b15792f7c488d3d9f8354199cc512cd012f
Normal file
Binary file not shown.
BIN
corpus/17f44a42bb6e083988b3eb00ee6f14dccc10bcbf
Normal file
BIN
corpus/17f44a42bb6e083988b3eb00ee6f14dccc10bcbf
Normal file
Binary file not shown.
BIN
corpus/180e912ab1eee5ccaa1cab79c9fd4d81d2009b6a
Normal file
BIN
corpus/180e912ab1eee5ccaa1cab79c9fd4d81d2009b6a
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
corpus/1906d18823ade0beb690c68d8585dfb3b565956f
Normal file
BIN
corpus/1906d18823ade0beb690c68d8585dfb3b565956f
Normal file
Binary file not shown.
Binary file not shown.
BIN
corpus/199d8d206d74e0e77ac21334cfe398a3885d84a2
Normal file
BIN
corpus/199d8d206d74e0e77ac21334cfe398a3885d84a2
Normal file
Binary file not shown.
Binary file not shown.
BIN
corpus/1ae168891529281e4d601dbbe83a064fb330e722
Normal file
BIN
corpus/1ae168891529281e4d601dbbe83a064fb330e722
Normal file
Binary file not shown.
BIN
corpus/1af2516afd85fafa8a6c40dae32059f8b60536b0
Normal file
BIN
corpus/1af2516afd85fafa8a6c40dae32059f8b60536b0
Normal file
Binary file not shown.
BIN
corpus/1af26a82f432e06da3d093435c1baa2251955ee7
Normal file
BIN
corpus/1af26a82f432e06da3d093435c1baa2251955ee7
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
corpus/1c2b4d5da82dc79063550e5cf0c15b867098b47c
Normal file
BIN
corpus/1c2b4d5da82dc79063550e5cf0c15b867098b47c
Normal file
Binary file not shown.
Binary file not shown.
BIN
corpus/1cbd253ecbe54b30051695216915f273716fb7eb
Normal file
BIN
corpus/1cbd253ecbe54b30051695216915f273716fb7eb
Normal file
Binary file not shown.
BIN
corpus/1d4c3da489e29554871120e2f9eb1227c92e9737
Normal file
BIN
corpus/1d4c3da489e29554871120e2f9eb1227c92e9737
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user