Compare commits
199 Commits
1a51aa00e5
...
v0.0.7
Author | SHA1 | Date | |
---|---|---|---|
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 |
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
|
||||||
|
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);
|
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,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();
|
||||||
|
}
|
||||||
|
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.7
|
||||||
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)
|
||||||
|
2403
ConflictSet.cpp
2403
ConflictSet.cpp
File diff suppressed because it is too large
Load Diff
@@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
316
Internal.h
316
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>
|
||||||
@@ -35,6 +39,17 @@ operator<=>(const std::span<const uint8_t> &lhs,
|
|||||||
return lhs.size() <=> rhs.size();
|
return lhs.size() <=> rhs.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline auto operator<=>(const std::span<const uint8_t> &lhs,
|
||||||
|
const ConflictSet::Key &rhs) noexcept {
|
||||||
|
int cl = std::min<int>(lhs.size(), rhs.len);
|
||||||
|
if (cl > 0) {
|
||||||
|
if (auto c = memcmp(lhs.data(), rhs.p, cl) <=> 0; c != 0) {
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return lhs.size() <=> size_t(rhs.len);
|
||||||
|
}
|
||||||
|
|
||||||
// This header contains code that we want to reuse outside of ConflictSet.cpp or
|
// This header contains code that we want to reuse outside of ConflictSet.cpp or
|
||||||
// want to exclude from coverage since it's only testing related.
|
// want to exclude from coverage since it's only testing related.
|
||||||
|
|
||||||
@@ -43,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 ====================
|
||||||
@@ -127,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 -
|
||||||
@@ -135,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);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -150,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);
|
||||||
@@ -166,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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -191,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);
|
||||||
@@ -230,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));
|
||||||
@@ -386,42 +466,16 @@ inline uint32_t Arbitrary::bounded(uint32_t s) {
|
|||||||
|
|
||||||
// ==================== END ARBITRARY IMPL ====================
|
// ==================== END ARBITRARY IMPL ====================
|
||||||
|
|
||||||
// ==================== BEGIN UTILITIES IMPL ====================
|
|
||||||
|
|
||||||
// Call Stepwise::step for each element of remaining until it returns true.
|
|
||||||
// Applies a permutation to `remaining` as a side effect.
|
|
||||||
template <class Stepwise> void runInterleaved(std::span<Stepwise> remaining) {
|
|
||||||
while (remaining.size() > 0) {
|
|
||||||
for (int i = 0; i < int(remaining.size());) {
|
|
||||||
bool done = remaining[i].step();
|
|
||||||
if (done) {
|
|
||||||
if (i != int(remaining.size()) - 1) {
|
|
||||||
using std::swap;
|
|
||||||
swap(remaining[i], remaining.back());
|
|
||||||
}
|
|
||||||
remaining = remaining.subspan(0, remaining.size() - 1);
|
|
||||||
} else {
|
|
||||||
++i;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
template <class Stepwise> void runSequential(std::span<Stepwise> remaining) {
|
|
||||||
for (auto &r : remaining) {
|
|
||||||
while (!r.step()) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct ReferenceImpl {
|
struct ReferenceImpl {
|
||||||
explicit ReferenceImpl(int64_t oldestVersion) : oldestVersion(oldestVersion) {
|
explicit ReferenceImpl(int64_t oldestVersion)
|
||||||
|
: oldestVersion(oldestVersion), newestVersion(oldestVersion) {
|
||||||
writeVersionMap[""] = oldestVersion;
|
writeVersionMap[""] = oldestVersion;
|
||||||
}
|
}
|
||||||
void check(const ConflictSet::ReadRange *reads, ConflictSet::Result *results,
|
void check(const ConflictSet::ReadRange *reads, ConflictSet::Result *results,
|
||||||
int count) const {
|
int count) const {
|
||||||
for (int i = 0; i < count; ++i) {
|
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;
|
results[i] = ConflictSet::TooOld;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -443,6 +497,8 @@ struct ReferenceImpl {
|
|||||||
}
|
}
|
||||||
void addWrites(const ConflictSet::WriteRange *writes, int count,
|
void addWrites(const ConflictSet::WriteRange *writes, int count,
|
||||||
int64_t writeVersion) {
|
int64_t writeVersion) {
|
||||||
|
assert(writeVersion >= newestVersion);
|
||||||
|
newestVersion = writeVersion;
|
||||||
for (int i = 0; i < count; ++i) {
|
for (int i = 0; i < count; ++i) {
|
||||||
auto begin =
|
auto begin =
|
||||||
std::string((const char *)writes[i].begin.p, writes[i].begin.len);
|
std::string((const char *)writes[i].begin.p, writes[i].begin.len);
|
||||||
@@ -462,11 +518,12 @@ struct ReferenceImpl {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void setOldestVersion(int64_t oldestVersion) {
|
void setOldestVersion(int64_t oldestVersion) {
|
||||||
assert(oldestVersion >= oldestVersion);
|
assert(oldestVersion >= this->oldestVersion);
|
||||||
this->oldestVersion = oldestVersion;
|
this->oldestVersion = oldestVersion;
|
||||||
}
|
}
|
||||||
|
|
||||||
int64_t oldestVersion;
|
int64_t oldestVersion;
|
||||||
|
int64_t newestVersion;
|
||||||
std::map<std::string, int64_t> writeVersionMap;
|
std::map<std::string, int64_t> writeVersionMap;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -507,6 +564,18 @@ inline std::string printable(std::span<const uint8_t> key) {
|
|||||||
return printable(std::string_view((const char *)key.data(), key.size()));
|
return printable(std::string_view((const char *)key.data(), key.size()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline const char *resultToStr(ConflictSet::Result r) {
|
||||||
|
switch (r) {
|
||||||
|
case ConflictSet::Commit:
|
||||||
|
return "commit";
|
||||||
|
case ConflictSet::Conflict:
|
||||||
|
return "conflict";
|
||||||
|
case ConflictSet::TooOld:
|
||||||
|
return "too old";
|
||||||
|
}
|
||||||
|
abort();
|
||||||
|
}
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
template <class ConflictSetImpl> struct TestDriver {
|
template <class ConflictSetImpl> struct TestDriver {
|
||||||
@@ -514,30 +583,22 @@ 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 oldestVersion = arbitrary.next();
|
||||||
int64_t oldestVersion = 0;
|
int64_t writeVersion = oldestVersion;
|
||||||
ConflictSetImpl cs{oldestVersion};
|
ConflictSetImpl cs{oldestVersion};
|
||||||
ReferenceImpl refImpl{oldestVersion};
|
ReferenceImpl refImpl{oldestVersion};
|
||||||
|
|
||||||
constexpr static auto kMaxKeyLen = 128;
|
constexpr static auto kMaxKeySuffixLen = 8;
|
||||||
|
|
||||||
bool ok = true;
|
bool ok = true;
|
||||||
|
|
||||||
static const char *resultToStr(ConflictSet::Result r) {
|
const int prefixLen = arbitrary.bounded(512);
|
||||||
switch (r) {
|
const int prefixByte = arbitrary.randT<uint8_t>();
|
||||||
case ConflictSet::Commit:
|
|
||||||
return "commit";
|
|
||||||
case ConflictSet::Conflict:
|
|
||||||
return "conflict";
|
|
||||||
case ConflictSet::TooOld:
|
|
||||||
return "too old";
|
|
||||||
}
|
|
||||||
abort();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Call until it returns true, for "done". Check internal invariants etc
|
// Call until it returns true, for "done". Check internal invariants etc
|
||||||
// between calls to next.
|
// between calls to next.
|
||||||
bool next() {
|
bool next() {
|
||||||
|
assert(cs.getBytes() >= 0);
|
||||||
if (!arbitrary.hasEntropy()) {
|
if (!arbitrary.hasEntropy()) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -545,7 +606,8 @@ 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) ? arbitrary.bounded(10)
|
||||||
|
: arbitrary.next());
|
||||||
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);
|
||||||
@@ -553,9 +615,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));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -586,33 +649,41 @@ template <class ConflictSetImpl> struct TestDriver {
|
|||||||
}
|
}
|
||||||
#if DEBUG_VERBOSE && !defined(NDEBUG)
|
#if DEBUG_VERBOSE && !defined(NDEBUG)
|
||||||
if (writes[i].end.len == 0) {
|
if (writes[i].end.len == 0) {
|
||||||
fprintf(stderr, "Write: {%s} -> %" PRId64 "\n",
|
fprintf(stderr, "Write: {%s}\n", printable(writes[i].begin).c_str());
|
||||||
printable(writes[i].begin).c_str(), writeVersion);
|
|
||||||
} else {
|
} else {
|
||||||
fprintf(stderr, "Write: [%s, %s) -> %" PRId64 "\n",
|
fprintf(stderr, "Write: [%s, %s)\n",
|
||||||
printable(writes[i].begin).c_str(),
|
printable(writes[i].begin).c_str(),
|
||||||
printable(writes[i].end).c_str(), writeVersion);
|
printable(writes[i].end).c_str());
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
assert(iter == keys.end());
|
assert(iter == keys.end());
|
||||||
assert(i == numPointWrites + numRangeWrites);
|
assert(i == numPointWrites + numRangeWrites);
|
||||||
|
|
||||||
|
#if DEBUG_VERBOSE && !defined(NDEBUG)
|
||||||
|
fprintf(stderr, "Write @ %" PRId64 "\n", v);
|
||||||
|
#endif
|
||||||
|
|
||||||
CALLGRIND_START_INSTRUMENTATION;
|
CALLGRIND_START_INSTRUMENTATION;
|
||||||
cs.addWrites(writes, numPointWrites + numRangeWrites, v);
|
cs.addWrites(writes, numPointWrites + numRangeWrites, v);
|
||||||
CALLGRIND_STOP_INSTRUMENTATION;
|
CALLGRIND_STOP_INSTRUMENTATION;
|
||||||
|
|
||||||
refImpl.addWrites(writes, numPointWrites + numRangeWrites, v);
|
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);
|
cs.setOldestVersion(oldestVersion);
|
||||||
refImpl.setOldestVersion(oldestVersion);
|
refImpl.setOldestVersion(oldestVersion);
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
int numPointReads = arbitrary.bounded(100);
|
int numPointReads = arbitrary.bounded(100);
|
||||||
int numRangeReads = 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 =
|
auto *reads =
|
||||||
new (arena) ConflictSet::ReadRange[numPointReads + numRangeReads];
|
new (arena) ConflictSet::ReadRange[numPointReads + numRangeReads];
|
||||||
auto keys = set<std::string_view>(arena);
|
auto keys = set<std::string_view>(arena);
|
||||||
@@ -620,9 +691,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));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -653,12 +725,12 @@ template <class ConflictSetImpl> struct TestDriver {
|
|||||||
reads[i].readVersion = v;
|
reads[i].readVersion = v;
|
||||||
#if DEBUG_VERBOSE && !defined(NDEBUG)
|
#if DEBUG_VERBOSE && !defined(NDEBUG)
|
||||||
if (reads[i].end.len == 0) {
|
if (reads[i].end.len == 0) {
|
||||||
fprintf(stderr, "Read: {%s} @ %d\n",
|
fprintf(stderr, "Read: {%s} @ %" PRId64 "\n",
|
||||||
printable(reads[i].begin).c_str(), int(reads[i].readVersion));
|
printable(reads[i].begin).c_str(), reads[i].readVersion);
|
||||||
} else {
|
} else {
|
||||||
fprintf(stderr, "Read: [%s, %s) @ %d\n",
|
fprintf(stderr, "Read: [%s, %s) @ %" PRId64 "\n",
|
||||||
printable(reads[i].begin).c_str(),
|
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
|
#endif
|
||||||
}
|
}
|
||||||
@@ -669,32 +741,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;
|
||||||
}
|
}
|
||||||
|
30
Jenkinsfile
vendored
30
Jenkinsfile
vendored
@@ -48,6 +48,28 @@ 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('32-bit versions') {
|
||||||
|
agent {
|
||||||
|
dockerfile {
|
||||||
|
args '-v /home/jenkins/ccache:/ccache'
|
||||||
|
reuseNode true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
steps {
|
||||||
|
CleanBuildAndTest("-DUSE_32_BIT_VERSIONS=ON")
|
||||||
|
}
|
||||||
|
}
|
||||||
stage('Release [gcc]') {
|
stage('Release [gcc]') {
|
||||||
agent {
|
agent {
|
||||||
dockerfile {
|
dockerfile {
|
||||||
@@ -97,9 +119,13 @@ pipeline {
|
|||||||
steps {
|
steps {
|
||||||
CleanBuildAndTest("-DCMAKE_C_COMPILER=gcc -DCMAKE_CXX_COMPILER=g++ -DCMAKE_C_FLAGS=--coverage -DCMAKE_CXX_FLAGS=--coverage -DCMAKE_BUILD_TYPE=Debug")
|
CleanBuildAndTest("-DCMAKE_C_COMPILER=gcc -DCMAKE_CXX_COMPILER=g++ -DCMAKE_C_FLAGS=--coverage -DCMAKE_CXX_FLAGS=--coverage -DCMAKE_BUILD_TYPE=Debug")
|
||||||
sh '''
|
sh '''
|
||||||
gcovr --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
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
110
README.md
110
README.md
@@ -9,49 +9,49 @@ Hardware for all benchmarks is a mac m1 2020.
|
|||||||
## Skip list
|
## Skip list
|
||||||
|
|
||||||
```
|
```
|
||||||
New conflict set: 1.962 sec
|
New conflict set: 1.957 sec
|
||||||
0.637 Mtransactions/sec
|
0.639 Mtransactions/sec
|
||||||
2.549 Mkeys/sec
|
2.555 Mkeys/sec
|
||||||
Detect only: 1.853 sec
|
Detect only: 1.845 sec
|
||||||
0.674 Mtransactions/sec
|
0.678 Mtransactions/sec
|
||||||
2.698 Mkeys/sec
|
2.710 Mkeys/sec
|
||||||
Skiplist only: 1.269 sec
|
Skiplist only: 1.263 sec
|
||||||
0.985 Mtransactions/sec
|
0.990 Mtransactions/sec
|
||||||
3.940 Mkeys/sec
|
3.960 Mkeys/sec
|
||||||
Performance counters:
|
Performance counters:
|
||||||
Build: 0.0526
|
Build: 0.0546
|
||||||
Add: 0.054
|
Add: 0.0563
|
||||||
Detect: 1.85
|
Detect: 1.84
|
||||||
D.Sort: 0.413
|
D.Sort: 0.412
|
||||||
D.Combine: 0.0148
|
D.Combine: 0.0141
|
||||||
D.CheckRead: 0.678
|
D.CheckRead: 0.671
|
||||||
D.CheckIntraBatch: 0.00679
|
D.CheckIntraBatch: 0.0068
|
||||||
D.MergeWrite: 0.591
|
D.MergeWrite: 0.592
|
||||||
D.RemoveBefore: 0.147
|
D.RemoveBefore: 0.146
|
||||||
```
|
```
|
||||||
|
|
||||||
## Radix tree (this implementation)
|
## Radix tree (this implementation)
|
||||||
|
|
||||||
```
|
```
|
||||||
New conflict set: 1.342 sec
|
New conflict set: 1.366 sec
|
||||||
0.931 Mtransactions/sec
|
0.915 Mtransactions/sec
|
||||||
3.726 Mkeys/sec
|
3.660 Mkeys/sec
|
||||||
Detect only: 1.227 sec
|
Detect only: 1.248 sec
|
||||||
1.018 Mtransactions/sec
|
1.002 Mtransactions/sec
|
||||||
4.074 Mkeys/sec
|
4.007 Mkeys/sec
|
||||||
Skiplist only: 0.576 sec
|
Skiplist only: 0.573 sec
|
||||||
2.169 Mtransactions/sec
|
2.182 Mtransactions/sec
|
||||||
8.676 Mkeys/sec
|
8.730 Mkeys/sec
|
||||||
Performance counters:
|
Performance counters:
|
||||||
Build: 0.0568
|
Build: 0.0594
|
||||||
Add: 0.0564
|
Add: 0.0572
|
||||||
Detect: 1.23
|
Detect: 1.25
|
||||||
D.Sort: 0.414
|
D.Sort: 0.418
|
||||||
D.Combine: 0.0162
|
D.Combine: 0.0149
|
||||||
D.CheckRead: 0.228
|
D.CheckRead: 0.232
|
||||||
D.CheckIntraBatch: 0.00665
|
D.CheckIntraBatch: 0.0067
|
||||||
D.MergeWrite: 0.348
|
D.MergeWrite: 0.341
|
||||||
D.RemoveBefore: 0.211
|
D.RemoveBefore: 0.232
|
||||||
```
|
```
|
||||||
|
|
||||||
# Our benchmark
|
# Our benchmark
|
||||||
@@ -60,25 +60,27 @@ Performance counters:
|
|||||||
|
|
||||||
| ns/op | op/s | err% | total | benchmark
|
| ns/op | op/s | err% | total | benchmark
|
||||||
|--------------------:|--------------------:|--------:|----------:|:----------
|
|--------------------:|--------------------:|--------:|----------:|:----------
|
||||||
| 254.17 | 3,934,426.23 | 0.3% | 0.01 | `point reads`
|
| 256.89 | 3,892,784.92 | 0.3% | 0.01 | `point reads`
|
||||||
| 275.58 | 3,628,769.92 | 2.1% | 0.01 | `prefix reads`
|
| 272.90 | 3,664,395.04 | 0.2% | 0.01 | `prefix reads`
|
||||||
| 494.87 | 2,020,718.51 | 0.7% | 0.01 | `range reads`
|
| 507.22 | 1,971,549.50 | 0.7% | 0.01 | `range reads`
|
||||||
| 495.91 | 2,016,512.61 | 1.7% | 0.01 | `point writes`
|
| 452.66 | 2,209,181.91 | 0.5% | 0.01 | `point writes`
|
||||||
| 478.11 | 2,091,578.00 | 0.8% | 0.01 | `prefix writes`
|
| 438.09 | 2,282,619.96 | 0.4% | 0.01 | `prefix writes`
|
||||||
| 307.08 | 3,256,480.40 | 1.9% | 0.04 | `range writes`
|
| 253.33 | 3,947,420.36 | 2.5% | 0.02 | `range writes`
|
||||||
| 610.89 | 1,636,953.81 | 0.7% | 0.01 | `monotonic increasing point writes`
|
| 574.07 | 1,741,936.71 | 0.3% | 0.01 | `monotonic increasing point writes`
|
||||||
|
| 151,562.50 | 6,597.94 | 1.5% | 0.01 | `worst case for radix tree`
|
||||||
|
|
||||||
## Radix tree (this implementation)
|
## Radix tree (this implementation)
|
||||||
|
|
||||||
| ns/op | op/s | err% | total | benchmark
|
| ns/op | op/s | err% | total | benchmark
|
||||||
|--------------------:|--------------------:|--------:|----------:|:----------
|
|--------------------:|--------------------:|--------:|----------:|:----------
|
||||||
| 22.39 | 44,667,832.17 | 0.2% | 0.01 | `point reads`
|
| 19.83 | 50,420,955.28 | 0.1% | 0.01 | `point reads`
|
||||||
| 53.89 | 18,554,840.41 | 3.4% | 0.01 | `prefix reads`
|
| 55.95 | 17,872,542.40 | 0.5% | 0.01 | `prefix reads`
|
||||||
| 217.71 | 4,593,220.24 | 0.2% | 0.01 | `range reads`
|
| 88.28 | 11,327,709.50 | 0.4% | 0.01 | `range reads`
|
||||||
| 29.72 | 33,642,099.66 | 1.1% | 0.01 | `point writes`
|
| 29.15 | 34,309,531.64 | 0.5% | 0.01 | `point writes`
|
||||||
| 44.86 | 22,294,037.02 | 0.8% | 0.01 | `prefix writes`
|
| 42.36 | 23,607,424.27 | 1.1% | 0.01 | `prefix writes`
|
||||||
| 55.00 | 18,181,818.18 | 0.8% | 0.03 | `range writes`
|
| 50.00 | 20,000,000.00 | 0.0% | 0.01 | `range writes`
|
||||||
| 109.21 | 9,156,917.53 | 2.9% | 0.01 | `monotonic increasing point writes`
|
| 93.52 | 10,692,413.79 | 3.3% | 0.01 | `monotonic increasing point writes`
|
||||||
|
| 2,388,417.00 | 418.69 | 0.4% | 0.03 | `worst case for radix tree`
|
||||||
|
|
||||||
# "Real data" test
|
# "Real data" test
|
||||||
|
|
||||||
@@ -87,13 +89,13 @@ Point queries only, best of three runs. Gc ratio is the ratio of time spent doin
|
|||||||
## skip list
|
## skip list
|
||||||
|
|
||||||
```
|
```
|
||||||
Check: 11.7282 seconds, 318.763 MB/s, Add: 5.76499 seconds, 121.776 MB/s, Gc ratio: 48.0772%
|
Check: 11.3385 seconds, 329.718 MB/s, Add: 5.35612 seconds, 131.072 MB/s, Gc ratio: 45.7173%
|
||||||
```
|
```
|
||||||
|
|
||||||
## radix tree
|
## radix tree
|
||||||
|
|
||||||
```
|
```
|
||||||
Check: 3.27475 seconds, 1141.62 MB/s, Add: 2.13594 seconds, 328.678 MB/s, Gc ratio: 50.1739%
|
Check: 2.48583 seconds, 1503.93 MB/s, Add: 2.12768 seconds, 329.954 MB/s, Gc ratio: 41.7943%
|
||||||
```
|
```
|
||||||
|
|
||||||
## hash table
|
## hash table
|
||||||
@@ -101,5 +103,5 @@ Check: 3.27475 seconds, 1141.62 MB/s, Add: 2.13594 seconds, 328.678 MB/s, Gc rat
|
|||||||
(The hash table implementation doesn't work on range queries, and its purpose is to provide an idea of how fast point queries can be)
|
(The hash table implementation doesn't work on range queries, and its purpose is to provide an idea of how fast point queries can be)
|
||||||
|
|
||||||
```
|
```
|
||||||
Check: 1.86291 seconds, 2006.81 MB/s, Add: 0.923653 seconds, 760.067 MB/s, Gc ratio: 54.2605%
|
Check: 1.83386 seconds, 2038.6 MB/s, Add: 0.601411 seconds, 1167.32 MB/s, Gc ratio: 48.9776%
|
||||||
```
|
```
|
||||||
|
@@ -1,13 +1,18 @@
|
|||||||
#include <ConflictSet.h>
|
#include <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));
|
||||||
|
}
|
||||||
|
136
ScriptTest.cpp
136
ScriptTest.cpp
@@ -1,136 +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;
|
|
||||||
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");
|
|
||||||
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");
|
|
||||||
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) {
|
|
||||||
assert(expected[i] == actual[i]);
|
|
||||||
}
|
|
||||||
readRanges = {};
|
|
||||||
} else if (line.starts_with("addwrites"_v)) {
|
|
||||||
printf("addwrites\n");
|
|
||||||
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");
|
|
||||||
cs.setOldestVersion(v);
|
|
||||||
ref.setOldestVersion(v);
|
|
||||||
} else {
|
|
||||||
printf("Unrecognized line: %.*s\n", int(line.size()), line.data());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
munmap((void *)mapOriginal, sizeOriginal);
|
|
||||||
close(fd);
|
|
||||||
}
|
|
||||||
}
|
|
106
SkipList.cpp
106
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;
|
||||||
}
|
}
|
||||||
@@ -567,7 +577,8 @@ struct SkipListConflictSet {};
|
|||||||
|
|
||||||
struct __attribute__((visibility("hidden"))) ConflictSet::Impl {
|
struct __attribute__((visibility("hidden"))) ConflictSet::Impl {
|
||||||
Impl(int64_t oldestVersion)
|
Impl(int64_t oldestVersion)
|
||||||
: oldestVersion(oldestVersion), skipList(oldestVersion) {}
|
: oldestVersion(oldestVersion), newestVersion(oldestVersion),
|
||||||
|
skipList(oldestVersion) {}
|
||||||
void check(const ConflictSet::ReadRange *reads, ConflictSet::Result *results,
|
void check(const ConflictSet::ReadRange *reads, ConflictSet::Result *results,
|
||||||
int count) const {
|
int count) const {
|
||||||
Arena arena;
|
Arena arena;
|
||||||
@@ -582,7 +593,8 @@ struct __attribute__((visibility("hidden"))) ConflictSet::Impl {
|
|||||||
}
|
}
|
||||||
skipList.detectConflicts(ranges, count, results);
|
skipList.detectConflicts(ranges, count, results);
|
||||||
for (int i = 0; i < count; ++i) {
|
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;
|
results[i] = TooOld;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -590,6 +602,8 @@ struct __attribute__((visibility("hidden"))) ConflictSet::Impl {
|
|||||||
|
|
||||||
void addWrites(const ConflictSet::WriteRange *writes, int count,
|
void addWrites(const ConflictSet::WriteRange *writes, int count,
|
||||||
int64_t writeVersion) {
|
int64_t writeVersion) {
|
||||||
|
assert(writeVersion >= newestVersion);
|
||||||
|
newestVersion = writeVersion;
|
||||||
Arena arena;
|
Arena arena;
|
||||||
const int stringCount = count * 2;
|
const int stringCount = count * 2;
|
||||||
|
|
||||||
@@ -603,6 +617,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)}
|
||||||
@@ -616,6 +634,7 @@ struct __attribute__((visibility("hidden"))) ConflictSet::Impl {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void setOldestVersion(int64_t oldestVersion) {
|
void setOldestVersion(int64_t oldestVersion) {
|
||||||
|
assert(oldestVersion >= this->oldestVersion);
|
||||||
this->oldestVersion = oldestVersion;
|
this->oldestVersion = oldestVersion;
|
||||||
SkipList::Finger finger;
|
SkipList::Finger finger;
|
||||||
int temp;
|
int temp;
|
||||||
@@ -627,35 +646,84 @@ 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;
|
||||||
std::span<const uint8_t> removalKey;
|
std::span<const uint8_t> removalKey;
|
||||||
int64_t oldestVersion;
|
int64_t oldestVersion;
|
||||||
|
int64_t newestVersion;
|
||||||
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 +744,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
|
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_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/0063b93252ca70953300cad44698a17baad92efa
Normal file
BIN
corpus/0063b93252ca70953300cad44698a17baad92efa
Normal file
Binary file not shown.
BIN
corpus/007b752a8b713ca7d9c9ad9f437af86372842294
Normal file
BIN
corpus/007b752a8b713ca7d9c9ad9f437af86372842294
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
corpus/0125f120343f7f62c8ba06a5dda992948aff6976
Normal file
BIN
corpus/0125f120343f7f62c8ba06a5dda992948aff6976
Normal file
Binary file not shown.
Binary file not shown.
BIN
corpus/0238340d4a6e588e2f721e375916197806e9e4a2
Normal file
BIN
corpus/0238340d4a6e588e2f721e375916197806e9e4a2
Normal file
Binary file not shown.
BIN
corpus/028811e14d54e1094773c8b099968d4772debc6a
Normal file
BIN
corpus/028811e14d54e1094773c8b099968d4772debc6a
Normal file
Binary file not shown.
BIN
corpus/02feb57a35b1aff93e0a39b1c8bdbe4fb4c68bb3
Normal file
BIN
corpus/02feb57a35b1aff93e0a39b1c8bdbe4fb4c68bb3
Normal file
Binary file not shown.
BIN
corpus/04ee8a49d63dde5f8fb0217581aac1349c8bef6e
Normal file
BIN
corpus/04ee8a49d63dde5f8fb0217581aac1349c8bef6e
Normal file
Binary file not shown.
@@ -1 +0,0 @@
|
|||||||
:
|
|
Binary file not shown.
BIN
corpus/065c5f5e542abe1b5c8b931894a4e3f2089d8be3
Normal file
BIN
corpus/065c5f5e542abe1b5c8b931894a4e3f2089d8be3
Normal file
Binary file not shown.
BIN
corpus/066d231a61a7b4f1fb3b3d6b3feab2360bd0586e
Normal file
BIN
corpus/066d231a61a7b4f1fb3b3d6b3feab2360bd0586e
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
corpus/077281b9908bdf8bc13273ebb8fab9f0e56e6414
Normal file
BIN
corpus/077281b9908bdf8bc13273ebb8fab9f0e56e6414
Normal file
Binary file not shown.
BIN
corpus/07af0cd9aa92b3167dde6cdc24efe99d59f246d1
Normal file
BIN
corpus/07af0cd9aa92b3167dde6cdc24efe99d59f246d1
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.
BIN
corpus/0974b99f57e954e667861e9ae4119e262f2703a8
Normal file
BIN
corpus/0974b99f57e954e667861e9ae4119e262f2703a8
Normal file
Binary file not shown.
Binary file not shown.
BIN
corpus/0a1e8a6619e2097d1d376a53993302878b7cdc5d
Normal file
BIN
corpus/0a1e8a6619e2097d1d376a53993302878b7cdc5d
Normal file
Binary file not shown.
BIN
corpus/0a674f2c935665bcb0ed565f8cb86ac1b35516d8
Normal file
BIN
corpus/0a674f2c935665bcb0ed565f8cb86ac1b35516d8
Normal file
Binary file not shown.
BIN
corpus/0add1f7bc40446f0b565ffa8a01344ef0e6989ca
Normal file
BIN
corpus/0add1f7bc40446f0b565ffa8a01344ef0e6989ca
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
corpus/0d80bd248e2de36bf251d8b7ffe315e0811fce57
Normal file
BIN
corpus/0d80bd248e2de36bf251d8b7ffe315e0811fce57
Normal file
Binary file not shown.
BIN
corpus/0d9431e398a37214417d6bef63ac6c986773b06d
Normal file
BIN
corpus/0d9431e398a37214417d6bef63ac6c986773b06d
Normal file
Binary file not shown.
BIN
corpus/0e33e7ae09777d9c6b81e8cee7d5c4de1c62ff2d
Normal file
BIN
corpus/0e33e7ae09777d9c6b81e8cee7d5c4de1c62ff2d
Normal file
Binary file not shown.
4
corpus/0e410d4488defd877d2c9690a26970f57ab78b0c
Normal file
4
corpus/0e410d4488defd877d2c9690a26970f57ab78b0c
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
*
|
||||||
|
|
||||||
|
|
||||||
|
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
corpus/10571ac120215c69cd97f409bf35143b5b917812
Normal file
BIN
corpus/10571ac120215c69cd97f409bf35143b5b917812
Normal file
Binary file not shown.
BIN
corpus/1095006f5706eb8e27faf1d3c1acd6af1b6718e3
Normal file
BIN
corpus/1095006f5706eb8e27faf1d3c1acd6af1b6718e3
Normal file
Binary file not shown.
Binary file not shown.
BIN
corpus/114c0edc40b90c08e0a4a6f15e8f49764cb5f241
Normal file
BIN
corpus/114c0edc40b90c08e0a4a6f15e8f49764cb5f241
Normal file
Binary file not shown.
Binary file not shown.
BIN
corpus/11f83645fe98bd2419197f30080be51ff76bc6e2
Normal file
BIN
corpus/11f83645fe98bd2419197f30080be51ff76bc6e2
Normal file
Binary file not shown.
Binary file not shown.
BIN
corpus/12b2f725568909759c8eb8d3cd6e762b97adcf8c
Normal file
BIN
corpus/12b2f725568909759c8eb8d3cd6e762b97adcf8c
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
corpus/15ec9e0a7a3ff3be4d02366c14e50256463f5ad9
Normal file
BIN
corpus/15ec9e0a7a3ff3be4d02366c14e50256463f5ad9
Normal file
Binary file not shown.
BIN
corpus/16032af55a737dededa0c4155bf2d21ffb54716c
Normal file
BIN
corpus/16032af55a737dededa0c4155bf2d21ffb54716c
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
corpus/16e88b562a6862401460f25825f2bff30fd55267
Normal file
BIN
corpus/16e88b562a6862401460f25825f2bff30fd55267
Normal file
Binary file not shown.
BIN
corpus/18114c1ea60aec549bd286b1b69419e724e8f21f
Normal file
BIN
corpus/18114c1ea60aec549bd286b1b69419e724e8f21f
Normal file
Binary file not shown.
BIN
corpus/182e6bc1f0cc00a80f5a6b588e0db7e603ab5f67
Normal file
BIN
corpus/182e6bc1f0cc00a80f5a6b588e0db7e603ab5f67
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
corpus/197cfb242d7638d21096b0eed211adbd7a1cf041
Normal file
BIN
corpus/197cfb242d7638d21096b0eed211adbd7a1cf041
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
corpus/1b590e4d1b29a6140055510b81af0ec758197b05
Normal file
BIN
corpus/1b590e4d1b29a6140055510b81af0ec758197b05
Normal file
Binary file not shown.
Binary file not shown.
BIN
corpus/1ddcfcbbf1b9ce188c2509aa99b2d10b26cdec38
Normal file
BIN
corpus/1ddcfcbbf1b9ce188c2509aa99b2d10b26cdec38
Normal file
Binary file not shown.
BIN
corpus/1e272a9a43badbbd8b66eb08ba49fc1650557df1
Normal file
BIN
corpus/1e272a9a43badbbd8b66eb08ba49fc1650557df1
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
corpus/1f3676c7a663a9ef780aa996ea88a905b25bd8d2
Normal file
BIN
corpus/1f3676c7a663a9ef780aa996ea88a905b25bd8d2
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
corpus/20bea5c29b0f49a3a7aa78d2237341895a3cb0da
Normal file
BIN
corpus/20bea5c29b0f49a3a7aa78d2237341895a3cb0da
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