13 Commits

Author SHA1 Message Date
3288c583e4 Make metrics thread-safe
Some checks failed
Tests / Clang total: 1533, failed: 1, passed: 1532
Tests / Clang - debug total: 1531, passed: 1531
Tests / SIMD fallback total: 1533, failed: 1, passed: 1532
Tests / Release [gcc] total: 1533, failed: 1, passed: 1532
Tests / Release [gcc,aarch64] total: 1144, failed: 2, passed: 1142
Tests / Coverage total: 1151, passed: 1151
weaselab/conflict-set/pipeline/head There was a failure building this commit
Even concurrently with calling non-const methods on the associated
ConflictSet
2024-07-12 13:22:02 -07:00
ef14003781 Try to fix symbol tests in CI
All checks were successful
Tests / Clang total: 1533, passed: 1533
Clang |Total|New|Outstanding|Fixed|Trend |:-:|:-:|:-:|:-:|:-: |0|0|0|0|:clap:
Tests / SIMD fallback total: 1533, passed: 1533
Tests / Release [gcc] total: 1533, passed: 1533
GNU C Compiler (gcc) |Total|New|Outstanding|Fixed|Trend |:-:|:-:|:-:|:-:|:-: |0|0|0|0|:clap:
Tests / Release [gcc,aarch64] total: 1144, passed: 1144
Tests / Coverage total: 1151, passed: 1151
Code Coverage #### Project Overview No changes detected, that affect the code coverage. * Line Coverage: 98.75% (1653/1674) * Branch Coverage: 64.81% (1497/2310) * Complexity Density: 0.00 * Lines of Code: 1674 #### Quality Gates Summary Output truncated.
weaselab/conflict-set/pipeline/head This commit looks good
2024-07-12 12:24:23 -07:00
3ac16bc966 Propose a metrics interface
Some checks failed
Tests / Clang total: 1533, failed: 2, passed: 1531
Tests / SIMD fallback total: 1533, failed: 2, passed: 1531
Tests / Release [gcc] total: 1533, failed: 2, passed: 1531
Tests / Release [gcc,aarch64] total: 1144, failed: 2, passed: 1142
Tests / Coverage total: 1151, passed: 1151
weaselab/conflict-set/pipeline/head There was a failure building this commit
2024-07-12 12:00:46 -07:00
1e82f7fe22 Fix compiler warnings
All checks were successful
Tests / Clang total: 1533, passed: 1533
Clang |Total|New|Outstanding|Fixed|Trend |:-:|:-:|:-:|:-:|:-: |0|0|0|0|:clap:
Tests / SIMD fallback total: 1533, passed: 1533
Tests / Release [gcc] total: 1533, passed: 1533
GNU C Compiler (gcc) |Total|New|Outstanding|Fixed|Trend |:-:|:-:|:-:|:-:|:-: |0|0|0|0|:clap:
Tests / Release [gcc,aarch64] total: 1144, passed: 1144
Tests / Coverage total: 1151, passed: 1151
Code Coverage #### Project Overview No changes detected, that affect the code coverage. * Line Coverage: 98.71% (1607/1628) * Branch Coverage: 65.61% (1473/2245) * Complexity Density: 0.00 * Lines of Code: 1628 #### Quality Gates Summary Output truncated.
weaselab/conflict-set/pipeline/head This commit looks good
2024-07-11 19:33:57 -07:00
4182d904c5 Fix an issue where gc wasn't outpacing writes
All checks were successful
Tests / Clang total: 1533, passed: 1533
Clang |Total|New|Outstanding|Fixed|Trend |:-:|:-:|:-:|:-:|:-: |0|0|0|0|:clap:
Tests / SIMD fallback total: 1533, passed: 1533
Tests / Release [gcc] total: 1533, passed: 1533
GNU C Compiler (gcc) |Total|New|Outstanding|Fixed|Trend |:-:|:-:|:-:|:-:|:-: |2|0|2|0|:zzz:
Tests / Release [gcc,aarch64] total: 1144, passed: 1144
Tests / Coverage total: 1151, passed: 1151
Code Coverage #### Project Overview No changes detected, that affect the code coverage. * Line Coverage: 98.71% (1607/1628) * Branch Coverage: 65.61% (1473/2245) * Complexity Density: 0.00 * Lines of Code: 1628 #### Quality Gates Summary Output truncated.
weaselab/conflict-set/pipeline/head This commit looks good
2024-07-11 16:40:18 -07:00
bd8ed4e7bd Bump version
All checks were successful
Tests / Clang total: 1533, passed: 1533
Clang |Total|New|Outstanding|Fixed|Trend |:-:|:-:|:-:|:-:|:-: |0|0|0|0|:clap:
Tests / SIMD fallback total: 1533, passed: 1533
Tests / Release [gcc] total: 1533, passed: 1533
GNU C Compiler (gcc) |Total|New|Outstanding|Fixed|Trend |:-:|:-:|:-:|:-:|:-: |2|0|2|0|:zzz:
Tests / Release [gcc,aarch64] total: 1144, passed: 1144
Tests / Coverage total: 1151, passed: 1151
Code Coverage #### Project Overview No changes detected, that affect the code coverage. * Line Coverage: 98.71% (1606/1627) * Branch Coverage: 65.63% (1472/2243) * Complexity Density: 0.00 * Lines of Code: 1627 #### Quality Gates Summary Output truncated.
weaselab/conflict-set/pipeline/head This commit looks good
2024-07-10 18:57:44 -07:00
60cb274a15 Update corpus
All checks were successful
Tests / Clang total: 1533, passed: 1533
Clang |Total|New|Outstanding|Fixed|Trend |:-:|:-:|:-:|:-:|:-: |0|0|0|0|:clap:
Tests / SIMD fallback total: 1533, passed: 1533
Tests / Release [gcc] total: 1533, passed: 1533
GNU C Compiler (gcc) |Total|New|Outstanding|Fixed|Trend |:-:|:-:|:-:|:-:|:-: |2|0|2|0|:zzz:
Tests / Release [gcc,aarch64] total: 1144, passed: 1144
Tests / Coverage total: 1151, passed: 1151
Code Coverage #### Project Overview No changes detected, that affect the code coverage. * Line Coverage: 98.71% (1606/1627) * Branch Coverage: 65.63% (1472/2243) * Complexity Density: 0.00 * Lines of Code: 1627 #### Quality Gates Summary Output truncated.
weaselab/conflict-set/pipeline/head This commit looks good
2024-07-10 18:49:41 -07:00
687bc9c935 Explicitly say that begin must be < end in interface 2024-07-10 17:05:08 -07:00
d50bb8bc80 Add missing header
All checks were successful
Tests / Clang total: 1337, passed: 1337
Clang |Total|New|Outstanding|Fixed|Trend |:-:|:-:|:-:|:-:|:-: |0|0|0|0|:clap:
Tests / SIMD fallback total: 1337, passed: 1337
Tests / Release [gcc] total: 1337, passed: 1337
GNU C Compiler (gcc) |Total|New|Outstanding|Fixed|Trend |:-:|:-:|:-:|:-:|:-: |2|0|2|0|:zzz:
Tests / Release [gcc,aarch64] total: 997, passed: 997
Tests / Coverage total: 1004, passed: 1004
Code Coverage #### Project Overview No changes detected, that affect the code coverage. * Line Coverage: 97.48% (1586/1627) * Branch Coverage: 63.66% (1428/2243) * Complexity Density: 0.00 * Lines of Code: 1627 #### Quality Gates Summary Output truncated.
weaselab/conflict-set/pipeline/head This commit looks good
2024-07-10 16:59:31 -07:00
f19b403f19 Remove "writes are canonical" precondition from addWrites
Some checks failed
weaselab/conflict-set/pipeline/head There was a failure building this commit
2024-07-10 16:42:53 -07:00
34cd210907 Update README benchmarks
All checks were successful
Tests / Clang total: 1337, passed: 1337
Clang |Total|New|Outstanding|Fixed|Trend |:-:|:-:|:-:|:-:|:-: |0|0|0|0|:clap:
Tests / SIMD fallback total: 1337, passed: 1337
Tests / Release [gcc] total: 1337, passed: 1337
GNU C Compiler (gcc) |Total|New|Outstanding|Fixed|Trend |:-:|:-:|:-:|:-:|:-: |0|0|0|0|:clap:
Tests / Release [gcc,aarch64] total: 997, passed: 997
Tests / Coverage total: 1004, passed: 1004
Code Coverage #### Project Overview No changes detected, that affect the code coverage. * Line Coverage: 98.71% (1606/1627) * Branch Coverage: 65.58% (1471/2243) * Complexity Density: 0.00 * Lines of Code: 1627 #### Quality Gates Summary Output truncated.
weaselab/conflict-set/pipeline/head This commit looks good
2024-07-09 16:06:44 -07:00
1a5da9e899 Bump version
All checks were successful
Tests / Clang total: 1337, passed: 1337
Clang |Total|New|Outstanding|Fixed|Trend |:-:|:-:|:-:|:-:|:-: |0|0|0|0|:clap:
Tests / SIMD fallback total: 1337, passed: 1337
Tests / Release [gcc] total: 1337, passed: 1337
GNU C Compiler (gcc) |Total|New|Outstanding|Fixed|Trend |:-:|:-:|:-:|:-:|:-: |0|0|0|0|:clap:
Tests / Release [gcc,aarch64] total: 997, passed: 997
Tests / Coverage total: 1004, passed: 1004
Code Coverage #### Project Overview No changes detected, that affect the code coverage. * Line Coverage: 98.71% (1606/1627) * Branch Coverage: 65.58% (1471/2243) * Complexity Density: 0.00 * Lines of Code: 1627 #### Quality Gates Summary Output truncated.
weaselab/conflict-set/pipeline/head This commit looks good
2024-07-08 16:12:23 -07:00
8ba9b04d8c Remove "32-bit versions" jenkins stage
This is the default now
2024-07-08 15:45:10 -07:00
726 changed files with 541 additions and 72 deletions

