Compare commits
36 Commits
v0.0.13
...
b376f6fdd5
Author | SHA1 | Date | |
---|---|---|---|
b376f6fdd5 | |||
6de63dd3fe | |||
3e5f13bf54 | |||
e7e1d1f7f5 | |||
442658e983 | |||
26f602215e | |||
98236f81cb | |||
3593b72880 | |||
814aac4ea7 | |||
0550fa0016 | |||
fe5cfb0336 | |||
82203515a0 | |||
465372c734 | |||
867136ff1b | |||
4b8f7320d3 | |||
6628092384 | |||
a0a4f1afea | |||
ca479c03ce | |||
0a2e133ab9 | |||
b0b31419b0 | |||
5c0cc1edf5 | |||
de47aa53b0 | |||
56893f9702 | |||
e2234be10f | |||
ce853680f2 | |||
5c39c1d64f | |||
55b73c8ddb | |||
b9503f8258 | |||
c4c4531bd3 | |||
2037d37c66 | |||
6fe6a244af | |||
8a4b370e2a | |||
394f09f9fb | |||
5e06a30357 | |||
cb6e4292f2 | |||
154a48ded0 |
@@ -24,6 +24,14 @@ repos:
|
|||||||
entry: "^#define SHOW_MEMORY 1$"
|
entry: "^#define SHOW_MEMORY 1$"
|
||||||
language: pygrep
|
language: pygrep
|
||||||
types: [c++]
|
types: [c++]
|
||||||
|
- repo: local
|
||||||
|
hooks:
|
||||||
|
- id: sim cache misses check
|
||||||
|
name: disallow checking in SIM_CACHE_MISSES=1
|
||||||
|
description: disallow checking in SIM_CACHE_MISSES=1
|
||||||
|
entry: "^#define SIM_CACHE_MISSES 1$"
|
||||||
|
language: pygrep
|
||||||
|
types: [c++]
|
||||||
- repo: https://github.com/shellcheck-py/shellcheck-py
|
- repo: https://github.com/shellcheck-py/shellcheck-py
|
||||||
rev: a23f6b85d0fdd5bb9d564e2579e678033debbdff # frozen: v0.10.0.1
|
rev: a23f6b85d0fdd5bb9d564e2579e678033debbdff # frozen: v0.10.0.1
|
||||||
hooks:
|
hooks:
|
||||||
|
@@ -7,7 +7,6 @@
|
|||||||
void showMemory(const ConflictSet &cs);
|
void showMemory(const ConflictSet &cs);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#define ANKERL_NANOBENCH_IMPLEMENT
|
|
||||||
#include "third_party/nanobench.h"
|
#include "third_party/nanobench.h"
|
||||||
|
|
||||||
constexpr int kNumKeys = 1000000;
|
constexpr int kNumKeys = 1000000;
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
cmake_minimum_required(VERSION 3.18)
|
cmake_minimum_required(VERSION 3.18)
|
||||||
project(
|
project(
|
||||||
conflict-set
|
conflict-set
|
||||||
VERSION 0.0.13
|
VERSION 0.0.14
|
||||||
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"
|
||||||
@@ -72,12 +72,6 @@ else()
|
|||||||
add_link_options(-Wl,--gc-sections)
|
add_link_options(-Wl,--gc-sections)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(EMSCRIPTEN)
|
|
||||||
# https://github.com/emscripten-core/emscripten/issues/15377#issuecomment-1285167486
|
|
||||||
add_link_options(-lnodefs.js -lnoderawfs.js)
|
|
||||||
add_link_options(-s ALLOW_MEMORY_GROWTH)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
if(NOT USE_SIMD_FALLBACK)
|
if(NOT USE_SIMD_FALLBACK)
|
||||||
cmake_push_check_state()
|
cmake_push_check_state()
|
||||||
list(APPEND CMAKE_REQUIRED_FLAGS -mavx)
|
list(APPEND CMAKE_REQUIRED_FLAGS -mavx)
|
||||||
@@ -144,6 +138,8 @@ include(CTest)
|
|||||||
# disable tests if this is being used through e.g. FetchContent
|
# disable tests if this is being used through e.g. FetchContent
|
||||||
if(CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR AND BUILD_TESTING)
|
if(CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR AND BUILD_TESTING)
|
||||||
|
|
||||||
|
add_library(nanobench ${CMAKE_CURRENT_SOURCE_DIR}/nanobench.cpp)
|
||||||
|
|
||||||
set(TEST_FLAGS -Wall -Wextra -Wunreachable-code -Wpedantic -UNDEBUG)
|
set(TEST_FLAGS -Wall -Wextra -Wunreachable-code -Wpedantic -UNDEBUG)
|
||||||
|
|
||||||
# corpus tests, which are tests curated by libfuzzer. The goal is to get broad
|
# corpus tests, which are tests curated by libfuzzer. The goal is to get broad
|
||||||
@@ -191,6 +187,7 @@ if(CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR AND BUILD_TESTING)
|
|||||||
target_include_directories(conflict_set_main
|
target_include_directories(conflict_set_main
|
||||||
PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include)
|
PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include)
|
||||||
target_compile_definitions(conflict_set_main PRIVATE ENABLE_MAIN)
|
target_compile_definitions(conflict_set_main PRIVATE ENABLE_MAIN)
|
||||||
|
target_link_libraries(conflict_set_main PRIVATE nanobench)
|
||||||
|
|
||||||
if(NOT APPLE)
|
if(NOT APPLE)
|
||||||
# libfuzzer target, to generate/manage corpus
|
# libfuzzer target, to generate/manage corpus
|
||||||
@@ -336,7 +333,7 @@ if(CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR AND BUILD_TESTING)
|
|||||||
|
|
||||||
# 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} nanobench)
|
||||||
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})
|
||||||
@@ -351,6 +348,14 @@ if(CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR AND BUILD_TESTING)
|
|||||||
add_executable(server_bench ServerBench.cpp)
|
add_executable(server_bench ServerBench.cpp)
|
||||||
target_link_libraries(server_bench PRIVATE ${PROJECT_NAME})
|
target_link_libraries(server_bench PRIVATE ${PROJECT_NAME})
|
||||||
set_target_properties(server_bench PROPERTIES SKIP_BUILD_RPATH ON)
|
set_target_properties(server_bench PROPERTIES SKIP_BUILD_RPATH ON)
|
||||||
|
|
||||||
|
add_executable(interleaving_test InterleavingTest.cpp)
|
||||||
|
# work around lack of musttail for gcc
|
||||||
|
if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" AND CMAKE_BUILD_TYPE STREQUAL "Debug")
|
||||||
|
target_compile_options(interleaving_test PRIVATE -Og
|
||||||
|
-foptimize-sibling-calls)
|
||||||
|
endif()
|
||||||
|
target_link_libraries(interleaving_test PRIVATE nanobench)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
# packaging
|
# packaging
|
||||||
|
696
ConflictSet.cpp
696
ConflictSet.cpp
@@ -17,9 +17,9 @@ limitations under the License.
|
|||||||
#include "ConflictSet.h"
|
#include "ConflictSet.h"
|
||||||
#include "Internal.h"
|
#include "Internal.h"
|
||||||
#include "LongestCommonPrefix.h"
|
#include "LongestCommonPrefix.h"
|
||||||
|
#include "Metrics.h"
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <atomic>
|
|
||||||
#include <bit>
|
#include <bit>
|
||||||
#include <cassert>
|
#include <cassert>
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
@@ -48,6 +48,17 @@ limitations under the License.
|
|||||||
#endif
|
#endif
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#define SIM_CACHE_MISSES 0
|
||||||
|
#if SIM_CACHE_MISSES
|
||||||
|
constexpr void simCacheMiss(void *x) {
|
||||||
|
if (x) {
|
||||||
|
_mm_clflush(x);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
constexpr void simCacheMiss(void *) {}
|
||||||
|
#endif
|
||||||
|
|
||||||
#include <memcheck.h>
|
#include <memcheck.h>
|
||||||
|
|
||||||
using namespace weaselab;
|
using namespace weaselab;
|
||||||
@@ -578,39 +589,6 @@ static_assert(kBytesPerKey - sizeof(Node0) >= kMinNodeSurplus);
|
|||||||
|
|
||||||
constexpr int64_t kFreeListMaxMemory = 1 << 20;
|
constexpr int64_t kFreeListMaxMemory = 1 << 20;
|
||||||
|
|
||||||
struct Metric {
|
|
||||||
Metric *prev;
|
|
||||||
const char *name;
|
|
||||||
const char *help;
|
|
||||||
ConflictSet::MetricsV1::Type type;
|
|
||||||
std::atomic<int64_t> value;
|
|
||||||
|
|
||||||
protected:
|
|
||||||
Metric(ConflictSet::Impl *impl, const char *name, const char *help,
|
|
||||||
ConflictSet::MetricsV1::Type type);
|
|
||||||
};
|
|
||||||
|
|
||||||
struct Gauge : private Metric {
|
|
||||||
Gauge(ConflictSet::Impl *impl, const char *name, const char *help)
|
|
||||||
: Metric(impl, name, help, ConflictSet::MetricsV1::Gauge) {}
|
|
||||||
|
|
||||||
void set(int64_t value) {
|
|
||||||
this->value.store(value, std::memory_order_relaxed);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
struct Counter : private Metric {
|
|
||||||
Counter(ConflictSet::Impl *impl, const char *name, const char *help)
|
|
||||||
: Metric(impl, name, help, ConflictSet::MetricsV1::Counter) {}
|
|
||||||
// Expensive. Accumulate locally and then call add instead of repeatedly
|
|
||||||
// calling add.
|
|
||||||
void add(int64_t value) {
|
|
||||||
assert(value >= 0);
|
|
||||||
static_assert(std::atomic<int64_t>::is_always_lock_free);
|
|
||||||
this->value.fetch_add(value, std::memory_order_relaxed);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
template <class T> struct BoundedFreeListAllocator {
|
template <class T> struct BoundedFreeListAllocator {
|
||||||
|
|
||||||
static_assert(sizeof(T) >= sizeof(void *));
|
static_assert(sizeof(T) >= sizeof(void *));
|
||||||
@@ -869,21 +847,29 @@ int getNodeIndex(Node16 *self, uint8_t index) {
|
|||||||
|
|
||||||
// Precondition - an entry for index must exist in the node
|
// Precondition - an entry for index must exist in the node
|
||||||
Node *&getChildExists(Node3 *self, uint8_t index) {
|
Node *&getChildExists(Node3 *self, uint8_t index) {
|
||||||
return self->children[getNodeIndex(self, index)];
|
auto &result = self->children[getNodeIndex(self, index)];
|
||||||
|
simCacheMiss(result);
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
// Precondition - an entry for index must exist in the node
|
// Precondition - an entry for index must exist in the node
|
||||||
Node *&getChildExists(Node16 *self, uint8_t index) {
|
Node *&getChildExists(Node16 *self, uint8_t index) {
|
||||||
return self->children[getNodeIndex(self, index)];
|
auto &result = self->children[getNodeIndex(self, index)];
|
||||||
|
simCacheMiss(result);
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
// Precondition - an entry for index must exist in the node
|
// Precondition - an entry for index must exist in the node
|
||||||
Node *&getChildExists(Node48 *self, uint8_t index) {
|
Node *&getChildExists(Node48 *self, uint8_t index) {
|
||||||
assert(self->bitSet.test(index));
|
assert(self->bitSet.test(index));
|
||||||
return self->children[self->index[index]];
|
auto &result = self->children[self->index[index]];
|
||||||
|
simCacheMiss(result);
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
// Precondition - an entry for index must exist in the node
|
// Precondition - an entry for index must exist in the node
|
||||||
Node *&getChildExists(Node256 *self, uint8_t index) {
|
Node *&getChildExists(Node256 *self, uint8_t index) {
|
||||||
assert(self->bitSet.test(index));
|
assert(self->bitSet.test(index));
|
||||||
return self->children[index];
|
auto &result = self->children[index];
|
||||||
|
simCacheMiss(result);
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Precondition - an entry for index must exist in the node
|
// Precondition - an entry for index must exist in the node
|
||||||
@@ -1063,6 +1049,7 @@ ChildAndMaxVersion getChildAndMaxVersion(Node3 *self, uint8_t index) {
|
|||||||
if (i < 0) {
|
if (i < 0) {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
simCacheMiss(self->children[i]);
|
||||||
return {self->children[i], self->childMaxVersion[i]};
|
return {self->children[i], self->childMaxVersion[i]};
|
||||||
}
|
}
|
||||||
ChildAndMaxVersion getChildAndMaxVersion(Node16 *self, uint8_t index) {
|
ChildAndMaxVersion getChildAndMaxVersion(Node16 *self, uint8_t index) {
|
||||||
@@ -1070,6 +1057,7 @@ ChildAndMaxVersion getChildAndMaxVersion(Node16 *self, uint8_t index) {
|
|||||||
if (i < 0) {
|
if (i < 0) {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
simCacheMiss(self->children[i]);
|
||||||
return {self->children[i], self->childMaxVersion[i]};
|
return {self->children[i], self->childMaxVersion[i]};
|
||||||
}
|
}
|
||||||
ChildAndMaxVersion getChildAndMaxVersion(Node48 *self, uint8_t index) {
|
ChildAndMaxVersion getChildAndMaxVersion(Node48 *self, uint8_t index) {
|
||||||
@@ -1077,9 +1065,11 @@ ChildAndMaxVersion getChildAndMaxVersion(Node48 *self, uint8_t index) {
|
|||||||
if (i < 0) {
|
if (i < 0) {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
simCacheMiss(self->children[i]);
|
||||||
return {self->children[i], self->childMaxVersion[i]};
|
return {self->children[i], self->childMaxVersion[i]};
|
||||||
}
|
}
|
||||||
ChildAndMaxVersion getChildAndMaxVersion(Node256 *self, uint8_t index) {
|
ChildAndMaxVersion getChildAndMaxVersion(Node256 *self, uint8_t index) {
|
||||||
|
simCacheMiss(self->children[index]);
|
||||||
return {self->children[index], self->childMaxVersion[index]};
|
return {self->children[index], self->childMaxVersion[index]};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1105,6 +1095,7 @@ Node *getChildGeq(Node0 *, int) { return nullptr; }
|
|||||||
Node *getChildGeq(Node3 *n, int child) {
|
Node *getChildGeq(Node3 *n, int child) {
|
||||||
for (int i = 0; i < n->numChildren; ++i) {
|
for (int i = 0; i < n->numChildren; ++i) {
|
||||||
if (n->index[i] >= child) {
|
if (n->index[i] >= child) {
|
||||||
|
simCacheMiss(n->children[i]);
|
||||||
return n->children[i];
|
return n->children[i];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1123,7 +1114,10 @@ Node *getChildGeq(Node16 *self, int child) {
|
|||||||
__m128i results = _mm_cmpeq_epi8(key_vec, _mm_min_epu8(key_vec, indices));
|
__m128i results = _mm_cmpeq_epi8(key_vec, _mm_min_epu8(key_vec, indices));
|
||||||
int mask = (1 << self->numChildren) - 1;
|
int mask = (1 << self->numChildren) - 1;
|
||||||
uint32_t bitfield = _mm_movemask_epi8(results) & mask;
|
uint32_t bitfield = _mm_movemask_epi8(results) & mask;
|
||||||
return bitfield == 0 ? nullptr : self->children[std::countr_zero(bitfield)];
|
auto *result =
|
||||||
|
bitfield == 0 ? nullptr : self->children[std::countr_zero(bitfield)];
|
||||||
|
simCacheMiss(result);
|
||||||
|
return result;
|
||||||
#elif defined(HAS_ARM_NEON)
|
#elif defined(HAS_ARM_NEON)
|
||||||
uint8x16_t indices;
|
uint8x16_t indices;
|
||||||
memcpy(&indices, self->index, sizeof(self->index));
|
memcpy(&indices, self->index, sizeof(self->index));
|
||||||
@@ -1159,13 +1153,16 @@ Node *getChildGeq(Node48 *self, int child) {
|
|||||||
if (c < 0) {
|
if (c < 0) {
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
return self->children[self->index[c]];
|
auto *result = self->children[self->index[c]];
|
||||||
|
simCacheMiss(result);
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
Node *getChildGeq(Node256 *self, int child) {
|
Node *getChildGeq(Node256 *self, int child) {
|
||||||
int c = self->bitSet.firstSetGeq(child);
|
int c = self->bitSet.firstSetGeq(child);
|
||||||
if (c < 0) {
|
if (c < 0) {
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
simCacheMiss(self->children[c]);
|
||||||
return self->children[c];
|
return self->children[c];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1189,20 +1186,26 @@ Node *getChildGeq(Node *self, int child) {
|
|||||||
// Precondition: self has a child
|
// Precondition: self has a child
|
||||||
Node *getFirstChildExists(Node3 *self) {
|
Node *getFirstChildExists(Node3 *self) {
|
||||||
assert(self->numChildren > 0);
|
assert(self->numChildren > 0);
|
||||||
|
simCacheMiss(self->children[0]);
|
||||||
return self->children[0];
|
return self->children[0];
|
||||||
}
|
}
|
||||||
// Precondition: self has a child
|
// Precondition: self has a child
|
||||||
Node *getFirstChildExists(Node16 *self) {
|
Node *getFirstChildExists(Node16 *self) {
|
||||||
assert(self->numChildren > 0);
|
assert(self->numChildren > 0);
|
||||||
|
simCacheMiss(self->children[0]);
|
||||||
return self->children[0];
|
return self->children[0];
|
||||||
}
|
}
|
||||||
// Precondition: self has a child
|
// Precondition: self has a child
|
||||||
Node *getFirstChildExists(Node48 *self) {
|
Node *getFirstChildExists(Node48 *self) {
|
||||||
return self->children[self->index[self->bitSet.firstSetGeq(0)]];
|
auto *result = self->children[self->index[self->bitSet.firstSetGeq(0)]];
|
||||||
|
simCacheMiss(result);
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
// Precondition: self has a child
|
// Precondition: self has a child
|
||||||
Node *getFirstChildExists(Node256 *self) {
|
Node *getFirstChildExists(Node256 *self) {
|
||||||
return self->children[self->bitSet.firstSetGeq(0)];
|
auto *result = self->children[self->bitSet.firstSetGeq(0)];
|
||||||
|
simCacheMiss(result);
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Precondition: self has a child
|
// Precondition: self has a child
|
||||||
@@ -2543,38 +2546,19 @@ downLeftSpine:
|
|||||||
namespace {
|
namespace {
|
||||||
// Return true if the max version among all keys that start with key[:prefixLen]
|
// Return true if the max version among all keys that start with key[:prefixLen]
|
||||||
// that are >= key is <= readVersion
|
// that are >= key is <= readVersion
|
||||||
struct CheckRangeLeftSide {
|
bool checkRangeLeftSide(Node *n, std::span<const uint8_t> key, int prefixLen,
|
||||||
CheckRangeLeftSide(Node *n, std::span<const uint8_t> key, int prefixLen,
|
InternalVersionT readVersion, ReadContext *tls) {
|
||||||
InternalVersionT readVersion, ReadContext *tls)
|
auto remaining = key;
|
||||||
: n(n), remaining(key), prefixLen(prefixLen), readVersion(readVersion),
|
|
||||||
impl(tls->impl), tls(tls) {
|
|
||||||
#if DEBUG_VERBOSE && !defined(NDEBUG)
|
|
||||||
fprintf(stderr, "Check range left side from %s for keys starting with %s\n",
|
|
||||||
printable(key).c_str(),
|
|
||||||
printable(key.subspan(0, prefixLen)).c_str());
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
Node *n;
|
|
||||||
std::span<const uint8_t> remaining;
|
|
||||||
int prefixLen;
|
|
||||||
InternalVersionT readVersion;
|
|
||||||
ConflictSet::Impl *impl;
|
|
||||||
ReadContext *tls;
|
|
||||||
int searchPathLen = 0;
|
int searchPathLen = 0;
|
||||||
bool ok;
|
for (;; ++tls->range_read_iterations_accum) {
|
||||||
|
|
||||||
bool step() {
|
|
||||||
if (remaining.size() == 0) {
|
if (remaining.size() == 0) {
|
||||||
assert(searchPathLen >= prefixLen);
|
assert(searchPathLen >= prefixLen);
|
||||||
ok = maxVersion(n) <= readVersion;
|
return maxVersion(n) <= readVersion;
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (searchPathLen >= prefixLen) {
|
if (searchPathLen >= prefixLen) {
|
||||||
if (!checkMaxBetweenExclusive(n, remaining[0], 256, readVersion, tls)) {
|
if (!checkMaxBetweenExclusive(n, remaining[0], 256, readVersion, tls)) {
|
||||||
ok = false;
|
return false;
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2584,18 +2568,16 @@ struct CheckRangeLeftSide {
|
|||||||
if (c != nullptr) {
|
if (c != nullptr) {
|
||||||
if (searchPathLen < prefixLen) {
|
if (searchPathLen < prefixLen) {
|
||||||
n = c;
|
n = c;
|
||||||
return downLeftSpine();
|
goto downLeftSpine;
|
||||||
}
|
}
|
||||||
n = c;
|
n = c;
|
||||||
ok = maxVersion(n) <= readVersion;
|
return maxVersion(n) <= readVersion;
|
||||||
return true;
|
|
||||||
} else {
|
} else {
|
||||||
n = nextSibling(n);
|
n = nextSibling(n);
|
||||||
if (n == nullptr) {
|
if (n == nullptr) {
|
||||||
ok = true;
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return downLeftSpine();
|
goto downLeftSpine;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2611,21 +2593,18 @@ struct CheckRangeLeftSide {
|
|||||||
auto c = n->partialKey()[i] <=> remaining[i];
|
auto c = n->partialKey()[i] <=> remaining[i];
|
||||||
if (c > 0) {
|
if (c > 0) {
|
||||||
if (searchPathLen < prefixLen) {
|
if (searchPathLen < prefixLen) {
|
||||||
return downLeftSpine();
|
goto downLeftSpine;
|
||||||
}
|
}
|
||||||
if (n->entryPresent && n->entry.rangeVersion > readVersion) {
|
if (n->entryPresent && n->entry.rangeVersion > readVersion) {
|
||||||
ok = false;
|
return false;
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
ok = maxVersion(n) <= readVersion;
|
return maxVersion(n) <= readVersion;
|
||||||
return true;
|
|
||||||
} else {
|
} else {
|
||||||
n = nextSibling(n);
|
n = nextSibling(n);
|
||||||
if (n == nullptr) {
|
if (n == nullptr) {
|
||||||
ok = true;
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return downLeftSpine();
|
goto downLeftSpine;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (commonLen == n->partialKeyLen) {
|
if (commonLen == n->partialKeyLen) {
|
||||||
@@ -2634,83 +2613,47 @@ struct CheckRangeLeftSide {
|
|||||||
} else if (n->partialKeyLen > int(remaining.size())) {
|
} else if (n->partialKeyLen > int(remaining.size())) {
|
||||||
assert(searchPathLen >= prefixLen);
|
assert(searchPathLen >= prefixLen);
|
||||||
if (n->entryPresent && n->entry.rangeVersion > readVersion) {
|
if (n->entryPresent && n->entry.rangeVersion > readVersion) {
|
||||||
ok = false;
|
return false;
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
ok = maxVersion(n) <= readVersion;
|
return maxVersion(n) <= readVersion;
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (maxV <= readVersion) {
|
if (maxV <= readVersion) {
|
||||||
ok = true;
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
downLeftSpine:
|
||||||
bool downLeftSpine() {
|
for (; !n->entryPresent; n = getFirstChildExists(n)) {
|
||||||
for (; !n->entryPresent; n = getFirstChildExists(n)) {
|
|
||||||
}
|
|
||||||
ok = n->entry.rangeVersion <= readVersion;
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
};
|
return n->entry.rangeVersion <= readVersion;
|
||||||
|
}
|
||||||
|
|
||||||
// Return true if the max version among all keys that start with key[:prefixLen]
|
// Return true if the max version among all keys that start with key[:prefixLen]
|
||||||
// that are < key is <= readVersion
|
// that are < key is <= readVersion
|
||||||
struct CheckRangeRightSide {
|
bool checkRangeRightSide(Node *n, std::span<const uint8_t> key, int prefixLen,
|
||||||
CheckRangeRightSide(Node *n, std::span<const uint8_t> key, int prefixLen,
|
InternalVersionT readVersion, ReadContext *tls) {
|
||||||
InternalVersionT readVersion, ReadContext *tls)
|
auto remaining = key;
|
||||||
: n(n), key(key), remaining(key), prefixLen(prefixLen),
|
|
||||||
readVersion(readVersion), impl(tls->impl), tls(tls) {
|
|
||||||
#if DEBUG_VERBOSE && !defined(NDEBUG)
|
|
||||||
fprintf(stderr, "Check range right side to %s for keys starting with %s\n",
|
|
||||||
printable(key).c_str(),
|
|
||||||
printable(key.subspan(0, prefixLen)).c_str());
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
Node *n;
|
|
||||||
std::span<const uint8_t> key;
|
|
||||||
std::span<const uint8_t> remaining;
|
|
||||||
int prefixLen;
|
|
||||||
InternalVersionT readVersion;
|
|
||||||
ConflictSet::Impl *impl;
|
|
||||||
ReadContext *tls;
|
|
||||||
int searchPathLen = 0;
|
int searchPathLen = 0;
|
||||||
bool ok;
|
|
||||||
|
|
||||||
bool step() {
|
|
||||||
#if DEBUG_VERBOSE && !defined(NDEBUG)
|
|
||||||
fprintf(stderr,
|
|
||||||
"Search path: %s, searchPathLen: %d, prefixLen: %d, remaining: "
|
|
||||||
"%s\n",
|
|
||||||
getSearchPathPrintable(n).c_str(), searchPathLen, prefixLen,
|
|
||||||
printable(remaining).c_str());
|
|
||||||
#endif
|
|
||||||
|
|
||||||
|
for (;; ++tls->range_read_iterations_accum) {
|
||||||
assert(searchPathLen <= int(key.size()));
|
assert(searchPathLen <= int(key.size()));
|
||||||
|
|
||||||
if (remaining.size() == 0) {
|
if (remaining.size() == 0) {
|
||||||
return downLeftSpine();
|
goto downLeftSpine;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (searchPathLen >= prefixLen) {
|
if (searchPathLen >= prefixLen) {
|
||||||
if (n->entryPresent && n->entry.pointVersion > readVersion) {
|
if (n->entryPresent && n->entry.pointVersion > readVersion) {
|
||||||
ok = false;
|
return false;
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!checkMaxBetweenExclusive(n, -1, remaining[0], readVersion, tls)) {
|
if (!checkMaxBetweenExclusive(n, -1, remaining[0], readVersion, tls)) {
|
||||||
ok = false;
|
return false;
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (searchPathLen > prefixLen && n->entryPresent &&
|
if (searchPathLen > prefixLen && n->entryPresent &&
|
||||||
n->entry.rangeVersion > readVersion) {
|
n->entry.rangeVersion > readVersion) {
|
||||||
ok = false;
|
return false;
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
auto *child = getChild(n, remaining[0]);
|
auto *child = getChild(n, remaining[0]);
|
||||||
@@ -2718,9 +2661,9 @@ struct CheckRangeRightSide {
|
|||||||
auto c = getChildGeq(n, remaining[0]);
|
auto c = getChildGeq(n, remaining[0]);
|
||||||
if (c != nullptr) {
|
if (c != nullptr) {
|
||||||
n = c;
|
n = c;
|
||||||
return downLeftSpine();
|
goto downLeftSpine;
|
||||||
} else {
|
} else {
|
||||||
return backtrack();
|
goto backtrack;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2736,57 +2679,48 @@ struct CheckRangeRightSide {
|
|||||||
++searchPathLen;
|
++searchPathLen;
|
||||||
auto c = n->partialKey()[i] <=> remaining[i];
|
auto c = n->partialKey()[i] <=> remaining[i];
|
||||||
if (c > 0) {
|
if (c > 0) {
|
||||||
return downLeftSpine();
|
goto downLeftSpine;
|
||||||
} else {
|
} else {
|
||||||
if (searchPathLen > prefixLen && n->entryPresent &&
|
if (searchPathLen > prefixLen && n->entryPresent &&
|
||||||
n->entry.rangeVersion > readVersion) {
|
n->entry.rangeVersion > readVersion) {
|
||||||
ok = false;
|
return false;
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
return backtrack();
|
goto backtrack;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (commonLen == n->partialKeyLen) {
|
if (commonLen == n->partialKeyLen) {
|
||||||
// partial key matches
|
// partial key matches
|
||||||
remaining = remaining.subspan(commonLen, remaining.size() - commonLen);
|
remaining = remaining.subspan(commonLen, remaining.size() - commonLen);
|
||||||
} else if (n->partialKeyLen > int(remaining.size())) {
|
} else if (n->partialKeyLen > int(remaining.size())) {
|
||||||
return downLeftSpine();
|
goto downLeftSpine;
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool backtrack() {
|
|
||||||
for (;;) {
|
|
||||||
// searchPathLen > prefixLen implies n is not the root
|
|
||||||
if (searchPathLen > prefixLen && maxVersion(n) > readVersion) {
|
|
||||||
ok = false;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (n->parent == nullptr) {
|
|
||||||
ok = true;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
auto next = getChildGeq(n->parent, n->parentsIndex + 1);
|
|
||||||
if (next == nullptr) {
|
|
||||||
searchPathLen -= 1 + n->partialKeyLen;
|
|
||||||
n = n->parent;
|
|
||||||
} else {
|
|
||||||
searchPathLen -= n->partialKeyLen;
|
|
||||||
n = next;
|
|
||||||
searchPathLen += n->partialKeyLen;
|
|
||||||
return downLeftSpine();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
backtrack:
|
||||||
bool downLeftSpine() {
|
for (;;) {
|
||||||
for (; !n->entryPresent; n = getFirstChildExists(n)) {
|
// searchPathLen > prefixLen implies n is not the root
|
||||||
|
if (searchPathLen > prefixLen && maxVersion(n) > readVersion) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (n->parent == nullptr) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
auto next = getChildGeq(n->parent, n->parentsIndex + 1);
|
||||||
|
if (next == nullptr) {
|
||||||
|
searchPathLen -= 1 + n->partialKeyLen;
|
||||||
|
n = n->parent;
|
||||||
|
} else {
|
||||||
|
searchPathLen -= n->partialKeyLen;
|
||||||
|
n = next;
|
||||||
|
searchPathLen += n->partialKeyLen;
|
||||||
|
goto downLeftSpine;
|
||||||
}
|
}
|
||||||
ok = n->entry.rangeVersion <= readVersion;
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
};
|
downLeftSpine:
|
||||||
|
for (; !n->entryPresent; n = getFirstChildExists(n)) {
|
||||||
|
}
|
||||||
|
return n->entry.rangeVersion <= readVersion;
|
||||||
|
}
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
bool checkRangeRead(Node *n, std::span<const uint8_t> begin,
|
bool checkRangeRead(Node *n, std::span<const uint8_t> begin,
|
||||||
@@ -2807,8 +2741,8 @@ bool checkRangeRead(Node *n, std::span<const uint8_t> begin,
|
|||||||
|
|
||||||
auto remaining = begin.subspan(0, lcp);
|
auto remaining = begin.subspan(0, lcp);
|
||||||
Arena arena;
|
Arena arena;
|
||||||
// If the common prefix isn't a prefix of any physical entry in the tree, we
|
|
||||||
// can go to "downLeftSpine"
|
// Advance down common prefix, but stay on a physical path in the tree
|
||||||
for (;; ++tls->range_read_iterations_accum) {
|
for (;; ++tls->range_read_iterations_accum) {
|
||||||
assert(getSearchPath(arena, n) <=>
|
assert(getSearchPath(arena, n) <=>
|
||||||
begin.subspan(0, lcp - remaining.size()) ==
|
begin.subspan(0, lcp - remaining.size()) ==
|
||||||
@@ -2849,47 +2783,17 @@ bool checkRangeRead(Node *n, std::span<const uint8_t> begin,
|
|||||||
lcp -= consumed;
|
lcp -= consumed;
|
||||||
|
|
||||||
if (lcp == int(begin.size())) {
|
if (lcp == int(begin.size())) {
|
||||||
CheckRangeRightSide checkRangeRightSide{n, end, lcp, readVersion, tls};
|
return checkRangeRightSide(n, end, lcp, readVersion, tls);
|
||||||
while (!checkRangeRightSide.step())
|
|
||||||
;
|
|
||||||
return checkRangeRightSide.ok;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!checkRangeStartsWith(n, begin.subspan(0, lcp), begin[lcp], end[lcp],
|
// This makes it safe to check maxVersion within checkRangeLeftSide. If this
|
||||||
readVersion, tls)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// This makes it safe to check maxVersion within CheckRangeLeftSide. If this
|
|
||||||
// were false, then we would have returned above since lcp == begin.size().
|
// were false, then we would have returned above since lcp == begin.size().
|
||||||
assert(!(n->parent == nullptr && begin.size() == 0));
|
assert(!(n->parent == nullptr && begin.size() == 0));
|
||||||
CheckRangeLeftSide checkRangeLeftSide{n, begin, lcp + 1, readVersion, tls};
|
|
||||||
CheckRangeRightSide checkRangeRightSide{n, end, lcp + 1, readVersion, tls};
|
|
||||||
|
|
||||||
for (;;) {
|
return checkRangeStartsWith(n, begin.subspan(0, lcp), begin[lcp], end[lcp],
|
||||||
bool leftDone = checkRangeLeftSide.step();
|
readVersion, tls) &&
|
||||||
bool rightDone = checkRangeRightSide.step();
|
checkRangeLeftSide(n, begin, lcp + 1, readVersion, tls) &&
|
||||||
if (!leftDone && !rightDone) {
|
checkRangeRightSide(n, end, lcp + 1, readVersion, tls);
|
||||||
tls->range_read_iterations_accum += 2;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (leftDone && rightDone) {
|
|
||||||
break;
|
|
||||||
} else if (leftDone) {
|
|
||||||
while (!checkRangeRightSide.step()) {
|
|
||||||
++tls->range_read_iterations_accum;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
} else {
|
|
||||||
assert(rightDone);
|
|
||||||
while (!checkRangeLeftSide.step()) {
|
|
||||||
++tls->range_read_iterations_accum;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
return checkRangeLeftSide.ok && checkRangeRightSide.ok;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef __x86_64__
|
#ifdef __x86_64__
|
||||||
@@ -3141,34 +3045,288 @@ Node *firstGeqPhysical(Node *n, const std::span<const uint8_t> key) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifndef __has_attribute
|
||||||
|
#define __has_attribute(x) 0
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if __has_attribute(musttail)
|
||||||
|
#define MUSTTAIL __attribute__((musttail))
|
||||||
|
#else
|
||||||
|
#define MUSTTAIL
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if __has_attribute(preserve_none)
|
||||||
|
#define CONTINUATION_CALLING_CONVENTION __attribute__((preserve_none))
|
||||||
|
#else
|
||||||
|
#define CONTINUATION_CALLING_CONVENTION
|
||||||
|
#endif
|
||||||
|
|
||||||
|
typedef CONTINUATION_CALLING_CONVENTION void (*continuation)(struct CheckAll *,
|
||||||
|
int64_t prevJob,
|
||||||
|
int64_t job,
|
||||||
|
int64_t started,
|
||||||
|
int64_t count);
|
||||||
|
|
||||||
|
// State relevant to a particular query
|
||||||
|
struct CheckJob {
|
||||||
|
void setResult(bool ok) {
|
||||||
|
*result = ok ? ConflictSet::Commit : ConflictSet::Conflict;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] continuation init(const ConflictSet::ReadRange *read,
|
||||||
|
ConflictSet::Result *result, Node *root,
|
||||||
|
int64_t oldestVersionFullPrecision,
|
||||||
|
ReadContext *tls);
|
||||||
|
|
||||||
|
Node *n;
|
||||||
|
ChildAndMaxVersion childAndVersion;
|
||||||
|
std::span<const uint8_t> begin;
|
||||||
|
InternalVersionT readVersion;
|
||||||
|
ConflictSet::Result *result;
|
||||||
|
};
|
||||||
|
|
||||||
|
// State relevant to all queries
|
||||||
|
struct CheckAll {
|
||||||
|
constexpr static int kConcurrent = 32;
|
||||||
|
const ConflictSet::ReadRange *queries;
|
||||||
|
ConflictSet::Result *results;
|
||||||
|
CheckJob inProgress[kConcurrent];
|
||||||
|
continuation next[kConcurrent];
|
||||||
|
int nextJob[kConcurrent];
|
||||||
|
Node *root;
|
||||||
|
int64_t oldestVersionFullPrecision;
|
||||||
|
ReadContext *tls;
|
||||||
|
};
|
||||||
|
|
||||||
|
CONTINUATION_CALLING_CONVENTION void keepGoing(CheckAll *context,
|
||||||
|
int64_t prevJob, int64_t job,
|
||||||
|
int64_t started, int64_t count) {
|
||||||
|
prevJob = job;
|
||||||
|
job = context->nextJob[job];
|
||||||
|
MUSTTAIL return context->next[job](context, prevJob, job, started, count);
|
||||||
|
}
|
||||||
|
|
||||||
|
CONTINUATION_CALLING_CONVENTION void complete(CheckAll *context,
|
||||||
|
int64_t prevJob, int64_t job,
|
||||||
|
int64_t started, int64_t count) {
|
||||||
|
if (started == count) {
|
||||||
|
if (prevJob == job) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
context->nextJob[prevJob] = context->nextJob[job];
|
||||||
|
job = prevJob;
|
||||||
|
} else {
|
||||||
|
int temp = started++;
|
||||||
|
context->next[job] = context->inProgress[job].init(
|
||||||
|
context->queries + temp, context->results + temp, context->root,
|
||||||
|
context->oldestVersionFullPrecision, context->tls);
|
||||||
|
}
|
||||||
|
MUSTTAIL return keepGoing(context, prevJob, job, started, count);
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace check_point_read_state_machine {
|
||||||
|
|
||||||
|
CONTINUATION_CALLING_CONVENTION void
|
||||||
|
down_left_spine(struct CheckAll *, int64_t prevJob, int64_t job,
|
||||||
|
int64_t started, int64_t count);
|
||||||
|
CONTINUATION_CALLING_CONVENTION void iter(struct CheckAll *, int64_t prevJob,
|
||||||
|
int64_t job, int64_t started,
|
||||||
|
int64_t count);
|
||||||
|
CONTINUATION_CALLING_CONVENTION void begin(struct CheckAll *, int64_t prevJob,
|
||||||
|
int64_t job, int64_t started,
|
||||||
|
int64_t count);
|
||||||
|
|
||||||
|
void begin(struct CheckAll *context, int64_t prevJob, int64_t job,
|
||||||
|
int64_t started, int64_t count) {
|
||||||
|
++context->tls->point_read_accum;
|
||||||
|
#if DEBUG_VERBOSE && !defined(NDEBUG)
|
||||||
|
fprintf(stderr, "Check point read: %s\n", printable(key).c_str());
|
||||||
|
#endif
|
||||||
|
auto *j = context->inProgress + job;
|
||||||
|
|
||||||
|
if (j->begin.size() == 0) {
|
||||||
|
if (j->n->entryPresent) {
|
||||||
|
j->setResult(j->n->entry.pointVersion <= j->readVersion);
|
||||||
|
MUSTTAIL return complete(context, prevJob, job, started, count);
|
||||||
|
}
|
||||||
|
j->n = getFirstChildExists(j->n);
|
||||||
|
context->next[job] = down_left_spine;
|
||||||
|
__builtin_prefetch(j->n);
|
||||||
|
MUSTTAIL return keepGoing(context, prevJob, job, started, count);
|
||||||
|
}
|
||||||
|
|
||||||
|
j->childAndVersion = getChildAndMaxVersion(j->n, j->begin[0]);
|
||||||
|
context->next[job] = iter;
|
||||||
|
__builtin_prefetch(j->childAndVersion.child);
|
||||||
|
MUSTTAIL return keepGoing(context, prevJob, job, started, count);
|
||||||
|
}
|
||||||
|
|
||||||
|
void iter(struct CheckAll *context, int64_t prevJob, int64_t job,
|
||||||
|
int64_t started, int64_t count) {
|
||||||
|
auto *j = context->inProgress + job;
|
||||||
|
if (j->childAndVersion.child == nullptr) {
|
||||||
|
auto c = getChildGeq(j->n, j->begin[0]);
|
||||||
|
if (c != nullptr) {
|
||||||
|
j->n = c;
|
||||||
|
context->next[job] = down_left_spine;
|
||||||
|
__builtin_prefetch(j->n);
|
||||||
|
MUSTTAIL return keepGoing(context, prevJob, job, started, count);
|
||||||
|
} else {
|
||||||
|
j->n = nextSibling(j->n);
|
||||||
|
if (j->n == nullptr) {
|
||||||
|
j->setResult(true);
|
||||||
|
MUSTTAIL return complete(context, prevJob, job, started, count);
|
||||||
|
}
|
||||||
|
context->next[job] = down_left_spine;
|
||||||
|
__builtin_prefetch(j->n);
|
||||||
|
MUSTTAIL return keepGoing(context, prevJob, job, started, count);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
j->n = j->childAndVersion.child;
|
||||||
|
j->begin = j->begin.subspan(1, j->begin.size() - 1);
|
||||||
|
|
||||||
|
if (j->n->partialKeyLen > 0) {
|
||||||
|
int commonLen = std::min<int>(j->n->partialKeyLen, j->begin.size());
|
||||||
|
int i = longestCommonPrefix(j->n->partialKey(), j->begin.data(), commonLen);
|
||||||
|
if (i < commonLen) {
|
||||||
|
auto c = j->n->partialKey()[i] <=> j->begin[i];
|
||||||
|
if (c > 0) {
|
||||||
|
context->next[job] = down_left_spine;
|
||||||
|
MUSTTAIL return down_left_spine(context, prevJob, job, started, count);
|
||||||
|
} else {
|
||||||
|
j->n = nextSibling(j->n);
|
||||||
|
if (j->n == nullptr) {
|
||||||
|
j->setResult(true);
|
||||||
|
MUSTTAIL return complete(context, prevJob, job, started, count);
|
||||||
|
}
|
||||||
|
context->next[job] = down_left_spine;
|
||||||
|
__builtin_prefetch(j->n);
|
||||||
|
MUSTTAIL return keepGoing(context, prevJob, job, started, count);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (commonLen == j->n->partialKeyLen) {
|
||||||
|
// partial key matches
|
||||||
|
j->begin = j->begin.subspan(commonLen, j->begin.size() - commonLen);
|
||||||
|
} else if (j->n->partialKeyLen > int(j->begin.size())) {
|
||||||
|
// n is the first physical node greater than remaining, and there's no
|
||||||
|
// eq node
|
||||||
|
context->next[job] = down_left_spine;
|
||||||
|
MUSTTAIL return down_left_spine(context, prevJob, job, started, count);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (j->childAndVersion.maxVersion <= j->readVersion) {
|
||||||
|
++context->tls->point_read_short_circuit_accum;
|
||||||
|
j->setResult(true);
|
||||||
|
MUSTTAIL return complete(context, prevJob, job, started, count);
|
||||||
|
}
|
||||||
|
|
||||||
|
++context->tls->point_read_iterations_accum;
|
||||||
|
|
||||||
|
if (j->begin.size() == 0) {
|
||||||
|
if (j->n->entryPresent) {
|
||||||
|
j->setResult(j->n->entry.pointVersion <= j->readVersion);
|
||||||
|
MUSTTAIL return complete(context, prevJob, job, started, count);
|
||||||
|
}
|
||||||
|
j->n = getFirstChildExists(j->n);
|
||||||
|
context->next[job] = down_left_spine;
|
||||||
|
__builtin_prefetch(j->n);
|
||||||
|
MUSTTAIL return keepGoing(context, prevJob, job, started, count);
|
||||||
|
}
|
||||||
|
|
||||||
|
j->childAndVersion = getChildAndMaxVersion(j->n, j->begin[0]);
|
||||||
|
__builtin_prefetch(j->childAndVersion.child);
|
||||||
|
// j->next is already iter
|
||||||
|
MUSTTAIL return keepGoing(context, prevJob, job, started, count);
|
||||||
|
}
|
||||||
|
|
||||||
|
void down_left_spine(struct CheckAll *context, int64_t prevJob, int64_t job,
|
||||||
|
int64_t started, int64_t count) {
|
||||||
|
auto *j = context->inProgress + job;
|
||||||
|
if (j->n->entryPresent) {
|
||||||
|
j->setResult(j->n->entry.rangeVersion <= j->readVersion);
|
||||||
|
MUSTTAIL return complete(context, prevJob, job, started, count);
|
||||||
|
}
|
||||||
|
j->n = getFirstChildExists(j->n);
|
||||||
|
__builtin_prefetch(j->n);
|
||||||
|
// j->next is already down_left_spine
|
||||||
|
MUSTTAIL return keepGoing(context, prevJob, job, started, count);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace check_point_read_state_machine
|
||||||
|
|
||||||
|
continuation CheckJob::init(const ConflictSet::ReadRange *read,
|
||||||
|
ConflictSet::Result *result, Node *root,
|
||||||
|
int64_t oldestVersionFullPrecision,
|
||||||
|
ReadContext *tls) {
|
||||||
|
auto begin = std::span<const uint8_t>(read->begin.p, read->begin.len);
|
||||||
|
auto end = std::span<const uint8_t>(read->end.p, read->end.len);
|
||||||
|
if (read->readVersion < oldestVersionFullPrecision) {
|
||||||
|
*result = ConflictSet::TooOld;
|
||||||
|
return complete;
|
||||||
|
} else if (end.size() == 0) {
|
||||||
|
this->begin = begin;
|
||||||
|
this->n = root;
|
||||||
|
this->readVersion = InternalVersionT(read->readVersion);
|
||||||
|
this->result = result;
|
||||||
|
return check_point_read_state_machine::begin;
|
||||||
|
// *result =
|
||||||
|
// checkPointRead(root, begin, InternalVersionT(read->readVersion), tls)
|
||||||
|
// ? ConflictSet::Commit
|
||||||
|
// : ConflictSet::Conflict;
|
||||||
|
// return complete;
|
||||||
|
} else {
|
||||||
|
*result = checkRangeRead(root, begin, end,
|
||||||
|
InternalVersionT(read->readVersion), tls)
|
||||||
|
? ConflictSet::Commit
|
||||||
|
: ConflictSet::Conflict;
|
||||||
|
return complete;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
struct __attribute__((visibility("hidden"))) ConflictSet::Impl {
|
struct __attribute__((visibility("hidden"))) ConflictSet::Impl {
|
||||||
|
|
||||||
void check(const ReadRange *reads, Result *result, int count) {
|
void check(const ReadRange *reads, Result *result, int count) {
|
||||||
|
assert(oldestVersionFullPrecision >=
|
||||||
|
newestVersionFullPrecision - kNominalVersionWindow);
|
||||||
|
|
||||||
|
if (count == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
ReadContext tls;
|
ReadContext tls;
|
||||||
tls.impl = this;
|
tls.impl = this;
|
||||||
int64_t check_byte_accum = 0;
|
int64_t check_byte_accum = 0;
|
||||||
|
|
||||||
|
CheckAll context;
|
||||||
|
context.oldestVersionFullPrecision = oldestVersionFullPrecision;
|
||||||
|
context.queries = reads;
|
||||||
|
context.results = result;
|
||||||
|
context.root = root;
|
||||||
|
context.tls = &tls;
|
||||||
|
|
||||||
|
int64_t started = std::min(context.kConcurrent, count);
|
||||||
|
for (int i = 0; i < started; i++) {
|
||||||
|
context.next[i] = context.inProgress[i].init(
|
||||||
|
reads + i, result + i, root, oldestVersionFullPrecision, &tls);
|
||||||
|
context.nextJob[i] = i + 1;
|
||||||
|
}
|
||||||
|
context.nextJob[started - 1] = 0;
|
||||||
|
int prevJob = started - 1;
|
||||||
|
int job = 0;
|
||||||
|
context.next[job](&context, prevJob, job, started, count);
|
||||||
|
|
||||||
for (int i = 0; i < count; ++i) {
|
for (int i = 0; i < count; ++i) {
|
||||||
assert(reads[i].readVersion >= 0);
|
assert(reads[i].readVersion >= 0);
|
||||||
assert(reads[i].readVersion <= newestVersionFullPrecision);
|
assert(reads[i].readVersion <= newestVersionFullPrecision);
|
||||||
const auto &r = reads[i];
|
const auto &r = reads[i];
|
||||||
check_byte_accum += r.begin.len + r.end.len;
|
check_byte_accum += r.begin.len + r.end.len;
|
||||||
auto begin = std::span<const uint8_t>(r.begin.p, r.begin.len);
|
|
||||||
auto end = std::span<const uint8_t>(r.end.p, r.end.len);
|
|
||||||
assert(oldestVersionFullPrecision >=
|
|
||||||
newestVersionFullPrecision - kNominalVersionWindow);
|
|
||||||
result[i] =
|
|
||||||
reads[i].readVersion < oldestVersionFullPrecision ? TooOld
|
|
||||||
: (end.size() > 0
|
|
||||||
? checkRangeRead(root, begin, end,
|
|
||||||
InternalVersionT(reads[i].readVersion), &tls)
|
|
||||||
: checkPointRead(root, begin,
|
|
||||||
InternalVersionT(reads[i].readVersion), &tls))
|
|
||||||
? Commit
|
|
||||||
: Conflict;
|
|
||||||
tls.commits_accum += result[i] == Commit;
|
tls.commits_accum += result[i] == Commit;
|
||||||
tls.conflicts_accum += result[i] == Conflict;
|
tls.conflicts_accum += result[i] == Conflict;
|
||||||
tls.too_olds_accum += result[i] == TooOld;
|
tls.too_olds_accum += result[i] == TooOld;
|
||||||
}
|
}
|
||||||
|
|
||||||
point_read_total.add(tls.point_read_accum);
|
point_read_total.add(tls.point_read_accum);
|
||||||
prefix_read_total.add(tls.prefix_read_accum);
|
prefix_read_total.add(tls.prefix_read_accum);
|
||||||
range_read_total.add(tls.range_read_accum);
|
range_read_total.add(tls.range_read_accum);
|
||||||
@@ -3242,7 +3400,6 @@ struct __attribute__((visibility("hidden"))) ConflictSet::Impl {
|
|||||||
0) *
|
0) *
|
||||||
2;
|
2;
|
||||||
|
|
||||||
memory_bytes.set(totalBytes);
|
|
||||||
point_writes_total.add(tls.accum.point_writes);
|
point_writes_total.add(tls.accum.point_writes);
|
||||||
range_writes_total.add(tls.accum.range_writes);
|
range_writes_total.add(tls.accum.range_writes);
|
||||||
nodes_allocated_total.add(tls.accum.nodes_allocated);
|
nodes_allocated_total.add(tls.accum.nodes_allocated);
|
||||||
@@ -3331,7 +3488,6 @@ struct __attribute__((visibility("hidden"))) ConflictSet::Impl {
|
|||||||
#endif
|
#endif
|
||||||
keyUpdates = gcScanStep(keyUpdates);
|
keyUpdates = gcScanStep(keyUpdates);
|
||||||
|
|
||||||
memory_bytes.set(totalBytes);
|
|
||||||
nodes_allocated_total.add(std::exchange(tls.accum.nodes_allocated, 0));
|
nodes_allocated_total.add(std::exchange(tls.accum.nodes_allocated, 0));
|
||||||
nodes_released_total.add(std::exchange(tls.accum.nodes_released, 0));
|
nodes_released_total.add(std::exchange(tls.accum.nodes_released, 0));
|
||||||
entries_inserted_total.add(std::exchange(tls.accum.entries_inserted, 0));
|
entries_inserted_total.add(std::exchange(tls.accum.entries_inserted, 0));
|
||||||
@@ -3379,7 +3535,7 @@ struct __attribute__((visibility("hidden"))) ConflictSet::Impl {
|
|||||||
explicit Impl(int64_t oldestVersion) {
|
explicit Impl(int64_t oldestVersion) {
|
||||||
assert(oldestVersion >= 0);
|
assert(oldestVersion >= 0);
|
||||||
init(oldestVersion);
|
init(oldestVersion);
|
||||||
initMetrics();
|
metrics = initMetrics(metricsList, metricsCount);
|
||||||
}
|
}
|
||||||
~Impl() {
|
~Impl() {
|
||||||
eraseTree(root, &tls);
|
eraseTree(root, &tls);
|
||||||
@@ -3402,23 +3558,12 @@ struct __attribute__((visibility("hidden"))) ConflictSet::Impl {
|
|||||||
|
|
||||||
MetricsV1 *metrics;
|
MetricsV1 *metrics;
|
||||||
int metricsCount = 0;
|
int metricsCount = 0;
|
||||||
void initMetrics() {
|
Metric *metricsList = nullptr;
|
||||||
metrics = (MetricsV1 *)safe_malloc(metricsCount * sizeof(metrics[0]));
|
|
||||||
for (auto [i, m] = std::make_tuple(metricsCount - 1, metricList); i >= 0;
|
|
||||||
--i, m = m->prev) {
|
|
||||||
metrics[i].name = m->name;
|
|
||||||
metrics[i].help = m->help;
|
|
||||||
metrics[i].p = m;
|
|
||||||
metrics[i].type = m->type;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Metric *metricList = nullptr;
|
|
||||||
|
|
||||||
#define GAUGE(name, help) \
|
#define GAUGE(name, help) \
|
||||||
Gauge name { this, #name, help }
|
Gauge name { metricsList, metricsCount, #name, help }
|
||||||
#define COUNTER(name, help) \
|
#define COUNTER(name, help) \
|
||||||
Counter name { this, #name, help }
|
Counter name { metricsList, metricsCount, #name, help }
|
||||||
// ==================== METRICS DEFINITIONS ====================
|
// ==================== METRICS DEFINITIONS ====================
|
||||||
COUNTER(point_read_total, "Total number of point reads checked");
|
COUNTER(point_read_total, "Total number of point reads checked");
|
||||||
COUNTER(point_read_short_circuit_total,
|
COUNTER(point_read_short_circuit_total,
|
||||||
@@ -3484,13 +3629,6 @@ struct __attribute__((visibility("hidden"))) ConflictSet::Impl {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
Metric::Metric(ConflictSet::Impl *impl, const char *name, const char *help,
|
|
||||||
ConflictSet::MetricsV1::Type type)
|
|
||||||
: prev(std::exchange(impl->metricList, this)), name(name), help(help),
|
|
||||||
type(type), value(0) {
|
|
||||||
++impl->metricsCount;
|
|
||||||
}
|
|
||||||
|
|
||||||
Node *&getInTree(Node *n, ConflictSet::Impl *impl) {
|
Node *&getInTree(Node *n, ConflictSet::Impl *impl) {
|
||||||
return n->parent == nullptr ? impl->root
|
return n->parent == nullptr ? impl->root
|
||||||
: getChildExists(n->parent, n->parentsIndex);
|
: getChildExists(n->parent, n->parentsIndex);
|
||||||
@@ -3509,6 +3647,7 @@ void internal_addWrites(ConflictSet::Impl *impl,
|
|||||||
mallocBytesDelta = 0;
|
mallocBytesDelta = 0;
|
||||||
impl->addWrites(writes, count, writeVersion);
|
impl->addWrites(writes, count, writeVersion);
|
||||||
impl->totalBytes += mallocBytesDelta;
|
impl->totalBytes += mallocBytesDelta;
|
||||||
|
impl->memory_bytes.set(impl->totalBytes);
|
||||||
#if SHOW_MEMORY
|
#if SHOW_MEMORY
|
||||||
if (impl->totalBytes != mallocBytes) {
|
if (impl->totalBytes != mallocBytes) {
|
||||||
abort();
|
abort();
|
||||||
@@ -3520,6 +3659,7 @@ void internal_setOldestVersion(ConflictSet::Impl *impl, int64_t oldestVersion) {
|
|||||||
mallocBytesDelta = 0;
|
mallocBytesDelta = 0;
|
||||||
impl->setOldestVersion(oldestVersion);
|
impl->setOldestVersion(oldestVersion);
|
||||||
impl->totalBytes += mallocBytesDelta;
|
impl->totalBytes += mallocBytesDelta;
|
||||||
|
impl->memory_bytes.set(impl->totalBytes);
|
||||||
#if SHOW_MEMORY
|
#if SHOW_MEMORY
|
||||||
if (impl->totalBytes != mallocBytes) {
|
if (impl->totalBytes != mallocBytes) {
|
||||||
abort();
|
abort();
|
||||||
@@ -4073,6 +4213,72 @@ struct __attribute__((visibility("default"))) PeakPrinter {
|
|||||||
|
|
||||||
#ifdef ENABLE_MAIN
|
#ifdef ENABLE_MAIN
|
||||||
|
|
||||||
|
#include "third_party/nanobench.h"
|
||||||
|
|
||||||
|
template <int kN> void benchRezero() {
|
||||||
|
static_assert(kN % 16 == 0);
|
||||||
|
ankerl::nanobench::Bench bench;
|
||||||
|
InternalVersionT vs[kN];
|
||||||
|
InternalVersionT zero;
|
||||||
|
bench.run("rezero" + std::to_string(kN), [&]() {
|
||||||
|
bench.doNotOptimizeAway(vs);
|
||||||
|
bench.doNotOptimizeAway(zero);
|
||||||
|
for (int i = 0; i < kN; i += 16) {
|
||||||
|
rezero16(vs + i, zero);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
template <int kN> void benchScan1() {
|
||||||
|
static_assert(kN % 16 == 0);
|
||||||
|
ankerl::nanobench::Bench bench;
|
||||||
|
InternalVersionT vs[kN];
|
||||||
|
uint8_t is[kN];
|
||||||
|
uint8_t begin;
|
||||||
|
uint8_t end;
|
||||||
|
InternalVersionT v;
|
||||||
|
bench.run("scan" + std::to_string(kN), [&]() {
|
||||||
|
bench.doNotOptimizeAway(vs);
|
||||||
|
bench.doNotOptimizeAway(is);
|
||||||
|
bench.doNotOptimizeAway(begin);
|
||||||
|
bench.doNotOptimizeAway(end);
|
||||||
|
bench.doNotOptimizeAway(v);
|
||||||
|
for (int i = 0; i < kN; i += 16) {
|
||||||
|
scan16</*kAVX512=*/true>(vs + i, is + i, begin, end, v);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
template <int kN> void benchScan2() {
|
||||||
|
static_assert(kN % 16 == 0);
|
||||||
|
ankerl::nanobench::Bench bench;
|
||||||
|
InternalVersionT vs[kN];
|
||||||
|
uint8_t is[kN];
|
||||||
|
uint8_t begin;
|
||||||
|
uint8_t end;
|
||||||
|
InternalVersionT v;
|
||||||
|
bench.run("scan" + std::to_string(kN), [&]() {
|
||||||
|
bench.doNotOptimizeAway(vs);
|
||||||
|
bench.doNotOptimizeAway(begin);
|
||||||
|
bench.doNotOptimizeAway(end);
|
||||||
|
bench.doNotOptimizeAway(v);
|
||||||
|
for (int i = 0; i < kN; i += 16) {
|
||||||
|
scan16</*kAVX512=*/true>(vs + i, begin, end, v);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void benchLCP(int len) {
|
||||||
|
ankerl::nanobench::Bench bench;
|
||||||
|
std::vector<uint8_t> lhs(len);
|
||||||
|
std::vector<uint8_t> rhs(len);
|
||||||
|
bench.run("lcp " + std::to_string(len), [&]() {
|
||||||
|
bench.doNotOptimizeAway(lhs);
|
||||||
|
bench.doNotOptimizeAway(rhs);
|
||||||
|
bench.doNotOptimizeAway(longestCommonPrefix(lhs.data(), rhs.data(), len));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
void printTree() {
|
void printTree() {
|
||||||
int64_t writeVersion = 0;
|
int64_t writeVersion = 0;
|
||||||
ConflictSet::Impl cs{writeVersion};
|
ConflictSet::Impl cs{writeVersion};
|
||||||
@@ -4094,7 +4300,11 @@ void printTree() {
|
|||||||
debugPrintDot(stdout, cs.root, &cs);
|
debugPrintDot(stdout, cs.root, &cs);
|
||||||
}
|
}
|
||||||
|
|
||||||
int main(void) { printTree(); }
|
int main(void) {
|
||||||
|
for (int i = 0; i < 256; ++i) {
|
||||||
|
benchLCP(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef ENABLE_FUZZ
|
#ifdef ENABLE_FUZZ
|
||||||
|
256
InterleavingTest.cpp
Normal file
256
InterleavingTest.cpp
Normal file
@@ -0,0 +1,256 @@
|
|||||||
|
#include <alloca.h>
|
||||||
|
#include <cassert>
|
||||||
|
#ifdef __x86_64__
|
||||||
|
#include <immintrin.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include "third_party/nanobench.h"
|
||||||
|
|
||||||
|
struct Job {
|
||||||
|
int *input;
|
||||||
|
// Returned void* is a function pointer to the next continuation. We have to
|
||||||
|
// use void* because otherwise the type would be recursive.
|
||||||
|
typedef void *(*continuation)(Job *);
|
||||||
|
continuation next;
|
||||||
|
};
|
||||||
|
|
||||||
|
void *stepJob(Job *j) {
|
||||||
|
auto done = --(*j->input) == 0;
|
||||||
|
#ifdef __x86_64__
|
||||||
|
_mm_clflush(j->input);
|
||||||
|
#endif
|
||||||
|
return done ? nullptr : (void *)stepJob;
|
||||||
|
}
|
||||||
|
|
||||||
|
void sequential(Job **jobs, int count) {
|
||||||
|
for (int i = 0; i < count; ++i) {
|
||||||
|
do {
|
||||||
|
jobs[i]->next = (Job::continuation)jobs[i]->next(jobs[i]);
|
||||||
|
} while (jobs[i]->next);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void sequentialNoFuncPtr(Job **jobs, int count) {
|
||||||
|
for (int i = 0; i < count; ++i) {
|
||||||
|
while (stepJob(jobs[i]))
|
||||||
|
;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void interleaveSwapping(Job **jobs, int remaining) {
|
||||||
|
int current = 0;
|
||||||
|
while (remaining > 0) {
|
||||||
|
auto next = (Job::continuation)jobs[current]->next(jobs[current]);
|
||||||
|
jobs[current]->next = next;
|
||||||
|
if (next == nullptr) {
|
||||||
|
jobs[current] = jobs[remaining - 1];
|
||||||
|
--remaining;
|
||||||
|
} else {
|
||||||
|
++current;
|
||||||
|
}
|
||||||
|
if (current == remaining) {
|
||||||
|
current = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void interleaveBoundedCyclicList(Job **jobs, int count) {
|
||||||
|
if (count == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr int kConcurrent = 32;
|
||||||
|
Job *inProgress[kConcurrent];
|
||||||
|
int nextJob[kConcurrent];
|
||||||
|
|
||||||
|
int started = std::min(kConcurrent, count);
|
||||||
|
for (int i = 0; i < started; i++) {
|
||||||
|
inProgress[i] = jobs[i];
|
||||||
|
nextJob[i] = i + 1;
|
||||||
|
}
|
||||||
|
nextJob[started - 1] = 0;
|
||||||
|
|
||||||
|
int prevJob = started - 1;
|
||||||
|
int job = 0;
|
||||||
|
for (;;) {
|
||||||
|
auto next = (Job::continuation)inProgress[job]->next(inProgress[job]);
|
||||||
|
inProgress[job]->next = next;
|
||||||
|
if (next == nullptr) {
|
||||||
|
if (started == count) {
|
||||||
|
if (prevJob == job)
|
||||||
|
break;
|
||||||
|
nextJob[prevJob] = nextJob[job];
|
||||||
|
job = prevJob;
|
||||||
|
} else {
|
||||||
|
int temp = started++;
|
||||||
|
inProgress[job] = jobs[temp];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
prevJob = job;
|
||||||
|
job = nextJob[job];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifndef __has_attribute
|
||||||
|
#define __has_attribute(x) 0
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if __has_attribute(musttail)
|
||||||
|
#define MUSTTAIL __attribute__((musttail))
|
||||||
|
#else
|
||||||
|
#define MUSTTAIL
|
||||||
|
#endif
|
||||||
|
|
||||||
|
struct Context {
|
||||||
|
constexpr static int kConcurrent = 32;
|
||||||
|
Job **jobs;
|
||||||
|
Job *inProgress[kConcurrent];
|
||||||
|
void (*continuation[kConcurrent])(Context *, int64_t prevJob, int64_t job,
|
||||||
|
int64_t started, int64_t count);
|
||||||
|
int nextJob[kConcurrent];
|
||||||
|
};
|
||||||
|
|
||||||
|
void keepGoing(Context *context, int64_t prevJob, int64_t job, int64_t started,
|
||||||
|
int64_t count) {
|
||||||
|
prevJob = job;
|
||||||
|
job = context->nextJob[job];
|
||||||
|
MUSTTAIL return context->continuation[job](context, prevJob, job, started,
|
||||||
|
count);
|
||||||
|
}
|
||||||
|
|
||||||
|
void stepJobTailCall(Context *context, int64_t prevJob, int64_t job,
|
||||||
|
int64_t started, int64_t count);
|
||||||
|
|
||||||
|
void complete(Context *context, int64_t prevJob, int64_t job, int64_t started,
|
||||||
|
int64_t count) {
|
||||||
|
if (started == count) {
|
||||||
|
if (prevJob == job) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
context->nextJob[prevJob] = context->nextJob[job];
|
||||||
|
job = prevJob;
|
||||||
|
} else {
|
||||||
|
context->inProgress[job] = context->jobs[started++];
|
||||||
|
context->continuation[job] = stepJobTailCall;
|
||||||
|
}
|
||||||
|
prevJob = job;
|
||||||
|
job = context->nextJob[job];
|
||||||
|
MUSTTAIL return context->continuation[job](context, prevJob, job, started,
|
||||||
|
count);
|
||||||
|
}
|
||||||
|
|
||||||
|
void stepJobTailCall(Context *context, int64_t prevJob, int64_t job,
|
||||||
|
int64_t started, int64_t count) {
|
||||||
|
auto *j = context->inProgress[job];
|
||||||
|
auto done = --(*j->input) == 0;
|
||||||
|
#ifdef __x86_64__
|
||||||
|
_mm_clflush(j->input);
|
||||||
|
#endif
|
||||||
|
if (done) {
|
||||||
|
MUSTTAIL return complete(context, prevJob, job, started, count);
|
||||||
|
} else {
|
||||||
|
context->continuation[job] = stepJobTailCall;
|
||||||
|
MUSTTAIL return keepGoing(context, prevJob, job, started, count);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void useTailCalls(Job **jobs, int count) {
|
||||||
|
if (count == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Context context;
|
||||||
|
context.jobs = jobs;
|
||||||
|
int64_t started = std::min(Context::kConcurrent, count);
|
||||||
|
for (int i = 0; i < started; i++) {
|
||||||
|
context.inProgress[i] = jobs[i];
|
||||||
|
context.nextJob[i] = i + 1;
|
||||||
|
context.continuation[i] = stepJobTailCall;
|
||||||
|
}
|
||||||
|
context.nextJob[started - 1] = 0;
|
||||||
|
int prevJob = started - 1;
|
||||||
|
int job = 0;
|
||||||
|
return context.continuation[job](&context, prevJob, job, started, count);
|
||||||
|
}
|
||||||
|
|
||||||
|
void interleaveCyclicList(Job **jobs, int count) {
|
||||||
|
auto *nextJob = (int *)alloca(sizeof(int) * count);
|
||||||
|
|
||||||
|
for (int i = 0; i < count - 1; ++i) {
|
||||||
|
nextJob[i] = i + 1;
|
||||||
|
}
|
||||||
|
nextJob[count - 1] = 0;
|
||||||
|
|
||||||
|
int prevJob = count - 1;
|
||||||
|
int job = 0;
|
||||||
|
for (;;) {
|
||||||
|
auto next = (Job::continuation)jobs[job]->next(jobs[job]);
|
||||||
|
jobs[job]->next = next;
|
||||||
|
if (next == nullptr) {
|
||||||
|
if (prevJob == job)
|
||||||
|
break;
|
||||||
|
nextJob[prevJob] = nextJob[job];
|
||||||
|
job = prevJob;
|
||||||
|
}
|
||||||
|
prevJob = job;
|
||||||
|
job = nextJob[job];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
ankerl::nanobench::Bench bench;
|
||||||
|
|
||||||
|
constexpr int kNumJobs = 10000;
|
||||||
|
bench.relative(true);
|
||||||
|
|
||||||
|
Job jobs[kNumJobs];
|
||||||
|
Job jobsCopy[kNumJobs];
|
||||||
|
int iters = 0;
|
||||||
|
int originalInput[kNumJobs];
|
||||||
|
for (int i = 0; i < kNumJobs; ++i) {
|
||||||
|
originalInput[i] = rand() % 5 + 3;
|
||||||
|
jobs[i].input = new int{originalInput[i]};
|
||||||
|
jobs[i].next = stepJob;
|
||||||
|
iters += *jobs[i].input;
|
||||||
|
}
|
||||||
|
bench.batch(iters);
|
||||||
|
|
||||||
|
for (auto [scheduler, name] :
|
||||||
|
{std::make_pair(sequentialNoFuncPtr, "sequentialNoFuncPtr"),
|
||||||
|
std::make_pair(sequential, "sequential"),
|
||||||
|
std::make_pair(useTailCalls, "useTailCalls"),
|
||||||
|
std::make_pair(interleaveSwapping, "interleavingSwapping"),
|
||||||
|
std::make_pair(interleaveBoundedCyclicList,
|
||||||
|
"interleaveBoundedCyclicList"),
|
||||||
|
std::make_pair(interleaveCyclicList, "interleaveCyclicList")}) {
|
||||||
|
for (int i = 0; i < kNumJobs; ++i) {
|
||||||
|
*jobs[i].input = originalInput[i];
|
||||||
|
}
|
||||||
|
memcpy(jobsCopy, jobs, sizeof(jobs));
|
||||||
|
Job *ps[kNumJobs];
|
||||||
|
for (int i = 0; i < kNumJobs; ++i) {
|
||||||
|
ps[i] = jobsCopy + i;
|
||||||
|
}
|
||||||
|
scheduler(ps, kNumJobs);
|
||||||
|
for (int i = 0; i < kNumJobs; ++i) {
|
||||||
|
if (*jobsCopy[i].input != 0) {
|
||||||
|
fprintf(stderr, "%s failed\n", name);
|
||||||
|
abort();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bench.run(name, [&]() {
|
||||||
|
for (int i = 0; i < kNumJobs; ++i) {
|
||||||
|
*jobs[i].input = originalInput[i];
|
||||||
|
}
|
||||||
|
memcpy(jobsCopy, jobs, sizeof(jobs));
|
||||||
|
Job *ps[kNumJobs];
|
||||||
|
for (int i = 0; i < kNumJobs; ++i) {
|
||||||
|
ps[i] = jobsCopy + i;
|
||||||
|
}
|
||||||
|
scheduler(ps, kNumJobs);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
for (int i = 0; i < kNumJobs; ++i) {
|
||||||
|
delete jobs[i].input;
|
||||||
|
}
|
||||||
|
}
|
2
Jenkinsfile
vendored
2
Jenkinsfile
vendored
@@ -129,7 +129,7 @@ pipeline {
|
|||||||
}
|
}
|
||||||
steps {
|
steps {
|
||||||
script {
|
script {
|
||||||
filter_args = "-f ConflictSet.cpp -f LongestCommonPrefix.h"
|
filter_args = "-f ConflictSet.cpp -f LongestCommonPrefix.h -f Metrics.h"
|
||||||
}
|
}
|
||||||
CleanBuildAndTest("-DCMAKE_C_COMPILER=gcc -DCMAKE_CXX_COMPILER=g++ -DCMAKE_C_FLAGS=--coverage -DCMAKE_CXX_FLAGS=--coverage -DCMAKE_BUILD_TYPE=Debug -DDISABLE_TSAN=ON")
|
CleanBuildAndTest("-DCMAKE_C_COMPILER=gcc -DCMAKE_CXX_COMPILER=g++ -DCMAKE_C_FLAGS=--coverage -DCMAKE_CXX_FLAGS=--coverage -DCMAKE_BUILD_TYPE=Debug -DDISABLE_TSAN=ON")
|
||||||
sh """
|
sh """
|
||||||
|
64
Metrics.h
Normal file
64
Metrics.h
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "ConflictSet.h"
|
||||||
|
#include "Internal.h"
|
||||||
|
|
||||||
|
#include <assert.h>
|
||||||
|
#include <atomic>
|
||||||
|
#include <tuple>
|
||||||
|
|
||||||
|
struct Metric {
|
||||||
|
Metric *prev;
|
||||||
|
const char *name;
|
||||||
|
const char *help;
|
||||||
|
weaselab::ConflictSet::MetricsV1::Type type;
|
||||||
|
std::atomic<int64_t> value;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
Metric(Metric *&metricList, int &metricsCount, const char *name,
|
||||||
|
const char *help, weaselab::ConflictSet::MetricsV1::Type type)
|
||||||
|
: prev(std::exchange(metricList, this)), name(name), help(help),
|
||||||
|
type(type), value(0) {
|
||||||
|
++metricsCount;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Gauge : private Metric {
|
||||||
|
Gauge(Metric *&metricList, int &metricsCount, const char *name,
|
||||||
|
const char *help)
|
||||||
|
: Metric(metricList, metricsCount, name, help,
|
||||||
|
weaselab::ConflictSet::MetricsV1::Gauge) {}
|
||||||
|
|
||||||
|
void set(int64_t value) {
|
||||||
|
this->value.store(value, std::memory_order_relaxed);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Counter : private Metric {
|
||||||
|
Counter(Metric *&metricList, int &metricsCount, const char *name,
|
||||||
|
const char *help)
|
||||||
|
: Metric(metricList, metricsCount, name, help,
|
||||||
|
weaselab::ConflictSet::MetricsV1::Counter) {}
|
||||||
|
// Expensive. Accumulate locally and then call add instead of repeatedly
|
||||||
|
// calling add.
|
||||||
|
void add(int64_t value) {
|
||||||
|
assert(value >= 0);
|
||||||
|
static_assert(std::atomic<int64_t>::is_always_lock_free);
|
||||||
|
this->value.fetch_add(value, std::memory_order_relaxed);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
inline weaselab::ConflictSet::MetricsV1 *initMetrics(Metric *metricsList,
|
||||||
|
int metricsCount) {
|
||||||
|
weaselab::ConflictSet::MetricsV1 *metrics =
|
||||||
|
(weaselab::ConflictSet::MetricsV1 *)safe_malloc(metricsCount *
|
||||||
|
sizeof(metrics[0]));
|
||||||
|
for (auto [i, m] = std::make_tuple(metricsCount - 1, metricsList); i >= 0;
|
||||||
|
--i, m = m->prev) {
|
||||||
|
metrics[i].name = m->name;
|
||||||
|
metrics[i].help = m->help;
|
||||||
|
metrics[i].p = m;
|
||||||
|
metrics[i].type = m->type;
|
||||||
|
}
|
||||||
|
return metrics;
|
||||||
|
}
|
18
README.md
18
README.md
@@ -24,15 +24,15 @@ Hardware for all benchmarks is an AMD Ryzen 9 7900 with (2x32GB) 5600MT/s CL28-3
|
|||||||
|
|
||||||
| ns/op | op/s | err% | ins/op | cyc/op | IPC | bra/op | miss% | total | benchmark
|
| ns/op | op/s | err% | ins/op | cyc/op | IPC | bra/op | miss% | total | benchmark
|
||||||
|--------------------:|--------------------:|--------:|----------------:|----------------:|-------:|---------------:|--------:|----------:|:----------
|
|--------------------:|--------------------:|--------:|----------------:|----------------:|-------:|---------------:|--------:|----------:|:----------
|
||||||
| 11.18 | 89,455,125.34 | 0.6% | 185.37 | 57.08 | 3.248 | 41.51 | 0.4% | 0.01 | `point reads`
|
| 12.88 | 77,653,350.77 | 0.5% | 185.37 | 64.45 | 2.876 | 41.51 | 0.4% | 0.01 | `point reads`
|
||||||
| 14.53 | 68,800,688.89 | 0.4% | 282.41 | 74.80 | 3.776 | 55.06 | 0.3% | 0.01 | `prefix reads`
|
| 14.67 | 68,179,354.49 | 0.1% | 271.44 | 73.40 | 3.698 | 53.70 | 0.3% | 0.01 | `prefix reads`
|
||||||
| 36.54 | 27,367,576.87 | 0.2% | 798.06 | 188.90 | 4.225 | 141.69 | 0.2% | 0.01 | `range reads`
|
| 34.84 | 28,701,444.36 | 0.3% | 715.74 | 175.27 | 4.084 | 127.30 | 0.2% | 0.01 | `range reads`
|
||||||
| 16.69 | 59,912,106.02 | 0.6% | 314.57 | 86.29 | 3.645 | 39.84 | 0.4% | 0.01 | `point writes`
|
| 17.12 | 58,422,988.28 | 0.2% | 314.30 | 86.11 | 3.650 | 39.82 | 0.4% | 0.01 | `point writes`
|
||||||
| 30.09 | 33,235,744.07 | 0.5% | 591.33 | 155.92 | 3.793 | 82.69 | 0.2% | 0.01 | `prefix writes`
|
| 31.42 | 31,830,804.65 | 0.1% | 591.06 | 158.07 | 3.739 | 82.67 | 0.2% | 0.01 | `prefix writes`
|
||||||
| 35.77 | 27,956,388.03 | 1.4% | 682.25 | 187.63 | 3.636 | 96.12 | 0.1% | 0.01 | `range writes`
|
| 37.37 | 26,759,432.70 | 2.2% | 681.98 | 188.95 | 3.609 | 96.10 | 0.1% | 0.01 | `range writes`
|
||||||
| 74.04 | 13,505,408.41 | 2.7% | 1,448.95 | 392.10 | 3.695 | 260.53 | 0.1% | 0.01 | `monotonic increasing point writes`
|
| 76.72 | 13,035,140.63 | 2.3% | 1,421.28 | 387.17 | 3.671 | 257.76 | 0.1% | 0.01 | `monotonic increasing point writes`
|
||||||
| 330,984.50 | 3,021.29 | 1.9% | 3,994,153.50 | 1,667,309.00 | 2.396 | 806,019.50 | 0.0% | 0.01 | `worst case for radix tree`
|
| 297,452.00 | 3,361.89 | 0.9% | 3,508,083.00 | 1,500,834.67 | 2.337 | 727,525.33 | 0.1% | 0.01 | `worst case for radix tree`
|
||||||
| 92.46 | 10,814,961.65 | 0.5% | 1,800.00 | 463.41 | 3.884 | 297.00 | 0.0% | 0.01 | `create and destroy`
|
| 87.70 | 11,402,490.60 | 1.0% | 1,795.00 | 442.09 | 4.060 | 297.00 | 0.0% | 0.01 | `create and destroy`
|
||||||
|
|
||||||
# "Real data" test
|
# "Real data" test
|
||||||
|
|
||||||
|
@@ -1,4 +1,5 @@
|
|||||||
#include <atomic>
|
#include <atomic>
|
||||||
|
#include <cstdint>
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
#include <netdb.h>
|
#include <netdb.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
@@ -21,78 +22,55 @@
|
|||||||
|
|
||||||
std::atomic<int64_t> transactions;
|
std::atomic<int64_t> transactions;
|
||||||
|
|
||||||
constexpr int kBaseSearchDepth = 32;
|
|
||||||
constexpr int kWindowSize = 10000000;
|
constexpr int kWindowSize = 10000000;
|
||||||
|
|
||||||
std::string numToKey(int64_t num) {
|
constexpr int kNumPrefixes = 250000;
|
||||||
|
|
||||||
|
std::string makeKey(int64_t num, int suffixLen) {
|
||||||
std::string result;
|
std::string result;
|
||||||
result.resize(kBaseSearchDepth + sizeof(int64_t));
|
result.resize(sizeof(int64_t) + suffixLen);
|
||||||
memset(result.data(), 0, kBaseSearchDepth);
|
|
||||||
int64_t be = __builtin_bswap64(num);
|
int64_t be = __builtin_bswap64(num);
|
||||||
memcpy(result.data() + kBaseSearchDepth, &be, sizeof(int64_t));
|
memcpy(result.data(), &be, sizeof(int64_t));
|
||||||
|
memset(result.data() + sizeof(int64_t), 0, suffixLen);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
void workload(weaselab::ConflictSet *cs) {
|
void workload(weaselab::ConflictSet *cs) {
|
||||||
int64_t version = kWindowSize;
|
int64_t version = kWindowSize;
|
||||||
cs->addWrites(nullptr, 0, version);
|
for (int i = 0; i < kNumPrefixes; ++i) {
|
||||||
|
for (int j = 0; j < 50; ++j) {
|
||||||
|
weaselab::ConflictSet::WriteRange wr;
|
||||||
|
auto k = makeKey(i, j);
|
||||||
|
wr.begin.p = (const uint8_t *)k.data();
|
||||||
|
wr.begin.len = k.size();
|
||||||
|
wr.end.len = 0;
|
||||||
|
cs->addWrites(&wr, 1, version);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
++version;
|
||||||
|
for (int i = 0; i < kNumPrefixes; ++i) {
|
||||||
|
weaselab::ConflictSet::WriteRange wr;
|
||||||
|
auto k = makeKey(i, 50);
|
||||||
|
wr.begin.p = (const uint8_t *)k.data();
|
||||||
|
wr.begin.len = k.size();
|
||||||
|
wr.end.len = 0;
|
||||||
|
cs->addWrites(&wr, 1, version);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<weaselab::ConflictSet::Result> results(10);
|
||||||
for (;; transactions.fetch_add(1, std::memory_order_relaxed)) {
|
for (;; transactions.fetch_add(1, std::memory_order_relaxed)) {
|
||||||
// Reads
|
std::vector<std::string> keys(10);
|
||||||
{
|
for (auto &k : keys) {
|
||||||
auto beginK = numToKey(version - kWindowSize);
|
k = makeKey(rand() % kNumPrefixes, 49);
|
||||||
auto endK = numToKey(version - 1);
|
|
||||||
auto pointRv = version - kWindowSize + rand() % kWindowSize + 1;
|
|
||||||
auto pointK = numToKey(pointRv);
|
|
||||||
weaselab::ConflictSet::ReadRange reads[] = {
|
|
||||||
{
|
|
||||||
{(const uint8_t *)pointK.data(), int(pointK.size())},
|
|
||||||
{nullptr, 0},
|
|
||||||
pointRv,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
{(const uint8_t *)beginK.data(), int(beginK.size())},
|
|
||||||
{(const uint8_t *)endK.data(), int(endK.size())},
|
|
||||||
version - 2,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
weaselab::ConflictSet::Result result[sizeof(reads) / sizeof(reads[0])];
|
|
||||||
cs->check(reads, result, sizeof(reads) / sizeof(reads[0]));
|
|
||||||
// for (int i = 0; i < sizeof(reads) / sizeof(reads[0]); ++i) {
|
|
||||||
// if (result[i] != weaselab::ConflictSet::Commit) {
|
|
||||||
// fprintf(stderr, "Unexpected conflict: [%s, %s) @ %" PRId64 "\n",
|
|
||||||
// printable(reads[i].begin).c_str(),
|
|
||||||
// printable(reads[i].end).c_str(), reads[i].readVersion);
|
|
||||||
// abort();
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
// Writes
|
std::vector<weaselab::ConflictSet::ReadRange> reads(10);
|
||||||
{
|
for (int i = 0; i < reads.size(); ++i) {
|
||||||
weaselab::ConflictSet::WriteRange w;
|
reads[i].begin.p = (const uint8_t *)(keys[i].data());
|
||||||
auto k = numToKey(version);
|
reads[i].begin.len = keys[i].size();
|
||||||
w.begin.p = (const uint8_t *)k.data();
|
reads[i].end.len = 0;
|
||||||
w.end.len = 0;
|
reads[i].readVersion = version - 1;
|
||||||
if (version % (kWindowSize / 2) == 0) {
|
|
||||||
for (int l = 0; l <= k.size(); ++l) {
|
|
||||||
w.begin.len = l;
|
|
||||||
cs->addWrites(&w, 1, version);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
w.begin.len = k.size();
|
|
||||||
cs->addWrites(&w, 1, version);
|
|
||||||
int64_t beginN = version - kWindowSize + rand() % kWindowSize;
|
|
||||||
auto b = numToKey(beginN);
|
|
||||||
auto e = numToKey(beginN + 1000);
|
|
||||||
w.begin.p = (const uint8_t *)b.data();
|
|
||||||
w.begin.len = b.size();
|
|
||||||
w.end.p = (const uint8_t *)e.data();
|
|
||||||
w.end.len = e.size();
|
|
||||||
cs->addWrites(&w, 1, version);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
// GC
|
cs->check(reads.data(), results.data(), 10);
|
||||||
cs->setOldestVersion(version - kWindowSize);
|
|
||||||
++version;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
97
SkipList.cpp
97
SkipList.cpp
@@ -22,6 +22,7 @@
|
|||||||
|
|
||||||
#include "ConflictSet.h"
|
#include "ConflictSet.h"
|
||||||
#include "Internal.h"
|
#include "Internal.h"
|
||||||
|
#include "Metrics.h"
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <span>
|
#include <span>
|
||||||
@@ -434,13 +435,14 @@ public:
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
void detectConflicts(ReadConflictRange *ranges, int count,
|
// Return number of iterations of main loop
|
||||||
ConflictSet::Result *transactionConflictStatus) const {
|
int detectConflicts(ReadConflictRange *ranges, int count,
|
||||||
|
ConflictSet::Result *transactionConflictStatus) const {
|
||||||
const int M = 16;
|
const int M = 16;
|
||||||
int nextJob[M];
|
int nextJob[M];
|
||||||
CheckMax inProgress[M];
|
CheckMax inProgress[M];
|
||||||
if (!count)
|
if (!count)
|
||||||
return;
|
return 0;
|
||||||
|
|
||||||
int started = std::min(M, count);
|
int started = std::min(M, count);
|
||||||
for (int i = 0; i < started; i++) {
|
for (int i = 0; i < started; i++) {
|
||||||
@@ -451,8 +453,9 @@ public:
|
|||||||
|
|
||||||
int prevJob = started - 1;
|
int prevJob = started - 1;
|
||||||
int job = 0;
|
int job = 0;
|
||||||
|
int iters = 0;
|
||||||
// vtune: 340 parts
|
// vtune: 340 parts
|
||||||
while (true) {
|
for (;; ++iters) {
|
||||||
if (inProgress[job].advance()) {
|
if (inProgress[job].advance()) {
|
||||||
if (started == count) {
|
if (started == count) {
|
||||||
if (prevJob == job)
|
if (prevJob == job)
|
||||||
@@ -468,6 +471,7 @@ public:
|
|||||||
prevJob = job;
|
prevJob = job;
|
||||||
job = nextJob[job];
|
job = nextJob[job];
|
||||||
}
|
}
|
||||||
|
return iters;
|
||||||
}
|
}
|
||||||
|
|
||||||
void find(const StringRef *values, Finger *results, int *temp, int count) {
|
void find(const StringRef *values, Finger *results, int *temp, int count) {
|
||||||
@@ -702,15 +706,27 @@ private:
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct ReadContext {
|
||||||
|
int64_t commits_accum = 0;
|
||||||
|
int64_t conflicts_accum = 0;
|
||||||
|
int64_t too_olds_accum = 0;
|
||||||
|
int64_t check_bytes_accum = 0;
|
||||||
|
};
|
||||||
|
|
||||||
struct __attribute__((visibility("hidden"))) ConflictSet::Impl {
|
struct __attribute__((visibility("hidden"))) ConflictSet::Impl {
|
||||||
Impl(int64_t oldestVersion)
|
Impl(int64_t oldestVersion)
|
||||||
: oldestVersion(oldestVersion), newestVersion(oldestVersion),
|
: oldestVersion(oldestVersion), newestVersion(oldestVersion),
|
||||||
skipList(oldestVersion) {}
|
skipList(oldestVersion) {
|
||||||
|
metrics = initMetrics(metricsList, metricsCount);
|
||||||
|
}
|
||||||
|
~Impl() { safe_free(metrics, metricsCount * sizeof(metrics[0])); }
|
||||||
void check(const ConflictSet::ReadRange *reads, ConflictSet::Result *results,
|
void check(const ConflictSet::ReadRange *reads, ConflictSet::Result *results,
|
||||||
int count) const {
|
int count) {
|
||||||
|
ReadContext tls;
|
||||||
Arena arena;
|
Arena arena;
|
||||||
auto *ranges = new (arena) ReadConflictRange[count];
|
auto *ranges = new (arena) ReadConflictRange[count];
|
||||||
for (int i = 0; i < count; ++i) {
|
for (int i = 0; i < count; ++i) {
|
||||||
|
tls.check_bytes_accum += reads[i].begin.len + reads[i].end.len;
|
||||||
ranges[i].begin = {reads[i].begin.p, size_t(reads[i].begin.len)};
|
ranges[i].begin = {reads[i].begin.p, size_t(reads[i].begin.len)};
|
||||||
ranges[i].end = reads[i].end.len > 0
|
ranges[i].end = reads[i].end.len > 0
|
||||||
? StringRef{reads[i].end.p, size_t(reads[i].end.len)}
|
? StringRef{reads[i].end.p, size_t(reads[i].end.len)}
|
||||||
@@ -718,13 +734,22 @@ struct __attribute__((visibility("hidden"))) ConflictSet::Impl {
|
|||||||
ranges[i].version = reads[i].readVersion;
|
ranges[i].version = reads[i].readVersion;
|
||||||
results[i] = ConflictSet::Commit;
|
results[i] = ConflictSet::Commit;
|
||||||
}
|
}
|
||||||
skipList.detectConflicts(ranges, count, results);
|
int iters = 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) {
|
reads[i].readVersion < newestVersion - 2e9) {
|
||||||
results[i] = TooOld;
|
results[i] = TooOld;
|
||||||
}
|
}
|
||||||
|
tls.commits_accum += results[i] == Commit;
|
||||||
|
tls.conflicts_accum += results[i] == Conflict;
|
||||||
|
tls.too_olds_accum += results[i] == TooOld;
|
||||||
}
|
}
|
||||||
|
range_read_iterations_total.add(iters);
|
||||||
|
range_read_total.add(count);
|
||||||
|
commits_total.add(tls.commits_accum);
|
||||||
|
conflicts_total.add(tls.conflicts_accum);
|
||||||
|
too_olds_total.add(tls.too_olds_accum);
|
||||||
|
check_bytes_total.add(tls.check_bytes_accum);
|
||||||
}
|
}
|
||||||
|
|
||||||
void addWrites(const ConflictSet::WriteRange *writes, int count,
|
void addWrites(const ConflictSet::WriteRange *writes, int count,
|
||||||
@@ -788,6 +813,9 @@ struct __attribute__((visibility("hidden"))) ConflictSet::Impl {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void setOldestVersion(int64_t oldestVersion) {
|
void setOldestVersion(int64_t oldestVersion) {
|
||||||
|
// This isn't 100% accurate. It overcounts if you hit the end
|
||||||
|
gc_iterations_total.add(keyUpdates);
|
||||||
|
|
||||||
assert(oldestVersion >= this->oldestVersion);
|
assert(oldestVersion >= this->oldestVersion);
|
||||||
this->oldestVersion = oldestVersion;
|
this->oldestVersion = oldestVersion;
|
||||||
SkipList::Finger finger;
|
SkipList::Finger finger;
|
||||||
@@ -802,6 +830,54 @@ struct __attribute__((visibility("hidden"))) ConflictSet::Impl {
|
|||||||
|
|
||||||
int64_t totalBytes = 0;
|
int64_t totalBytes = 0;
|
||||||
|
|
||||||
|
MetricsV1 *metrics;
|
||||||
|
int metricsCount = 0;
|
||||||
|
Metric *metricsList = nullptr;
|
||||||
|
|
||||||
|
#define GAUGE(name, help) \
|
||||||
|
Gauge name { metricsList, metricsCount, #name, help }
|
||||||
|
#define COUNTER(name, help) \
|
||||||
|
Counter name { metricsList, metricsCount, #name, help }
|
||||||
|
// ==================== METRICS DEFINITIONS ====================
|
||||||
|
COUNTER(range_read_total, "Total number of range reads checked");
|
||||||
|
COUNTER(range_read_iterations_total,
|
||||||
|
"Total number of iterations of the main loops for range read checks");
|
||||||
|
COUNTER(commits_total,
|
||||||
|
"Total number of checks where the result is \"commit\"");
|
||||||
|
COUNTER(conflicts_total,
|
||||||
|
"Total number of checks where the result is \"conflict\"");
|
||||||
|
COUNTER(too_olds_total,
|
||||||
|
"Total number of checks where the result is \"too old\"");
|
||||||
|
COUNTER(check_bytes_total, "Total number of key bytes checked");
|
||||||
|
GAUGE(memory_bytes, "Total number of bytes in use");
|
||||||
|
COUNTER(nodes_allocated_total,
|
||||||
|
"The total number of physical tree nodes allocated");
|
||||||
|
COUNTER(nodes_released_total,
|
||||||
|
"The total number of physical tree nodes released");
|
||||||
|
COUNTER(insert_iterations_total,
|
||||||
|
"The total number of iterations of the main loop for insertion. "
|
||||||
|
"Includes searches where the entry already existed, and so insertion "
|
||||||
|
"did not take place");
|
||||||
|
COUNTER(entries_inserted_total,
|
||||||
|
"The total number of entries inserted in the tree");
|
||||||
|
COUNTER(entries_erased_total,
|
||||||
|
"The total number of entries erased from the tree");
|
||||||
|
COUNTER(
|
||||||
|
gc_iterations_total,
|
||||||
|
"The total number of iterations of the main loop for garbage collection");
|
||||||
|
COUNTER(write_bytes_total, "Total number of key bytes in calls to addWrites");
|
||||||
|
GAUGE(oldest_version,
|
||||||
|
"The lowest version that doesn't result in \"TooOld\" for checks");
|
||||||
|
GAUGE(newest_version, "The version of the most recent call to addWrites");
|
||||||
|
// ==================== END METRICS DEFINITIONS ====================
|
||||||
|
#undef GAUGE
|
||||||
|
#undef COUNTER
|
||||||
|
|
||||||
|
void getMetricsV1(MetricsV1 **metrics, int *count) {
|
||||||
|
*metrics = this->metrics;
|
||||||
|
*count = metricsCount;
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
int64_t keyUpdates = 0;
|
int64_t keyUpdates = 0;
|
||||||
Arena removalArena;
|
Arena removalArena;
|
||||||
@@ -824,6 +900,7 @@ void internal_addWrites(ConflictSet::Impl *impl,
|
|||||||
mallocBytesDelta = 0;
|
mallocBytesDelta = 0;
|
||||||
impl->addWrites(writes, count, writeVersion);
|
impl->addWrites(writes, count, writeVersion);
|
||||||
impl->totalBytes += mallocBytesDelta;
|
impl->totalBytes += mallocBytesDelta;
|
||||||
|
impl->memory_bytes.set(impl->totalBytes);
|
||||||
#if SHOW_MEMORY
|
#if SHOW_MEMORY
|
||||||
if (impl->totalBytes != mallocBytes) {
|
if (impl->totalBytes != mallocBytes) {
|
||||||
abort();
|
abort();
|
||||||
@@ -835,6 +912,7 @@ void internal_setOldestVersion(ConflictSet::Impl *impl, int64_t oldestVersion) {
|
|||||||
mallocBytesDelta = 0;
|
mallocBytesDelta = 0;
|
||||||
impl->setOldestVersion(oldestVersion);
|
impl->setOldestVersion(oldestVersion);
|
||||||
impl->totalBytes += mallocBytesDelta;
|
impl->totalBytes += mallocBytesDelta;
|
||||||
|
impl->memory_bytes.set(impl->totalBytes);
|
||||||
#if SHOW_MEMORY
|
#if SHOW_MEMORY
|
||||||
if (impl->totalBytes != mallocBytes) {
|
if (impl->totalBytes != mallocBytes) {
|
||||||
abort();
|
abort();
|
||||||
@@ -858,12 +936,11 @@ int64_t internal_getBytes(ConflictSet::Impl *impl) { return impl->totalBytes; }
|
|||||||
|
|
||||||
void internal_getMetricsV1(ConflictSet::Impl *impl,
|
void internal_getMetricsV1(ConflictSet::Impl *impl,
|
||||||
ConflictSet::MetricsV1 **metrics, int *count) {
|
ConflictSet::MetricsV1 **metrics, int *count) {
|
||||||
*metrics = nullptr;
|
return impl->getMetricsV1(metrics, count);
|
||||||
*count = 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
double internal_getMetricValue(const ConflictSet::MetricsV1 *metric) {
|
double internal_getMetricValue(const ConflictSet::MetricsV1 *metric) {
|
||||||
return 0;
|
return ((Metric *)metric->p)->value.load(std::memory_order_relaxed);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ConflictSet::check(const ReadRange *reads, Result *results,
|
void ConflictSet::check(const ReadRange *reads, Result *results,
|
||||||
|
BIN
corpus/008e3be49d62c3e7fa3785b882bdb65ee4b68977
Normal file
BIN
corpus/008e3be49d62c3e7fa3785b882bdb65ee4b68977
Normal file
Binary file not shown.
BIN
corpus/0292e5991e94d8861e8240171e7ce2c0a9742616
Normal file
BIN
corpus/0292e5991e94d8861e8240171e7ce2c0a9742616
Normal file
Binary file not shown.
BIN
corpus/0390280ec499c5f687428e700028c230e8ce944e
Normal file
BIN
corpus/0390280ec499c5f687428e700028c230e8ce944e
Normal file
Binary file not shown.
BIN
corpus/05a9e20eac1e7efb9b40fa032c35f76f87622210
Normal file
BIN
corpus/05a9e20eac1e7efb9b40fa032c35f76f87622210
Normal file
Binary file not shown.
BIN
corpus/076952738748aac93fca3e1e59b104547e4177ba
Normal file
BIN
corpus/076952738748aac93fca3e1e59b104547e4177ba
Normal file
Binary file not shown.
BIN
corpus/0b1ed19f50f707d5e805bcbed195bff0bcad8c7c
Normal file
BIN
corpus/0b1ed19f50f707d5e805bcbed195bff0bcad8c7c
Normal file
Binary file not shown.
BIN
corpus/0b287addaa1aa43c286023650cd407af888ea016
Normal file
BIN
corpus/0b287addaa1aa43c286023650cd407af888ea016
Normal file
Binary file not shown.
BIN
corpus/10970b15ecff0b0564ef8dc31c30680e5e4a4421
Normal file
BIN
corpus/10970b15ecff0b0564ef8dc31c30680e5e4a4421
Normal file
Binary file not shown.
BIN
corpus/1357a36bfcab4e48cf165b984e1840f3e8c6ce51
Normal file
BIN
corpus/1357a36bfcab4e48cf165b984e1840f3e8c6ce51
Normal file
Binary file not shown.
BIN
corpus/170a0ec4714b5ee34045037b38f038a8e3fa2035
Normal file
BIN
corpus/170a0ec4714b5ee34045037b38f038a8e3fa2035
Normal file
Binary file not shown.
BIN
corpus/175195f4753b812598e59f9d14742c1aeed67180
Normal file
BIN
corpus/175195f4753b812598e59f9d14742c1aeed67180
Normal file
Binary file not shown.
BIN
corpus/198314d10d86adbfb9d14a0a48cb01fd050cdd50
Normal file
BIN
corpus/198314d10d86adbfb9d14a0a48cb01fd050cdd50
Normal file
Binary file not shown.
BIN
corpus/1f1a703161be463bc598349de981d56fecff1f7a
Normal file
BIN
corpus/1f1a703161be463bc598349de981d56fecff1f7a
Normal file
Binary file not shown.
BIN
corpus/23098c203c4d176ab274847ed6131d1e72bb2f79
Normal file
BIN
corpus/23098c203c4d176ab274847ed6131d1e72bb2f79
Normal file
Binary file not shown.
BIN
corpus/23d4c4704607cea96775f90eec72ec444a53327e
Normal file
BIN
corpus/23d4c4704607cea96775f90eec72ec444a53327e
Normal file
Binary file not shown.
BIN
corpus/256d76a7b41e7ed6e7baca0a4968a872f670b40e
Normal file
BIN
corpus/256d76a7b41e7ed6e7baca0a4968a872f670b40e
Normal file
Binary file not shown.
BIN
corpus/26cb93458db8c96971c2dac90d1656309626057c
Normal file
BIN
corpus/26cb93458db8c96971c2dac90d1656309626057c
Normal file
Binary file not shown.
BIN
corpus/288d8bc1d39945470448074ce98e4cc92e95c4df
Normal file
BIN
corpus/288d8bc1d39945470448074ce98e4cc92e95c4df
Normal file
Binary file not shown.
BIN
corpus/2b2a3848abb733ce26310bce956cc299144e2c37
Normal file
BIN
corpus/2b2a3848abb733ce26310bce956cc299144e2c37
Normal file
Binary file not shown.
BIN
corpus/2cbf5f3c9d7c4fe38f2da48096d37d29fa75eec6
Normal file
BIN
corpus/2cbf5f3c9d7c4fe38f2da48096d37d29fa75eec6
Normal file
Binary file not shown.
BIN
corpus/2f850fa41006d53a3b4869295c319f077f585e27
Normal file
BIN
corpus/2f850fa41006d53a3b4869295c319f077f585e27
Normal file
Binary file not shown.
BIN
corpus/3246c5494cf81eb96a15a962352bf3f62cf44188
Normal file
BIN
corpus/3246c5494cf81eb96a15a962352bf3f62cf44188
Normal file
Binary file not shown.
BIN
corpus/32b1f5278c998e688bf7a44d89f54a2c183e75eb
Normal file
BIN
corpus/32b1f5278c998e688bf7a44d89f54a2c183e75eb
Normal file
Binary file not shown.
BIN
corpus/37636f0129daeb11db2665b746ec1d63650ddea7
Normal file
BIN
corpus/37636f0129daeb11db2665b746ec1d63650ddea7
Normal file
Binary file not shown.
BIN
corpus/3b6efa6b4581d3f75d307ecdd5aa53b4fa3fea90
Normal file
BIN
corpus/3b6efa6b4581d3f75d307ecdd5aa53b4fa3fea90
Normal file
Binary file not shown.
BIN
corpus/3feb7520632cdb454178f4fa5b63a5665d739e1c
Normal file
BIN
corpus/3feb7520632cdb454178f4fa5b63a5665d739e1c
Normal file
Binary file not shown.
BIN
corpus/403cac13d939872be09b35ba73712a7026412ad2
Normal file
BIN
corpus/403cac13d939872be09b35ba73712a7026412ad2
Normal file
Binary file not shown.
BIN
corpus/4137466d9c418d90463f635ac6401ec69da598ab
Normal file
BIN
corpus/4137466d9c418d90463f635ac6401ec69da598ab
Normal file
Binary file not shown.
BIN
corpus/4206bbb85073ab30a5f04f17bcec0b6b09dec446
Normal file
BIN
corpus/4206bbb85073ab30a5f04f17bcec0b6b09dec446
Normal file
Binary file not shown.
BIN
corpus/4aee549dc867bd0fa32db38bd1680d66ef1a80e4
Normal file
BIN
corpus/4aee549dc867bd0fa32db38bd1680d66ef1a80e4
Normal file
Binary file not shown.
BIN
corpus/4e12dbd10635705a7230aa0f062e590264828cde
Normal file
BIN
corpus/4e12dbd10635705a7230aa0f062e590264828cde
Normal file
Binary file not shown.
BIN
corpus/4ee3990f6a97d901eaa69a32216a17f9f86e8c77
Normal file
BIN
corpus/4ee3990f6a97d901eaa69a32216a17f9f86e8c77
Normal file
Binary file not shown.
BIN
corpus/54952d3ba4a1dca49238f941dee26d26edc46db2
Normal file
BIN
corpus/54952d3ba4a1dca49238f941dee26d26edc46db2
Normal file
Binary file not shown.
BIN
corpus/57e1c08766ffade2c8221997d16b5e0ad7ecc93c
Normal file
BIN
corpus/57e1c08766ffade2c8221997d16b5e0ad7ecc93c
Normal file
Binary file not shown.
BIN
corpus/5acdc011bd62a40c8e018a2621545bf4e005a903
Normal file
BIN
corpus/5acdc011bd62a40c8e018a2621545bf4e005a903
Normal file
Binary file not shown.
BIN
corpus/5ede0d70336f00fad64d4a617e5b34f53533a306
Normal file
BIN
corpus/5ede0d70336f00fad64d4a617e5b34f53533a306
Normal file
Binary file not shown.
BIN
corpus/63edba2479b48db7458149a7df2e2c224920877c
Normal file
BIN
corpus/63edba2479b48db7458149a7df2e2c224920877c
Normal file
Binary file not shown.
BIN
corpus/6769617662feb59ce9e326d3263cc75951e0a0bc
Normal file
BIN
corpus/6769617662feb59ce9e326d3263cc75951e0a0bc
Normal file
Binary file not shown.
BIN
corpus/688ce2c66897225949c811ce88c2fde511d69047
Normal file
BIN
corpus/688ce2c66897225949c811ce88c2fde511d69047
Normal file
Binary file not shown.
BIN
corpus/6b44ebb59ae80f72048e8c2c5572a7901166a461
Normal file
BIN
corpus/6b44ebb59ae80f72048e8c2c5572a7901166a461
Normal file
Binary file not shown.
BIN
corpus/6bea64428c692936d5017a7bfc9a9bbae4a7c646
Normal file
BIN
corpus/6bea64428c692936d5017a7bfc9a9bbae4a7c646
Normal file
Binary file not shown.
BIN
corpus/6bf94eb62dc0d1ac85f7529f3efbdea197c37c4a
Normal file
BIN
corpus/6bf94eb62dc0d1ac85f7529f3efbdea197c37c4a
Normal file
Binary file not shown.
BIN
corpus/6cc2abc6c19e461912bb07ae9d3031ebb1c6f2a7
Normal file
BIN
corpus/6cc2abc6c19e461912bb07ae9d3031ebb1c6f2a7
Normal file
Binary file not shown.
BIN
corpus/6edf9950965a9643a3f5611164e77fdcc3f31b99
Normal file
BIN
corpus/6edf9950965a9643a3f5611164e77fdcc3f31b99
Normal file
Binary file not shown.
BIN
corpus/73894bc13e0bae69f58b228e148341143ebd5a8a
Normal file
BIN
corpus/73894bc13e0bae69f58b228e148341143ebd5a8a
Normal file
Binary file not shown.
BIN
corpus/74ebc76aa3859fb9db3b51af55713cbd845f99f0
Normal file
BIN
corpus/74ebc76aa3859fb9db3b51af55713cbd845f99f0
Normal file
Binary file not shown.
BIN
corpus/7754ffffb55baeb3e25d9c772d11e0efec95f751
Normal file
BIN
corpus/7754ffffb55baeb3e25d9c772d11e0efec95f751
Normal file
Binary file not shown.
BIN
corpus/7879186e12b1fa9339efa2aa37e80136df0e427c
Normal file
BIN
corpus/7879186e12b1fa9339efa2aa37e80136df0e427c
Normal file
Binary file not shown.
BIN
corpus/79e82b2aeffca8c33516ce8723ac5acfa85e2c61
Normal file
BIN
corpus/79e82b2aeffca8c33516ce8723ac5acfa85e2c61
Normal file
Binary file not shown.
BIN
corpus/7badef6c646baf6f1ac4aadedcb3842be1dd876c
Normal file
BIN
corpus/7badef6c646baf6f1ac4aadedcb3842be1dd876c
Normal file
Binary file not shown.
BIN
corpus/7e7b26c627325c68b5de1cb17d7e0bd35f6e2039
Normal file
BIN
corpus/7e7b26c627325c68b5de1cb17d7e0bd35f6e2039
Normal file
Binary file not shown.
BIN
corpus/7e7b3164f81316a25b103f3b391867692ea5cec0
Normal file
BIN
corpus/7e7b3164f81316a25b103f3b391867692ea5cec0
Normal file
Binary file not shown.
BIN
corpus/834f54738de969afb72c5355bc6bf226aee2bc46
Normal file
BIN
corpus/834f54738de969afb72c5355bc6bf226aee2bc46
Normal file
Binary file not shown.
BIN
corpus/83fb951c0b10b3a0beffbb100314c717a8085c05
Normal file
BIN
corpus/83fb951c0b10b3a0beffbb100314c717a8085c05
Normal file
Binary file not shown.
BIN
corpus/841521614f7ecab8ba225275035ad413024dd552
Normal file
BIN
corpus/841521614f7ecab8ba225275035ad413024dd552
Normal file
Binary file not shown.
BIN
corpus/845ac7d3ef2e4e3844308ba4aa966cef34dae1c7
Normal file
BIN
corpus/845ac7d3ef2e4e3844308ba4aa966cef34dae1c7
Normal file
Binary file not shown.
BIN
corpus/86707e4f00fefde1e3fde7e5d3d66260658f0dbc
Normal file
BIN
corpus/86707e4f00fefde1e3fde7e5d3d66260658f0dbc
Normal file
Binary file not shown.
BIN
corpus/8ad5c9dd3bc2f5323dc895bd924c45351413ee4d
Normal file
BIN
corpus/8ad5c9dd3bc2f5323dc895bd924c45351413ee4d
Normal file
Binary file not shown.
BIN
corpus/8c41ef3f7296bfb6205b075316ef446b481031fd
Normal file
BIN
corpus/8c41ef3f7296bfb6205b075316ef446b481031fd
Normal file
Binary file not shown.
BIN
corpus/9010fce51ca20e78fadcf10003ad80dec1a92b38
Normal file
BIN
corpus/9010fce51ca20e78fadcf10003ad80dec1a92b38
Normal file
Binary file not shown.
BIN
corpus/927404b87e7cf01180250b4c9310efa8911462e4
Normal file
BIN
corpus/927404b87e7cf01180250b4c9310efa8911462e4
Normal file
Binary file not shown.
BIN
corpus/9a4c784fc270a66e635d42ca267ae9edeca2b948
Normal file
BIN
corpus/9a4c784fc270a66e635d42ca267ae9edeca2b948
Normal file
Binary file not shown.
BIN
corpus/9ae60c538e98c4569e1ed1744ee5be7cc8d02c51
Normal file
BIN
corpus/9ae60c538e98c4569e1ed1744ee5be7cc8d02c51
Normal file
Binary file not shown.
BIN
corpus/9be9e164978a0553b3a1cfe45c197ec1159ee7d0
Normal file
BIN
corpus/9be9e164978a0553b3a1cfe45c197ec1159ee7d0
Normal file
Binary file not shown.
BIN
corpus/9c3032fc7003dbee823e55556bc24c5f5833311e
Normal file
BIN
corpus/9c3032fc7003dbee823e55556bc24c5f5833311e
Normal file
Binary file not shown.
BIN
corpus/a1a4d54aea9674d4b415cdae8626b6991e01ed1e
Normal file
BIN
corpus/a1a4d54aea9674d4b415cdae8626b6991e01ed1e
Normal file
Binary file not shown.
BIN
corpus/a4b24364f0a23987e8eeac1ef66e16e91766ff92
Normal file
BIN
corpus/a4b24364f0a23987e8eeac1ef66e16e91766ff92
Normal file
Binary file not shown.
BIN
corpus/a4f4b06d1c524d09ad4b9515353f0dcbc1e8ed92
Normal file
BIN
corpus/a4f4b06d1c524d09ad4b9515353f0dcbc1e8ed92
Normal file
Binary file not shown.
BIN
corpus/a7298a2735842a9f9a9f7c40c1f940df308c4107
Normal file
BIN
corpus/a7298a2735842a9f9a9f7c40c1f940df308c4107
Normal file
Binary file not shown.
BIN
corpus/a8614158379e0b3f8eec546e17d3f73a9b8e81fe
Normal file
BIN
corpus/a8614158379e0b3f8eec546e17d3f73a9b8e81fe
Normal file
Binary file not shown.
BIN
corpus/aa26947b4c530eb1cd226950fe6202958dc9a755
Normal file
BIN
corpus/aa26947b4c530eb1cd226950fe6202958dc9a755
Normal file
Binary file not shown.
BIN
corpus/af593f241017f854b87f692ed318d7935d34510f
Normal file
BIN
corpus/af593f241017f854b87f692ed318d7935d34510f
Normal file
Binary file not shown.
BIN
corpus/b216fa66614fbd934e587e97f85f72ac7965f8ff
Normal file
BIN
corpus/b216fa66614fbd934e587e97f85f72ac7965f8ff
Normal file
Binary file not shown.
BIN
corpus/b5814790889fd3452daa4beed6f1167ee1986a17
Normal file
BIN
corpus/b5814790889fd3452daa4beed6f1167ee1986a17
Normal file
Binary file not shown.
BIN
corpus/b73d259e5934e4275aef3298f16aeaf63b294a84
Normal file
BIN
corpus/b73d259e5934e4275aef3298f16aeaf63b294a84
Normal file
Binary file not shown.
BIN
corpus/bc32def2213b5758b2e03413209c2f67e2768ca4
Normal file
BIN
corpus/bc32def2213b5758b2e03413209c2f67e2768ca4
Normal file
Binary file not shown.
BIN
corpus/be536e15193fbf20b4c6daf9da5543d4a5048ea0
Normal file
BIN
corpus/be536e15193fbf20b4c6daf9da5543d4a5048ea0
Normal file
Binary file not shown.
BIN
corpus/bf103ed62958ca20d91b5cc9774c1977b190f251
Normal file
BIN
corpus/bf103ed62958ca20d91b5cc9774c1977b190f251
Normal file
Binary file not shown.
BIN
corpus/c1cc87e1bdfb7e6a6d2e039bf7686c169a7d2bfb
Normal file
BIN
corpus/c1cc87e1bdfb7e6a6d2e039bf7686c169a7d2bfb
Normal file
Binary file not shown.
BIN
corpus/c49c4863fef47396b5d588a453275289c66e13cf
Normal file
BIN
corpus/c49c4863fef47396b5d588a453275289c66e13cf
Normal file
Binary file not shown.
BIN
corpus/c7937b0759b0319ef1eaa9e938bc2fae4a1bb9e3
Normal file
BIN
corpus/c7937b0759b0319ef1eaa9e938bc2fae4a1bb9e3
Normal file
Binary file not shown.
BIN
corpus/cc297c7df1c523973836d6eed05187664cf8b028
Normal file
BIN
corpus/cc297c7df1c523973836d6eed05187664cf8b028
Normal file
Binary file not shown.
BIN
corpus/cdf9b63169be156e3d3981f0046d63a833a8a9d4
Normal file
BIN
corpus/cdf9b63169be156e3d3981f0046d63a833a8a9d4
Normal file
Binary file not shown.
BIN
corpus/cfaba94477a30e01d857f9e63b26f04ade740205
Normal file
BIN
corpus/cfaba94477a30e01d857f9e63b26f04ade740205
Normal file
Binary file not shown.
BIN
corpus/cfd924c0ac6d2c4b8b091373cc110094c8ad47d1
Normal file
BIN
corpus/cfd924c0ac6d2c4b8b091373cc110094c8ad47d1
Normal file
Binary file not shown.
BIN
corpus/d12ac70ac49ad2d5b8fdc2cb1f800f9702c395bf
Normal file
BIN
corpus/d12ac70ac49ad2d5b8fdc2cb1f800f9702c395bf
Normal file
Binary file not shown.
BIN
corpus/d426172fde1e278c2c00cb6618ecb0fc18f246b6
Normal file
BIN
corpus/d426172fde1e278c2c00cb6618ecb0fc18f246b6
Normal file
Binary file not shown.
BIN
corpus/dba9de8358577750602a49cc36bfcdc1961f99f2
Normal file
BIN
corpus/dba9de8358577750602a49cc36bfcdc1961f99f2
Normal file
Binary file not shown.
BIN
corpus/dcbb39e697bfa20d9540b4cfe3560b90dafab2f8
Normal file
BIN
corpus/dcbb39e697bfa20d9540b4cfe3560b90dafab2f8
Normal file
Binary file not shown.
BIN
corpus/dce5be7f32f8303efb13a259c509d18df6baec4c
Normal file
BIN
corpus/dce5be7f32f8303efb13a259c509d18df6baec4c
Normal file
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user