Compare commits
23 Commits
v0.0.8
...
8ce14c58a4
Author | SHA1 | Date | |
---|---|---|---|
8ce14c58a4 | |||
56e847b63c | |||
7fd1c9e140 | |||
ebaac253e2 | |||
9b470a367c | |||
e7806a36d1 | |||
ffd1dfe74d | |||
c39af9117f | |||
ed274c24d7 | |||
cecfcc0da7 | |||
f6edde0e50 | |||
04ac41a7e7 | |||
354920f86f | |||
bfd02503e7 | |||
d0bd293f8d | |||
41e887c358 | |||
e394e3d96a | |||
3288c583e4 | |||
ef14003781 | |||
3ac16bc966 | |||
1e82f7fe22 | |||
4182d904c5 | |||
bd8ed4e7bd |
23
Bench.cpp
23
Bench.cpp
@@ -358,7 +358,30 @@ void benchWorstCaseForRadixRangeRead() {
|
|||||||
// }
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void benchMetrics() {
|
||||||
|
ankerl::nanobench::Bench bench;
|
||||||
|
ConflictSet cs{0};
|
||||||
|
|
||||||
|
int count;
|
||||||
|
ConflictSet::MetricsV1 *m;
|
||||||
|
cs.getMetricsV1(&m, &count);
|
||||||
|
bench.batch(count);
|
||||||
|
bench.run("fetch metric", [&]() {
|
||||||
|
for (int i = 0; i < count; ++i) {
|
||||||
|
m[i].getValue();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void benchCreateAndDestroy() {
|
||||||
|
ankerl::nanobench::Bench bench;
|
||||||
|
|
||||||
|
bench.run("create and destroy", [&]() { ConflictSet cs{0}; });
|
||||||
|
}
|
||||||
|
|
||||||
int main(void) {
|
int main(void) {
|
||||||
benchConflictSet();
|
benchConflictSet();
|
||||||
benchWorstCaseForRadixRangeRead();
|
benchWorstCaseForRadixRangeRead();
|
||||||
|
benchMetrics();
|
||||||
|
benchCreateAndDestroy();
|
||||||
}
|
}
|
||||||
|
@@ -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.8
|
VERSION 0.0.9
|
||||||
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"
|
||||||
@@ -60,6 +60,8 @@ cmake_pop_check_state()
|
|||||||
option(USE_SIMD_FALLBACK
|
option(USE_SIMD_FALLBACK
|
||||||
"Use fallback implementations of functions that use SIMD" OFF)
|
"Use fallback implementations of functions that use SIMD" OFF)
|
||||||
|
|
||||||
|
option(DISABLE_TSAN "Disable TSAN" OFF)
|
||||||
|
|
||||||
# This is encouraged according to
|
# This is encouraged according to
|
||||||
# https://valgrind.org/docs/manual/manual-core-adv.html#manual-core-adv.clientreq
|
# https://valgrind.org/docs/manual/manual-core-adv.html#manual-core-adv.clientreq
|
||||||
include_directories(SYSTEM ${CMAKE_CURRENT_SOURCE_DIR}/third_party/valgrind)
|
include_directories(SYSTEM ${CMAKE_CURRENT_SOURCE_DIR}/third_party/valgrind)
|
||||||
@@ -245,7 +247,7 @@ if(CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR AND BUILD_TESTING)
|
|||||||
endforeach()
|
endforeach()
|
||||||
|
|
||||||
# tsan tests
|
# tsan tests
|
||||||
if(NOT CMAKE_CROSSCOMPILING AND NOT CMAKE_BUILD_TYPE STREQUAL Debug)
|
if(NOT CMAKE_CROSSCOMPILING AND NOT DISABLE_TSAN)
|
||||||
add_executable(tsan_driver ConflictSet.cpp FuzzTestDriver.cpp)
|
add_executable(tsan_driver ConflictSet.cpp FuzzTestDriver.cpp)
|
||||||
target_compile_options(tsan_driver PRIVATE ${TEST_FLAGS} -fsanitize=thread)
|
target_compile_options(tsan_driver PRIVATE ${TEST_FLAGS} -fsanitize=thread)
|
||||||
target_link_options(tsan_driver PRIVATE -fsanitize=thread)
|
target_link_options(tsan_driver PRIVATE -fsanitize=thread)
|
||||||
|
227
ConflictSet.cpp
227
ConflictSet.cpp
@@ -29,6 +29,7 @@ limitations under the License.
|
|||||||
#include <span>
|
#include <span>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <string_view>
|
#include <string_view>
|
||||||
|
#include <sys/time.h>
|
||||||
#include <type_traits>
|
#include <type_traits>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
||||||
@@ -546,7 +547,48 @@ 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<double> 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(double 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(double value) {
|
||||||
|
assert(value >= 0);
|
||||||
|
static_assert(std::atomic<double>::is_always_lock_free);
|
||||||
|
double old = this->value.load(std::memory_order_relaxed);
|
||||||
|
for (;;) {
|
||||||
|
double newVal = old + value;
|
||||||
|
if (this->value.compare_exchange_weak(old, newVal,
|
||||||
|
std::memory_order_relaxed)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
template <class T> struct BoundedFreeListAllocator {
|
template <class T> struct BoundedFreeListAllocator {
|
||||||
|
|
||||||
static_assert(sizeof(T) >= sizeof(void *));
|
static_assert(sizeof(T) >= sizeof(void *));
|
||||||
static_assert(std::derived_from<T, Node>);
|
static_assert(std::derived_from<T, Node>);
|
||||||
static_assert(std::is_trivial_v<T>);
|
static_assert(std::is_trivial_v<T>);
|
||||||
@@ -1670,17 +1712,30 @@ struct SearchStepWise {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
thread_local double point_read_accum = 0;
|
||||||
|
thread_local double prefix_read_accum = 0;
|
||||||
|
thread_local double range_read_accum = 0;
|
||||||
|
thread_local double point_read_short_circuit_accum = 0;
|
||||||
|
thread_local double prefix_read_short_circuit_accum = 0;
|
||||||
|
thread_local double range_read_short_circuit_accum = 0;
|
||||||
|
thread_local double point_read_iterations_accum = 0;
|
||||||
|
thread_local double prefix_read_iterations_accum = 0;
|
||||||
|
thread_local double range_read_iterations_accum = 0;
|
||||||
|
thread_local double range_read_node_scan_accum = 0;
|
||||||
|
|
||||||
// Logically this is the same as performing firstGeq and then checking against
|
// Logically this is the same as performing firstGeq and then checking against
|
||||||
// point or range version according to cmp, but this version short circuits as
|
// point or range version according to cmp, but this version short circuits as
|
||||||
// soon as it can prove that there's no conflict.
|
// soon as it can prove that there's no conflict.
|
||||||
bool checkPointRead(Node *n, const std::span<const uint8_t> key,
|
bool checkPointRead(Node *n, const std::span<const uint8_t> key,
|
||||||
InternalVersionT readVersion, ConflictSet::Impl *impl) {
|
InternalVersionT readVersion, ConflictSet::Impl *impl) {
|
||||||
|
++point_read_accum;
|
||||||
#if DEBUG_VERBOSE && !defined(NDEBUG)
|
#if DEBUG_VERBOSE && !defined(NDEBUG)
|
||||||
fprintf(stderr, "Check point read: %s\n", printable(key).c_str());
|
fprintf(stderr, "Check point read: %s\n", printable(key).c_str());
|
||||||
#endif
|
#endif
|
||||||
auto remaining = key;
|
auto remaining = key;
|
||||||
for (;;) {
|
for (;; ++point_read_iterations_accum) {
|
||||||
if (maxVersion(n, impl) <= readVersion) {
|
if (maxVersion(n, impl) <= readVersion) {
|
||||||
|
++point_read_short_circuit_accum;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (remaining.size() == 0) {
|
if (remaining.size() == 0) {
|
||||||
@@ -1752,17 +1807,19 @@ downLeftSpine:
|
|||||||
// short circuits as soon as it can prove that there's no conflict.
|
// short circuits as soon as it can prove that there's no conflict.
|
||||||
bool checkPrefixRead(Node *n, const std::span<const uint8_t> key,
|
bool checkPrefixRead(Node *n, const std::span<const uint8_t> key,
|
||||||
InternalVersionT readVersion, ConflictSet::Impl *impl) {
|
InternalVersionT readVersion, ConflictSet::Impl *impl) {
|
||||||
|
++prefix_read_accum;
|
||||||
#if DEBUG_VERBOSE && !defined(NDEBUG)
|
#if DEBUG_VERBOSE && !defined(NDEBUG)
|
||||||
fprintf(stderr, "Check prefix read: %s\n", printable(key).c_str());
|
fprintf(stderr, "Check prefix read: %s\n", printable(key).c_str());
|
||||||
#endif
|
#endif
|
||||||
auto remaining = key;
|
auto remaining = key;
|
||||||
for (;;) {
|
for (;; ++prefix_read_iterations_accum) {
|
||||||
auto m = maxVersion(n, impl);
|
auto m = maxVersion(n, impl);
|
||||||
if (remaining.size() == 0) {
|
if (remaining.size() == 0) {
|
||||||
return m <= readVersion;
|
return m <= readVersion;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (m <= readVersion) {
|
if (m <= readVersion) {
|
||||||
|
++prefix_read_short_circuit_accum;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1993,6 +2050,7 @@ scan16(const InternalVersionT *vs, int begin, int end,
|
|||||||
template <bool kAVX512>
|
template <bool kAVX512>
|
||||||
bool checkMaxBetweenExclusive(Node *n, int begin, int end,
|
bool checkMaxBetweenExclusive(Node *n, int begin, int end,
|
||||||
InternalVersionT readVersion) {
|
InternalVersionT readVersion) {
|
||||||
|
++range_read_node_scan_accum;
|
||||||
assume(-1 <= begin);
|
assume(-1 <= begin);
|
||||||
assume(begin <= 256);
|
assume(begin <= 256);
|
||||||
assume(-1 <= end);
|
assume(-1 <= end);
|
||||||
@@ -2601,13 +2659,16 @@ bool checkRangeReadImpl(Node *n, std::span<const uint8_t> begin,
|
|||||||
return checkPrefixRead(n, begin, readVersion, impl);
|
return checkPrefixRead(n, begin, readVersion, impl);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
++range_read_accum;
|
||||||
|
|
||||||
SearchStepWise search{n, begin.subspan(0, lcp)};
|
SearchStepWise search{n, begin.subspan(0, lcp)};
|
||||||
Arena arena;
|
Arena arena;
|
||||||
for (;;) {
|
for (;; ++range_read_iterations_accum) {
|
||||||
assert(getSearchPath(arena, search.n) <=>
|
assert(getSearchPath(arena, search.n) <=>
|
||||||
begin.subspan(0, lcp - search.remaining.size()) ==
|
begin.subspan(0, lcp - search.remaining.size()) ==
|
||||||
0);
|
0);
|
||||||
if (maxVersion(search.n, impl) <= readVersion) {
|
if (maxVersion(search.n, impl) <= readVersion) {
|
||||||
|
++range_read_short_circuit_accum;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (search.step()) {
|
if (search.step()) {
|
||||||
@@ -2648,18 +2709,21 @@ bool checkRangeReadImpl(Node *n, std::span<const uint8_t> begin,
|
|||||||
bool leftDone = checkRangeLeftSide.step();
|
bool leftDone = checkRangeLeftSide.step();
|
||||||
bool rightDone = checkRangeRightSide.step();
|
bool rightDone = checkRangeRightSide.step();
|
||||||
if (!leftDone && !rightDone) {
|
if (!leftDone && !rightDone) {
|
||||||
|
range_read_iterations_accum += 2;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (leftDone && rightDone) {
|
if (leftDone && rightDone) {
|
||||||
break;
|
break;
|
||||||
} else if (leftDone) {
|
} else if (leftDone) {
|
||||||
while (!checkRangeRightSide.step())
|
while (!checkRangeRightSide.step()) {
|
||||||
;
|
++range_read_iterations_accum;
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
} else {
|
} else {
|
||||||
assert(rightDone);
|
assert(rightDone);
|
||||||
while (!checkRangeLeftSide.step())
|
while (!checkRangeLeftSide.step()) {
|
||||||
;
|
++range_read_iterations_accum;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -3004,11 +3068,22 @@ Node *firstGeqPhysical(Node *n, const std::span<const uint8_t> key) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#define MEASURE_CHECK_CPU_TIME 0
|
||||||
|
|
||||||
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) {
|
||||||
|
#if MEASURE_CHECK_CPU_TIME
|
||||||
|
timespec ts_begin;
|
||||||
|
clock_gettime(CLOCK_THREAD_CPUTIME_ID, &ts_begin);
|
||||||
|
#endif
|
||||||
|
int commits_accum = 0;
|
||||||
|
int conflicts_accum = 0;
|
||||||
|
int too_olds_accum = 0;
|
||||||
|
double check_byte_accum = 0;
|
||||||
for (int i = 0; i < count; ++i) {
|
for (int i = 0; i < count; ++i) {
|
||||||
const auto &r = reads[i];
|
const auto &r = reads[i];
|
||||||
|
check_byte_accum += r.begin.len + r.end.len;
|
||||||
auto begin = std::span<const uint8_t>(r.begin.p, r.begin.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);
|
auto end = std::span<const uint8_t>(r.end.p, r.end.len);
|
||||||
assert(oldestVersionFullPrecision >=
|
assert(oldestVersionFullPrecision >=
|
||||||
@@ -3022,7 +3097,38 @@ struct __attribute__((visibility("hidden"))) ConflictSet::Impl {
|
|||||||
InternalVersionT(reads[i].readVersion), this))
|
InternalVersionT(reads[i].readVersion), this))
|
||||||
? Commit
|
? Commit
|
||||||
: Conflict;
|
: Conflict;
|
||||||
|
commits_accum += result[i] == Commit;
|
||||||
|
conflicts_accum += result[i] == Conflict;
|
||||||
|
too_olds_accum += result[i] == TooOld;
|
||||||
}
|
}
|
||||||
|
point_read_total.add(std::exchange(point_read_accum, 0));
|
||||||
|
prefix_read_total.add(std::exchange(prefix_read_accum, 0));
|
||||||
|
range_read_total.add(std::exchange(range_read_accum, 0));
|
||||||
|
range_read_node_scan_total.add(
|
||||||
|
std::exchange(range_read_node_scan_accum, 0));
|
||||||
|
point_read_short_circuit_total.add(
|
||||||
|
std::exchange(point_read_short_circuit_accum, 0));
|
||||||
|
prefix_read_short_circuit_total.add(
|
||||||
|
std::exchange(prefix_read_short_circuit_accum, 0));
|
||||||
|
range_read_short_circuit_total.add(
|
||||||
|
std::exchange(range_read_short_circuit_accum, 0));
|
||||||
|
point_read_iterations_total.add(
|
||||||
|
std::exchange(point_read_iterations_accum, 0));
|
||||||
|
prefix_read_iterations_total.add(
|
||||||
|
std::exchange(prefix_read_iterations_accum, 0));
|
||||||
|
range_read_iterations_total.add(
|
||||||
|
std::exchange(range_read_iterations_accum, 0));
|
||||||
|
commits_total.add(commits_accum);
|
||||||
|
conflicts_total.add(conflicts_accum);
|
||||||
|
too_olds_total.add(too_olds_accum);
|
||||||
|
check_bytes_total.add(check_byte_accum);
|
||||||
|
#if MEASURE_CHECK_CPU_TIME
|
||||||
|
timespec ts_end;
|
||||||
|
clock_gettime(CLOCK_THREAD_CPUTIME_ID, &ts_end);
|
||||||
|
check_cpu_seconds_total.add(
|
||||||
|
std::max<double>(0, (ts_end.tv_nsec * 1e-9 + ts_end.tv_sec) -
|
||||||
|
(ts_begin.tv_nsec * 1e-9 + ts_begin.tv_sec)));
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
void addWrites(const WriteRange *writes, int count, int64_t writeVersion) {
|
void addWrites(const WriteRange *writes, int count, int64_t writeVersion) {
|
||||||
@@ -3056,6 +3162,8 @@ struct __attribute__((visibility("hidden"))) ConflictSet::Impl {
|
|||||||
this);
|
this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
memory_bytes.set(totalBytes);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Spends up to `fuel` gc'ing, and returns its unused fuel. Reclaims memory
|
// Spends up to `fuel` gc'ing, and returns its unused fuel. Reclaims memory
|
||||||
@@ -3070,8 +3178,12 @@ struct __attribute__((visibility("hidden"))) ConflictSet::Impl {
|
|||||||
rootMaxVersion = std::max(rootMaxVersion, oldestVersion);
|
rootMaxVersion = std::max(rootMaxVersion, oldestVersion);
|
||||||
n = nextPhysical(n);
|
n = nextPhysical(n);
|
||||||
}
|
}
|
||||||
for (; fuel > 0 && n != nullptr; --fuel) {
|
for (; fuel > 0 && n != nullptr;) {
|
||||||
rezero(n, oldestVersion);
|
rezero(n, oldestVersion);
|
||||||
|
// The "make sure gc keeps up with writes" calculations assume that we're
|
||||||
|
// scanning key by key, not node by node. Make sure we only spend fuel
|
||||||
|
// when there's a logical entry.
|
||||||
|
fuel -= n->entryPresent;
|
||||||
if (n->entryPresent && std::max(n->entry.pointVersion,
|
if (n->entryPresent && std::max(n->entry.pointVersion,
|
||||||
n->entry.rangeVersion) <= oldestVersion) {
|
n->entry.rangeVersion) <= oldestVersion) {
|
||||||
// Any transaction n would have prevented from committing is
|
// Any transaction n would have prevented from committing is
|
||||||
@@ -3121,6 +3233,8 @@ struct __attribute__((visibility("hidden"))) ConflictSet::Impl {
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
keyUpdates = gcScanStep(keyUpdates);
|
keyUpdates = gcScanStep(keyUpdates);
|
||||||
|
|
||||||
|
memory_bytes.set(totalBytes);
|
||||||
}
|
}
|
||||||
|
|
||||||
int64_t getBytes() const { return totalBytes; }
|
int64_t getBytes() const { return totalBytes; }
|
||||||
@@ -3156,8 +3270,14 @@ struct __attribute__((visibility("hidden"))) ConflictSet::Impl {
|
|||||||
// Intentionally not resetting totalBytes
|
// Intentionally not resetting totalBytes
|
||||||
}
|
}
|
||||||
|
|
||||||
explicit Impl(int64_t oldestVersion) { init(oldestVersion); }
|
explicit Impl(int64_t oldestVersion) {
|
||||||
~Impl() { destroyTree(root); }
|
init(oldestVersion);
|
||||||
|
initMetrics();
|
||||||
|
}
|
||||||
|
~Impl() {
|
||||||
|
destroyTree(root);
|
||||||
|
safe_free(metrics, metricsCount * sizeof(metrics[0]));
|
||||||
|
}
|
||||||
|
|
||||||
NodeAllocators allocators;
|
NodeAllocators allocators;
|
||||||
|
|
||||||
@@ -3173,8 +3293,77 @@ struct __attribute__((visibility("hidden"))) ConflictSet::Impl {
|
|||||||
int64_t oldestVersionAtGcBegin;
|
int64_t oldestVersionAtGcBegin;
|
||||||
int64_t newestVersionFullPrecision;
|
int64_t newestVersionFullPrecision;
|
||||||
int64_t totalBytes = 0;
|
int64_t totalBytes = 0;
|
||||||
|
|
||||||
|
MetricsV1 *metrics;
|
||||||
|
int metricsCount = 0;
|
||||||
|
void initMetrics() {
|
||||||
|
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) \
|
||||||
|
Gauge name { this, #name, help }
|
||||||
|
#define COUNTER(name, help) \
|
||||||
|
Counter name { this, #name, help }
|
||||||
|
// ==================== METRICS DEFINITIONS ====================
|
||||||
|
GAUGE(memory_bytes, "Total number of bytes in use");
|
||||||
|
COUNTER(point_read_total, "Total number of point reads checked");
|
||||||
|
COUNTER(point_read_short_circuit_total,
|
||||||
|
"Total number of point reads that did not require a full search to "
|
||||||
|
"check");
|
||||||
|
COUNTER(point_read_iterations_total,
|
||||||
|
"Total number of iterations of the main loop for point read checks");
|
||||||
|
COUNTER(prefix_read_total, "Total number of prefix reads checked");
|
||||||
|
COUNTER(prefix_read_short_circuit_total,
|
||||||
|
"Total number of prefix reads that did not require a full search to "
|
||||||
|
"check");
|
||||||
|
COUNTER(prefix_read_iterations_total,
|
||||||
|
"Total number of iterations of the main loop for prefix read checks");
|
||||||
|
COUNTER(range_read_total, "Total number of range reads checked");
|
||||||
|
COUNTER(range_read_short_circuit_total,
|
||||||
|
"Total number of range reads that did not require a full search to "
|
||||||
|
"check");
|
||||||
|
COUNTER(range_read_iterations_total,
|
||||||
|
"Total number of iterations of the main loops for range read checks");
|
||||||
|
COUNTER(range_read_node_scan_total,
|
||||||
|
"Total number of scans of individual nodes while "
|
||||||
|
"checking a range read");
|
||||||
|
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");
|
||||||
|
#if MEASURE_CHECK_CPU_TIME
|
||||||
|
COUNTER(check_cpu_seconds_total,
|
||||||
|
"Total cpu seconds spent in a call to check");
|
||||||
|
#endif
|
||||||
|
// ==================== END METRICS DEFINITIONS ====================
|
||||||
|
#undef GAUGE
|
||||||
|
#undef COUNTER
|
||||||
|
|
||||||
|
void getMetricsV1(MetricsV1 **metrics, int *count) {
|
||||||
|
*metrics = this->metrics;
|
||||||
|
*count = metricsCount;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
InternalVersionT maxVersion(Node *n, ConflictSet::Impl *impl) {
|
InternalVersionT maxVersion(Node *n, ConflictSet::Impl *impl) {
|
||||||
int index = n->parentsIndex;
|
int index = n->parentsIndex;
|
||||||
n = n->parent;
|
n = n->parent;
|
||||||
@@ -3304,6 +3493,15 @@ void internal_destroy(ConflictSet::Impl *impl) {
|
|||||||
|
|
||||||
int64_t internal_getBytes(ConflictSet::Impl *impl) { return impl->getBytes(); }
|
int64_t internal_getBytes(ConflictSet::Impl *impl) { return impl->getBytes(); }
|
||||||
|
|
||||||
|
void internal_getMetricsV1(ConflictSet::Impl *impl,
|
||||||
|
ConflictSet::MetricsV1 **metrics, int *count) {
|
||||||
|
impl->getMetricsV1(metrics, count);
|
||||||
|
}
|
||||||
|
|
||||||
|
double internal_getMetricValue(const ConflictSet::MetricsV1 *metric) {
|
||||||
|
return ((Metric *)metric->p)->value.load(std::memory_order_relaxed);
|
||||||
|
}
|
||||||
|
|
||||||
// ==================== END IMPLEMENTATION ====================
|
// ==================== END IMPLEMENTATION ====================
|
||||||
|
|
||||||
// GCOVR_EXCL_START
|
// GCOVR_EXCL_START
|
||||||
@@ -3388,6 +3586,14 @@ void ConflictSet::setOldestVersion(int64_t oldestVersion) {
|
|||||||
|
|
||||||
int64_t ConflictSet::getBytes() const { return internal_getBytes(impl); }
|
int64_t ConflictSet::getBytes() const { return internal_getBytes(impl); }
|
||||||
|
|
||||||
|
void ConflictSet::getMetricsV1(MetricsV1 **metrics, int *count) const {
|
||||||
|
return internal_getMetricsV1(impl, metrics, count);
|
||||||
|
}
|
||||||
|
|
||||||
|
double ConflictSet::MetricsV1::getValue() const {
|
||||||
|
return internal_getMetricValue(this);
|
||||||
|
}
|
||||||
|
|
||||||
ConflictSet::ConflictSet(int64_t oldestVersion)
|
ConflictSet::ConflictSet(int64_t oldestVersion)
|
||||||
: impl(internal_create(oldestVersion)) {}
|
: impl(internal_create(oldestVersion)) {}
|
||||||
|
|
||||||
@@ -3443,6 +3649,7 @@ static_assert(std::is_standard_layout_v<ConflictSet::Result>);
|
|||||||
static_assert(std::is_standard_layout_v<ConflictSet::Key>);
|
static_assert(std::is_standard_layout_v<ConflictSet::Key>);
|
||||||
static_assert(std::is_standard_layout_v<ConflictSet::ReadRange>);
|
static_assert(std::is_standard_layout_v<ConflictSet::ReadRange>);
|
||||||
static_assert(std::is_standard_layout_v<ConflictSet::WriteRange>);
|
static_assert(std::is_standard_layout_v<ConflictSet::WriteRange>);
|
||||||
|
static_assert(std::is_standard_layout_v<ConflictSet::MetricsV1>);
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
|
42
Internal.h
42
Internal.h
@@ -702,17 +702,36 @@ template <class ConflictSetImpl> struct TestDriver {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
oldestVersion +=
|
||||||
|
arbitrary.bounded(10) ? arbitrary.bounded(10) : arbitrary.next();
|
||||||
|
oldestVersion = std::min(oldestVersion, writeVersion);
|
||||||
|
|
||||||
|
#ifdef THREAD_TEST
|
||||||
|
std::latch ready{1};
|
||||||
|
std::thread thread2{[&]() {
|
||||||
|
ready.count_down();
|
||||||
|
ConflictSet::MetricsV1 *m;
|
||||||
|
int count;
|
||||||
|
cs.getMetricsV1(&m, &count);
|
||||||
|
for (int i = 0; i < count; ++i) {
|
||||||
|
m[i].getValue();
|
||||||
|
}
|
||||||
|
}};
|
||||||
|
ready.wait();
|
||||||
|
#endif
|
||||||
|
|
||||||
CALLGRIND_START_INSTRUMENTATION;
|
CALLGRIND_START_INSTRUMENTATION;
|
||||||
cs.addWrites(writes, numPointWrites + numRangeWrites, v);
|
cs.addWrites(writes, numPointWrites + numRangeWrites, v);
|
||||||
CALLGRIND_STOP_INSTRUMENTATION;
|
CALLGRIND_STOP_INSTRUMENTATION;
|
||||||
|
|
||||||
refImpl.addWrites(writes, numPointWrites + numRangeWrites, v);
|
refImpl.addWrites(writes, numPointWrites + numRangeWrites, v);
|
||||||
|
|
||||||
oldestVersion +=
|
|
||||||
arbitrary.bounded(10) ? arbitrary.bounded(10) : arbitrary.next();
|
|
||||||
oldestVersion = std::min(oldestVersion, writeVersion);
|
|
||||||
cs.setOldestVersion(oldestVersion);
|
cs.setOldestVersion(oldestVersion);
|
||||||
refImpl.setOldestVersion(oldestVersion);
|
refImpl.setOldestVersion(oldestVersion);
|
||||||
|
|
||||||
|
#ifdef THREAD_TEST
|
||||||
|
thread2.join();
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
int numPointReads = arbitrary.bounded(100);
|
int numPointReads = arbitrary.bounded(100);
|
||||||
@@ -785,7 +804,15 @@ template <class ConflictSetImpl> struct TestDriver {
|
|||||||
std::latch ready{1};
|
std::latch ready{1};
|
||||||
std::thread thread2{[&]() {
|
std::thread thread2{[&]() {
|
||||||
ready.count_down();
|
ready.count_down();
|
||||||
|
// Call all const methods
|
||||||
cs.check(reads, results3, numPointReads + numRangeReads);
|
cs.check(reads, results3, numPointReads + numRangeReads);
|
||||||
|
cs.getBytes();
|
||||||
|
ConflictSet::MetricsV1 *m;
|
||||||
|
int count;
|
||||||
|
cs.getMetricsV1(&m, &count);
|
||||||
|
for (int i = 0; i < count; ++i) {
|
||||||
|
m[i].getValue();
|
||||||
|
}
|
||||||
}};
|
}};
|
||||||
ready.wait();
|
ready.wait();
|
||||||
#endif
|
#endif
|
||||||
@@ -794,6 +821,15 @@ template <class ConflictSetImpl> struct TestDriver {
|
|||||||
cs.check(reads, results1, numPointReads + numRangeReads);
|
cs.check(reads, results1, numPointReads + numRangeReads);
|
||||||
CALLGRIND_STOP_INSTRUMENTATION;
|
CALLGRIND_STOP_INSTRUMENTATION;
|
||||||
|
|
||||||
|
// Call remaining const methods
|
||||||
|
cs.getBytes();
|
||||||
|
ConflictSet::MetricsV1 *m;
|
||||||
|
int count;
|
||||||
|
cs.getMetricsV1(&m, &count);
|
||||||
|
for (int i = 0; i < count; ++i) {
|
||||||
|
m[i].getValue();
|
||||||
|
}
|
||||||
|
|
||||||
refImpl.check(reads, results2, numPointReads + numRangeReads);
|
refImpl.check(reads, results2, numPointReads + numRangeReads);
|
||||||
|
|
||||||
auto compareResults = [reads](ConflictSet::Result *results1,
|
auto compareResults = [reads](ConflictSet::Result *results1,
|
||||||
|
13
Jenkinsfile
vendored
13
Jenkinsfile
vendored
@@ -48,6 +48,17 @@ pipeline {
|
|||||||
recordIssues(tools: [clang()])
|
recordIssues(tools: [clang()])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
stage('Debug') {
|
||||||
|
agent {
|
||||||
|
dockerfile {
|
||||||
|
args '-v /home/jenkins/ccache:/ccache'
|
||||||
|
reuseNode true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
steps {
|
||||||
|
CleanBuildAndTest("-DCMAKE_BUILD_TYPE=Debug")
|
||||||
|
}
|
||||||
|
}
|
||||||
stage('SIMD fallback') {
|
stage('SIMD fallback') {
|
||||||
agent {
|
agent {
|
||||||
dockerfile {
|
dockerfile {
|
||||||
@@ -106,7 +117,7 @@ pipeline {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
steps {
|
steps {
|
||||||
CleanBuildAndTest("-DCMAKE_C_COMPILER=gcc -DCMAKE_CXX_COMPILER=g++ -DCMAKE_C_FLAGS=--coverage -DCMAKE_CXX_FLAGS=--coverage -DCMAKE_BUILD_TYPE=Debug")
|
CleanBuildAndTest("-DCMAKE_C_COMPILER=gcc -DCMAKE_CXX_COMPILER=g++ -DCMAKE_C_FLAGS=--coverage -DCMAKE_CXX_FLAGS=--coverage -DCMAKE_BUILD_TYPE=Debug -DDISABLE_TSAN=ON")
|
||||||
sh '''
|
sh '''
|
||||||
gcovr -f ConflictSet.cpp --cobertura > build/coverage.xml
|
gcovr -f ConflictSet.cpp --cobertura > build/coverage.xml
|
||||||
'''
|
'''
|
||||||
|
@@ -129,6 +129,16 @@ int main(int argc, const char **argv) {
|
|||||||
close(fd);
|
close(fd);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ConflictSet::MetricsV1 *metrics;
|
||||||
|
int metricsCount;
|
||||||
|
cs.getMetricsV1(&metrics, &metricsCount);
|
||||||
|
for (int i = 0; i < metricsCount; ++i) {
|
||||||
|
printf("# HELP %s %s\n", metrics[i].name, metrics[i].help);
|
||||||
|
printf("# TYPE %s %s\n", metrics[i].name,
|
||||||
|
metrics[i].type == metrics[i].Counter ? "counter" : "gauge");
|
||||||
|
printf("%s %g\n", metrics[i].name, metrics[i].getValue());
|
||||||
|
}
|
||||||
|
|
||||||
printf("Check: %g seconds, %g MB/s, Add: %g seconds, %g MB/s, Gc ratio: "
|
printf("Check: %g seconds, %g MB/s, Add: %g seconds, %g MB/s, Gc ratio: "
|
||||||
"%g%%, Peak idle memory: %g\n",
|
"%g%%, Peak idle memory: %g\n",
|
||||||
checkTime, checkBytes / checkTime * 1e-6, addTime,
|
checkTime, checkBytes / checkTime * 1e-6, addTime,
|
||||||
|
20
SkipList.cpp
20
SkipList.cpp
@@ -42,7 +42,7 @@ std::span<const uint8_t> copyToArena(Arena &arena,
|
|||||||
}
|
}
|
||||||
|
|
||||||
using Version = int64_t;
|
using Version = int64_t;
|
||||||
#define force_inline __attribute__((always_inline))
|
#define force_inline inline __attribute__((always_inline))
|
||||||
using StringRef = std::span<const uint8_t>;
|
using StringRef = std::span<const uint8_t>;
|
||||||
|
|
||||||
struct KeyRangeRef {
|
struct KeyRangeRef {
|
||||||
@@ -861,6 +861,16 @@ void internal_destroy(ConflictSet::Impl *impl) {
|
|||||||
|
|
||||||
int64_t internal_getBytes(ConflictSet::Impl *impl) { return impl->totalBytes; }
|
int64_t internal_getBytes(ConflictSet::Impl *impl) { return impl->totalBytes; }
|
||||||
|
|
||||||
|
void internal_getMetricsV1(ConflictSet::Impl *impl,
|
||||||
|
ConflictSet::MetricsV1 **metrics, int *count) {
|
||||||
|
*metrics = nullptr;
|
||||||
|
*count = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
double internal_getMetricValue(const ConflictSet::MetricsV1 *metric) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
void ConflictSet::check(const ReadRange *reads, Result *results,
|
void ConflictSet::check(const ReadRange *reads, Result *results,
|
||||||
int count) const {
|
int count) const {
|
||||||
internal_check(impl, reads, results, count);
|
internal_check(impl, reads, results, count);
|
||||||
@@ -877,6 +887,14 @@ void ConflictSet::setOldestVersion(int64_t oldestVersion) {
|
|||||||
|
|
||||||
int64_t ConflictSet::getBytes() const { return internal_getBytes(impl); }
|
int64_t ConflictSet::getBytes() const { return internal_getBytes(impl); }
|
||||||
|
|
||||||
|
void ConflictSet::getMetricsV1(MetricsV1 **metrics, int *count) const {
|
||||||
|
return internal_getMetricsV1(impl, metrics, count);
|
||||||
|
}
|
||||||
|
|
||||||
|
double ConflictSet::MetricsV1::getValue() const {
|
||||||
|
return internal_getMetricValue(this);
|
||||||
|
}
|
||||||
|
|
||||||
ConflictSet::ConflictSet(int64_t oldestVersion)
|
ConflictSet::ConflictSet(int64_t oldestVersion)
|
||||||
: impl(internal_create(oldestVersion)) {}
|
: impl(internal_create(oldestVersion)) {}
|
||||||
|
|
||||||
|
@@ -15,5 +15,18 @@ int main(int argc, char **argv) {
|
|||||||
if (!driver.ok) {
|
if (!driver.ok) {
|
||||||
abort();
|
abort();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ConflictSet::MetricsV1 *metrics;
|
||||||
|
int metricsCount;
|
||||||
|
driver.cs.getMetricsV1(&metrics, &metricsCount);
|
||||||
|
printf("#################### METRICS FOR %s ####################\n",
|
||||||
|
argv[i]);
|
||||||
|
for (int i = 0; i < metricsCount; ++i) {
|
||||||
|
printf("# HELP %s %s\n", metrics[i].name, metrics[i].help);
|
||||||
|
printf("# TYPE %s %s\n", metrics[i].name,
|
||||||
|
metrics[i].type == metrics[i].Counter ? "counter" : "gauge");
|
||||||
|
printf("%s %g\n", metrics[i].name, metrics[i].getValue());
|
||||||
|
}
|
||||||
|
puts("");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,3 +1,5 @@
|
|||||||
|
__aarch64_cas8_relax
|
||||||
|
__getauxval@GLIBC_2.17
|
||||||
__stack_chk_fail@GLIBC_2.17
|
__stack_chk_fail@GLIBC_2.17
|
||||||
__stack_chk_guard@GLIBC_2.17
|
__stack_chk_guard@GLIBC_2.17
|
||||||
abort@GLIBC_2.17
|
abort@GLIBC_2.17
|
||||||
|
@@ -13,5 +13,7 @@ __ZN8weaselab11ConflictSetC2Ex
|
|||||||
__ZN8weaselab11ConflictSetD1Ev
|
__ZN8weaselab11ConflictSetD1Ev
|
||||||
__ZN8weaselab11ConflictSetD2Ev
|
__ZN8weaselab11ConflictSetD2Ev
|
||||||
__ZN8weaselab11ConflictSetaSEOS0_
|
__ZN8weaselab11ConflictSetaSEOS0_
|
||||||
|
__ZNK8weaselab11ConflictSet12getMetricsV1EPPNS0_9MetricsV1EPi
|
||||||
__ZNK8weaselab11ConflictSet5checkEPKNS0_9ReadRangeEPNS0_6ResultEi
|
__ZNK8weaselab11ConflictSet5checkEPKNS0_9ReadRangeEPNS0_6ResultEi
|
||||||
__ZNK8weaselab11ConflictSet8getBytesEv
|
__ZNK8weaselab11ConflictSet8getBytesEv
|
||||||
|
__ZNK8weaselab11ConflictSet9MetricsV18getValueEv
|
@@ -1,6 +1,7 @@
|
|||||||
#include "ConflictSet.h"
|
#include "ConflictSet.h"
|
||||||
|
|
||||||
#include <cassert>
|
#include <cassert>
|
||||||
|
#include <cstdio>
|
||||||
|
|
||||||
using namespace weaselab;
|
using namespace weaselab;
|
||||||
|
|
||||||
@@ -21,4 +22,14 @@ int main(void) {
|
|||||||
assert(result == ConflictSet::Conflict);
|
assert(result == ConflictSet::Conflict);
|
||||||
int64_t bytes = cs.getBytes();
|
int64_t bytes = cs.getBytes();
|
||||||
assert(bytes > 0);
|
assert(bytes > 0);
|
||||||
|
|
||||||
|
ConflictSet::MetricsV1 *metrics;
|
||||||
|
int metricsCount;
|
||||||
|
cs.getMetricsV1(&metrics, &metricsCount);
|
||||||
|
for (int i = 0; i < metricsCount; ++i) {
|
||||||
|
printf("# HELP %s %s\n", metrics[i].name, metrics[i].help);
|
||||||
|
printf("# TYPE %s %s\n", metrics[i].name,
|
||||||
|
metrics[i].type == metrics[i].Counter ? "counter" : "gauge");
|
||||||
|
printf("%s %g\n", metrics[i].name, metrics[i].getValue());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -88,6 +88,35 @@ struct __attribute__((__visibility__("default"))) ConflictSet {
|
|||||||
/** Returns the total bytes in use by this ConflictSet */
|
/** Returns the total bytes in use by this ConflictSet */
|
||||||
int64_t getBytes() const;
|
int64_t getBytes() const;
|
||||||
|
|
||||||
|
/** Experimental! */
|
||||||
|
struct MetricsV1 {
|
||||||
|
/** A null-terminated string with static lifetime. Identifies this metric.
|
||||||
|
* Matches the regex [a-zA-Z_:][a-zA-Z0-9_:]*
|
||||||
|
*/
|
||||||
|
const char *name;
|
||||||
|
/** A null-terminated string with static lifetime. Describes this metric.
|
||||||
|
* May contain any sequence of UTF-8 characters, but the backslash and the
|
||||||
|
* line feed characters are escaped as \\ and \n, respectively.
|
||||||
|
*/
|
||||||
|
const char *help;
|
||||||
|
/** Counters are >= 0 and non-decreasing. Gauges are any value. */
|
||||||
|
enum Type { Counter, Gauge } type;
|
||||||
|
/** Get the most up-to-date (best effort) value for this metric.
|
||||||
|
* Thread-safe. */
|
||||||
|
double getValue() const;
|
||||||
|
/** @private */
|
||||||
|
void *p;
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Experimental! Store a pointer to an array of MetricsV1 (owned by the
|
||||||
|
* ConflictSet) to `*metrics`, and its length to `*count`. This function makes
|
||||||
|
* no guarantees about the contents of the metrics (e.g. names, help text, and
|
||||||
|
* the meaning of values). A correct implementation is free to return nonsense
|
||||||
|
* or nothing at all. Not intended to be inspected programmatically. Only
|
||||||
|
* intended to be plumbed along to e.g. Prometheus. Callers may repeatedly
|
||||||
|
* call `getValue` on the metrics they're interested in. */
|
||||||
|
void getMetricsV1(MetricsV1 **metrics, int *count) const;
|
||||||
|
|
||||||
#if __cplusplus > 199711L
|
#if __cplusplus > 199711L
|
||||||
ConflictSet(ConflictSet &&) noexcept;
|
ConflictSet(ConflictSet &&) noexcept;
|
||||||
ConflictSet &operator=(ConflictSet &&) noexcept;
|
ConflictSet &operator=(ConflictSet &&) noexcept;
|
||||||
|
@@ -6,12 +6,14 @@ ConflictSet_getBytes
|
|||||||
ConflictSet_setOldestVersion
|
ConflictSet_setOldestVersion
|
||||||
_ZN8weaselab11ConflictSet16setOldestVersionEl
|
_ZN8weaselab11ConflictSet16setOldestVersionEl
|
||||||
_ZN8weaselab11ConflictSet9addWritesEPKNS0_10WriteRangeEil
|
_ZN8weaselab11ConflictSet9addWritesEPKNS0_10WriteRangeEil
|
||||||
_ZN8weaselab11ConflictSetaSEOS0_
|
|
||||||
_ZN8weaselab11ConflictSetC1El
|
|
||||||
_ZN8weaselab11ConflictSetC1EOS0_
|
_ZN8weaselab11ConflictSetC1EOS0_
|
||||||
_ZN8weaselab11ConflictSetC2El
|
_ZN8weaselab11ConflictSetC1El
|
||||||
_ZN8weaselab11ConflictSetC2EOS0_
|
_ZN8weaselab11ConflictSetC2EOS0_
|
||||||
|
_ZN8weaselab11ConflictSetC2El
|
||||||
_ZN8weaselab11ConflictSetD1Ev
|
_ZN8weaselab11ConflictSetD1Ev
|
||||||
_ZN8weaselab11ConflictSetD2Ev
|
_ZN8weaselab11ConflictSetD2Ev
|
||||||
|
_ZN8weaselab11ConflictSetaSEOS0_
|
||||||
|
_ZNK8weaselab11ConflictSet12getMetricsV1EPPNS0_9MetricsV1EPi
|
||||||
_ZNK8weaselab11ConflictSet5checkEPKNS0_9ReadRangeEPNS0_6ResultEi
|
_ZNK8weaselab11ConflictSet5checkEPKNS0_9ReadRangeEPNS0_6ResultEi
|
||||||
_ZNK8weaselab11ConflictSet8getBytesEv
|
_ZNK8weaselab11ConflictSet8getBytesEv
|
||||||
|
_ZNK8weaselab11ConflictSet9MetricsV18getValueEv
|
@@ -8,4 +8,4 @@ free@GLIBC_2.2.5
|
|||||||
malloc@GLIBC_2.2.5
|
malloc@GLIBC_2.2.5
|
||||||
memcpy@GLIBC_2.14
|
memcpy@GLIBC_2.14
|
||||||
memmove@GLIBC_2.2.5
|
memmove@GLIBC_2.2.5
|
||||||
memset@GLIBC_2.2.5
|
memset@GLIBC_2.2.5
|
Reference in New Issue
Block a user