View File

@@ -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) {
benchConflictSet();
benchWorstCaseForRadixRangeRead();
benchMetrics();
benchCreateAndDestroy();
}

View File

@@ -1,7 +1,7 @@
cmake_minimum_required(VERSION 3.18)
project(
conflict-set
VERSION 0.0.7
VERSION 0.0.9
DESCRIPTION
"A data structure for optimistic concurrency control on ranges of bitwise-lexicographically-ordered keys."
HOMEPAGE_URL "https://git.weaselab.dev/weaselab/conflict-set"
@@ -60,6 +60,8 @@ cmake_pop_check_state()
option(USE_SIMD_FALLBACK
"Use fallback implementations of functions that use SIMD" OFF)
option(DISABLE_TSAN "Disable TSAN" OFF)
# This is encouraged according to
# https://valgrind.org/docs/manual/manual-core-adv.html#manual-core-adv.clientreq
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()
# 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)
target_compile_options(tsan_driver PRIVATE ${TEST_FLAGS} -fsanitize=thread)
target_link_options(tsan_driver PRIVATE -fsanitize=thread)

View File

@@ -546,7 +546,50 @@ static_assert(kBytesPerKey - sizeof(Node0) >= kMinNodeSurplus);
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 {
Counter *allocated_total = nullptr;
Counter *released_total = nullptr;
static_assert(sizeof(T) >= sizeof(void *));
static_assert(std::derived_from<T, Node>);
static_assert(std::is_trivial_v<T>);
@@ -580,6 +623,7 @@ template <class T> struct BoundedFreeListAllocator {
}
T *allocate(int partialKeyCapacity) {
allocated_total->add(1);
T *result = allocate_helper(partialKeyCapacity);
if constexpr (!std::is_same_v<T, Node0>) {
memset(result->children, 0, sizeof(result->children));
@@ -596,6 +640,7 @@ template <class T> struct BoundedFreeListAllocator {
}
void release(T *p) {
released_total->add(1);
if (freeListBytes >= kFreeListMaxMemory) {
removeNode(p);
return safe_free(p, sizeof(T) + p->partialKeyCapacity);
@@ -3070,8 +3115,12 @@ struct __attribute__((visibility("hidden"))) ConflictSet::Impl {
rootMaxVersion = std::max(rootMaxVersion, oldestVersion);
n = nextPhysical(n);
}
for (; fuel > 0 && n != nullptr; --fuel) {
for (; fuel > 0 && n != nullptr;) {
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,
n->entry.rangeVersion) <= oldestVersion) {
// Any transaction n would have prevented from committing is
@@ -3132,6 +3181,16 @@ struct __attribute__((visibility("hidden"))) ConflictSet::Impl {
allocators.~NodeAllocators();
new (&allocators) NodeAllocators();
allocators.node0.allocated_total = &node0_allocated_total;
allocators.node3.allocated_total = &node3_allocated_total;
allocators.node16.allocated_total = &node16_allocated_total;
allocators.node48.allocated_total = &node48_allocated_total;
allocators.node256.allocated_total = &node256_allocated_total;
allocators.node0.released_total = &node0_released_total;
allocators.node3.released_total = &node3_released_total;
allocators.node16.released_total = &node16_released_total;
allocators.node48.released_total = &node48_released_total;
allocators.node256.released_total = &node256_released_total;
removalKeyArena = Arena{};
removalKey = {};
@@ -3156,8 +3215,14 @@ struct __attribute__((visibility("hidden"))) ConflictSet::Impl {
// Intentionally not resetting totalBytes
}
explicit Impl(int64_t oldestVersion) { init(oldestVersion); }
~Impl() { destroyTree(root); }
explicit Impl(int64_t oldestVersion) {
init(oldestVersion);
initMetrics();
}
~Impl() {
destroyTree(root);
safe_free(metrics, metricsCount * sizeof(metrics[0]));
}
NodeAllocators allocators;
@@ -3173,8 +3238,68 @@ struct __attribute__((visibility("hidden"))) ConflictSet::Impl {
int64_t oldestVersionAtGcBegin;
int64_t newestVersionFullPrecision;
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(0, metricList); i < metricsCount;
++i, m = m->prev) {
metrics[i].name = m->name;
metrics[i].help = m->help;
metrics[i].p = m;
metrics[i].type = m->type;
}
std::sort(metrics, metrics + metricsCount,
[](const MetricsV1 &lhs, const MetricsV1 &rhs) -> bool {
return std::string_view(lhs.name) < std::string_view(rhs.name);
});
}
Metric *metricList = nullptr;
#define GAUGE(name, help) \
Gauge name { this, #name, help }
#define COUNTER(name, help) \
Counter name { this, #name, help }
// ==================== METRICS DEFINITIONS ====================
COUNTER(node0_allocated_total,
"Total number of nodes of type \"Node0\" that have been allocated");
COUNTER(node3_allocated_total,
"Total number of nodes of type \"Node3\" that have been allocated");
COUNTER(node16_allocated_total,
"Total number of nodes of type \"Node16\" that have been allocated");
COUNTER(node48_allocated_total,
"Total number of nodes of type \"Node48\" that have been allocated");
COUNTER(node256_allocated_total,
"Total number of nodes of type \"Node256\" that have been allocated");
COUNTER(node0_released_total,
"Total number of nodes of type \"Node0\" that have been released");
COUNTER(node3_released_total,
"Total number of nodes of type \"Node3\" that have been released");
COUNTER(node16_released_total,
"Total number of nodes of type \"Node16\" that have been released");
COUNTER(node48_released_total,
"Total number of nodes of type \"Node48\" that have been released");
COUNTER(node256_released_total,
"Total number of nodes of type \"Node256\" that have been released");
// ==================== 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) {
int index = n->parentsIndex;
n = n->parent;
@@ -3304,6 +3429,15 @@ void internal_destroy(ConflictSet::Impl *impl) {
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 ====================
// GCOVR_EXCL_START
@@ -3388,6 +3522,14 @@ void ConflictSet::setOldestVersion(int64_t oldestVersion) {
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)
: impl(internal_create(oldestVersion)) {}
@@ -3443,6 +3585,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::ReadRange>);
static_assert(std::is_standard_layout_v<ConflictSet::WriteRange>);
static_assert(std::is_standard_layout_v<ConflictSet::MetricsV1>);
namespace {

View File

@@ -664,17 +664,74 @@ template <class ConflictSetImpl> struct TestDriver {
fprintf(stderr, "Write @ %" PRId64 "\n", v);
#endif
// Test non-canonical writes
if (numPointWrites > 0) {
int overlaps = arbitrary.bounded(numPointWrites);
for (int i = 0; i < numPointWrites + numRangeWrites && overlaps > 0;
++i) {
if (writes[i].end.len == 0) {
int keyLen = prefixLen + arbitrary.bounded(kMaxKeySuffixLen);
auto *begin = new (arena) uint8_t[keyLen];
memset(begin, prefixByte, prefixLen);
arbitrary.randomBytes(begin + prefixLen, keyLen - prefixLen);
writes[i].end.len = keyLen;
writes[i].end.p = begin;
auto c =
std::span<const uint8_t>(writes[i].begin.p,
writes[i].begin.len) <=>
std::span<const uint8_t>(writes[i].end.p, writes[i].end.len);
if (c > 0) {
using std::swap;
swap(writes[i].begin, writes[i].end);
} else if (c == 0) {
// It's a point write after all, I guess
writes[i].end.len = 0;
}
--overlaps;
}
}
}
if (arbitrary.bounded(2)) {
// Shuffle writes
for (int i = numPointWrites + numRangeWrites - 1; i > 0; --i) {
int j = arbitrary.bounded(i + 1);
if (i != j) {
using std::swap;
swap(writes[i], writes[j]);
}
}
}
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;
cs.addWrites(writes, numPointWrites + numRangeWrites, v);
CALLGRIND_STOP_INSTRUMENTATION;
refImpl.addWrites(writes, numPointWrites + numRangeWrites, v);
oldestVersion +=
arbitrary.bounded(10) ? arbitrary.bounded(10) : arbitrary.next();
oldestVersion = std::min(oldestVersion, writeVersion);
cs.setOldestVersion(oldestVersion);
refImpl.setOldestVersion(oldestVersion);
#ifdef THREAD_TEST
thread2.join();
#endif
}
{
int numPointReads = arbitrary.bounded(100);
@@ -747,7 +804,15 @@ template <class ConflictSetImpl> struct TestDriver {
std::latch ready{1};
std::thread thread2{[&]() {
ready.count_down();
// Call all const methods
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();
#endif
@@ -756,6 +821,15 @@ template <class ConflictSetImpl> struct TestDriver {
cs.check(reads, results1, numPointReads + numRangeReads);
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);
auto compareResults = [reads](ConflictSet::Result *results1,

25
Jenkinsfile vendored
View File

@@ -48,6 +48,18 @@ pipeline {
recordIssues(tools: [clang()])
}
}
stage('Clang - debug') {
agent {
dockerfile {
args '-v /home/jenkins/ccache:/ccache'
reuseNode true
}
}
steps {
CleanBuildAndTest("-DCMAKE_BUILD_TYPE=Debug")
recordIssues(tools: [clang()])
}
}
stage('SIMD fallback') {
agent {
dockerfile {
@@ -59,17 +71,6 @@ pipeline {
CleanBuildAndTest("-DUSE_SIMD_FALLBACK=ON")
}
}
stage('32-bit versions') {
agent {
dockerfile {
args '-v /home/jenkins/ccache:/ccache'
reuseNode true
}
}
steps {
CleanBuildAndTest("-DUSE_32_BIT_VERSIONS=ON")
}
}
stage('Release [gcc]') {
agent {
dockerfile {
@@ -117,7 +118,7 @@ pipeline {
}
}
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 '''
gcovr -f ConflictSet.cpp --cobertura > build/coverage.xml
'''

View File

@@ -60,27 +60,27 @@ Performance counters:
| ns/op | op/s | err% | total | benchmark
|--------------------:|--------------------:|--------:|----------:|:----------
| 256.89 | 3,892,784.92 | 0.3% | 0.01 | `point reads`
| 272.90 | 3,664,395.04 | 0.2% | 0.01 | `prefix reads`
| 507.22 | 1,971,549.50 | 0.7% | 0.01 | `range reads`
| 452.66 | 2,209,181.91 | 0.5% | 0.01 | `point writes`
| 438.09 | 2,282,619.96 | 0.4% | 0.01 | `prefix writes`
| 253.33 | 3,947,420.36 | 2.5% | 0.02 | `range writes`
| 574.07 | 1,741,936.71 | 0.3% | 0.01 | `monotonic increasing point writes`
| 151,562.50 | 6,597.94 | 1.5% | 0.01 | `worst case for radix tree`
| 245.99 | 4,065,232.81 | 0.3% | 0.01 | `point reads`
| 265.93 | 3,760,430.49 | 0.2% | 0.01 | `prefix reads`
| 485.30 | 2,060,569.50 | 0.2% | 0.01 | `range reads`
| 449.60 | 2,224,195.17 | 0.4% | 0.01 | `point writes`
| 441.76 | 2,263,688.18 | 1.1% | 0.01 | `prefix writes`
| 245.42 | 4,074,647.54 | 2.4% | 0.02 | `range writes`
| 572.80 | 1,745,810.06 | 1.3% | 0.01 | `monotonic increasing point writes`
| 154,819.33 | 6,459.14 | 0.9% | 0.01 | `worst case for radix tree`
## Radix tree (this implementation)
| ns/op | op/s | err% | total | benchmark
|--------------------:|--------------------:|--------:|----------:|:----------
| 19.83 | 50,420,955.28 | 0.1% | 0.01 | `point reads`
| 55.95 | 17,872,542.40 | 0.5% | 0.01 | `prefix reads`
| 88.28 | 11,327,709.50 | 0.4% | 0.01 | `range reads`
| 29.15 | 34,309,531.64 | 0.5% | 0.01 | `point writes`
| 42.36 | 23,607,424.27 | 1.1% | 0.01 | `prefix writes`
| 50.00 | 20,000,000.00 | 0.0% | 0.01 | `range writes`
| 93.52 | 10,692,413.79 | 3.3% | 0.01 | `monotonic increasing point writes`
| 2,388,417.00 | 418.69 | 0.4% | 0.03 | `worst case for radix tree`
| 19.17 | 52,163,930.66 | 0.1% | 0.01 | `point reads`
| 23.68 | 42,224,388.21 | 0.7% | 0.01 | `prefix reads`
| 63.30 | 15,797,506.06 | 0.9% | 0.01 | `range reads`
| 29.66 | 33,720,994.74 | 0.3% | 0.01 | `point writes`
| 43.50 | 22,987,781.25 | 1.0% | 0.01 | `prefix writes`
| 50.00 | 20,000,000.00 | 0.8% | 0.01 | `range writes`
| 103.25 | 9,684,786.47 | 2.9% | 0.01 | `monotonic increasing point writes`
| 1,181,500.00 | 846.38 | 2.3% | 0.01 | `worst case for radix tree`
# "Real data" test

View File

@@ -129,6 +129,16 @@ int main(int argc, const char **argv) {
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: "
"%g%%, Peak idle memory: %g\n",
checkTime, checkBytes / checkTime * 1e-6, addTime,

View File

@@ -22,6 +22,8 @@
#include "ConflictSet.h"
#include "Internal.h"
#include <algorithm>
#include <span>
std::span<const uint8_t> keyAfter(Arena &arena, std::span<const uint8_t> key) {
@@ -40,7 +42,7 @@ std::span<const uint8_t> copyToArena(Arena &arena,
}
using Version = int64_t;
#define force_inline __attribute__((always_inline))
#define force_inline inline __attribute__((always_inline))
using StringRef = std::span<const uint8_t>;
struct KeyRangeRef {
@@ -52,6 +54,135 @@ struct KeyRangeRef {
: begin(begin), end(keyAfter(arena, begin)) {}
};
struct KeyInfo {
StringRef key;
bool begin;
bool write;
KeyInfo() = default;
KeyInfo(StringRef key, bool begin, bool write)
: key(key), begin(begin), write(write) {}
};
force_inline int extra_ordering(const KeyInfo &ki) {
return ki.begin * 2 + (ki.write ^ ki.begin);
}
// returns true if done with string
force_inline bool getCharacter(const KeyInfo &ki, int character,
int &outputCharacter) {
// normal case
if (character < ki.key.size()) {
outputCharacter = 5 + ki.key.begin()[character];
return false;
}
// termination
if (character == ki.key.size()) {
outputCharacter = 0;
return false;
}
if (character == ki.key.size() + 1) {
// end/begin+read/write relative sorting
outputCharacter = extra_ordering(ki);
return false;
}
outputCharacter = 0;
return true;
}
bool operator<(const KeyInfo &lhs, const KeyInfo &rhs) {
int i = std::min(lhs.key.size(), rhs.key.size());
int c = memcmp(lhs.key.data(), rhs.key.data(), i);
if (c != 0)
return c < 0;
// Always sort shorter keys before longer keys.
if (lhs.key.size() < rhs.key.size()) {
return true;
}
if (lhs.key.size() > rhs.key.size()) {
return false;
}
// When the keys are the same length, use the extra ordering constraint.
return extra_ordering(lhs) < extra_ordering(rhs);
}
bool operator==(const KeyInfo &lhs, const KeyInfo &rhs) {
return !(lhs < rhs || rhs < lhs);
}
void swapSort(std::vector<KeyInfo> &points, int a, int b) {
if (points[b] < points[a]) {
KeyInfo temp;
temp = points[a];
points[a] = points[b];
points[b] = temp;
}
}
struct SortTask {
int begin;
int size;
int character;
SortTask(int begin, int size, int character)
: begin(begin), size(size), character(character) {}
};
void sortPoints(std::vector<KeyInfo> &points) {
std::vector<SortTask> tasks;
std::vector<KeyInfo> newPoints;
std::vector<int> counts;
tasks.emplace_back(0, points.size(), 0);
while (tasks.size()) {
SortTask st = tasks.back();
tasks.pop_back();
if (st.size < 10) {
std::sort(points.begin() + st.begin, points.begin() + st.begin + st.size);
continue;
}
newPoints.resize(st.size);
counts.assign(256 + 5, 0);
// get counts
int c;
bool allDone = true;
for (int i = st.begin; i < st.begin + st.size; i++) {
allDone &= getCharacter(points[i], st.character, c);
counts[c]++;
}
if (allDone)
continue;
// calculate offsets from counts and build next level of tasks
int total = 0;
for (int i = 0; i < counts.size(); i++) {
int temp = counts[i];
if (temp > 1)
tasks.emplace_back(st.begin + total, temp, st.character + 1);
counts[i] = total;
total += temp;
}
// put in their places
for (int i = st.begin; i < st.begin + st.size; i++) {
getCharacter(points[i], st.character, c);
newPoints[counts[c]++] = points[i];
}
// copy back into original points array
for (int i = 0; i < st.size; i++)
points[st.begin + i] = newPoints[i];
}
}
static thread_local uint32_t g_seed = 0;
static inline int skfastrand() {
@@ -602,10 +733,40 @@ struct __attribute__((visibility("hidden"))) ConflictSet::Impl {
void addWrites(const ConflictSet::WriteRange *writes, int count,
int64_t writeVersion) {
auto points = std::vector<KeyInfo>(count * 2);
Arena arena;
for (int r = 0; r < count; r++) {
points.emplace_back(StringRef(writes[r].begin.p, writes[r].begin.len),
true, true);
points.emplace_back(
writes[r].end.len > 0
? StringRef{writes[r].end.p, size_t(writes[r].end.len)}
: keyAfter(arena, points.back().key),
false, true);
}
sortPoints(points);
int activeWriteCount = 0;
std::vector<std::pair<StringRef, StringRef>> combinedWriteConflictRanges;
for (const KeyInfo &point : points) {
if (point.write) {
if (point.begin) {
activeWriteCount++;
if (activeWriteCount == 1)
combinedWriteConflictRanges.emplace_back(point.key, StringRef());
} else /*if (point.end)*/ {
activeWriteCount--;
if (activeWriteCount == 0)
combinedWriteConflictRanges.back().second = point.key;
}
}
}
assert(writeVersion >= newestVersion);
newestVersion = writeVersion;
Arena arena;
const int stringCount = count * 2;
const int stringCount = combinedWriteConflictRanges.size() * 2;
const int stripeSize = 16;
SkipList::Finger fingers[stripeSize];
@@ -616,15 +777,13 @@ struct __attribute__((visibility("hidden"))) ConflictSet::Impl {
int ss = stringCount - (stripes - 1) * stripeSize;
for (int s = stripes - 1; s >= 0; s--) {
for (int i = 0; i * 2 < ss; ++i) {
const auto &w = writes[s * stripeSize / 2 + i];
const auto &w = combinedWriteConflictRanges[s * stripeSize / 2 + i];
#if DEBUG_VERBOSE
printf("Write begin: %s\n", printable(w.begin).c_str());
fflush(stdout);
#endif
values[i * 2] = {w.begin.p, size_t(w.begin.len)};
values[i * 2 + 1] = w.end.len > 0
? StringRef{w.end.p, size_t(w.end.len)}
: keyAfter(arena, values[i * 2]);
values[i * 2] = w.first;
values[i * 2 + 1] = w.second;
keyUpdates += 3;
}
skipList.find(values, fingers, temp, ss);
@@ -702,6 +861,16 @@ void internal_destroy(ConflictSet::Impl *impl) {
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,
int count) const {
internal_check(impl, reads, results, count);
@@ -718,6 +887,14 @@ void ConflictSet::setOldestVersion(int64_t oldestVersion) {
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)
: impl(internal_create(oldestVersion)) {}

View File

@@ -3,6 +3,8 @@ __stack_chk_guard@GLIBC_2.17
abort@GLIBC_2.17
free@GLIBC_2.17
malloc@GLIBC_2.17
memcmp@GLIBC_2.17
memcpy@GLIBC_2.17
memmove@GLIBC_2.17
memset@GLIBC_2.17
memset@GLIBC_2.17
strlen@GLIBC_2.17

View File

@@ -13,5 +13,7 @@ __ZN8weaselab11ConflictSetC2Ex
__ZN8weaselab11ConflictSetD1Ev
__ZN8weaselab11ConflictSetD2Ev
__ZN8weaselab11ConflictSetaSEOS0_
__ZNK8weaselab11ConflictSet12getMetricsV1EPPNS0_9MetricsV1EPi
__ZNK8weaselab11ConflictSet5checkEPKNS0_9ReadRangeEPNS0_6ResultEi
__ZNK8weaselab11ConflictSet8getBytesEv
__ZNK8weaselab11ConflictSet9MetricsV18getValueEv

View File

@@ -5,6 +5,8 @@ _abort
_bzero
_free
_malloc
_memcmp
_memcpy
_memmove
_strlen
dyld_stub_binder

View File

@@ -1,6 +1,7 @@
#include "ConflictSet.h"
#include <cassert>
#include <cstdio>
using namespace weaselab;
@@ -21,4 +22,14 @@ int main(void) {
assert(result == ConflictSet::Conflict);
int64_t bytes = cs.getBytes();
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());
}
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -1,4 +0,0 @@
*


Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -1,2 +0,0 @@
*

Binary file not shown.

Some files were not shown because too many files have changed in this diff Show More