5 Commits

Author SHA1 Message Date
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
6 changed files with 227 additions and 45 deletions

View File

@@ -1,7 +1,7 @@
cmake_minimum_required(VERSION 3.18)
project(
conflict-set
VERSION 0.0.7
VERSION 0.0.8
DESCRIPTION
"A data structure for optimistic concurrency control on ranges of bitwise-lexicographically-ordered keys."
HOMEPAGE_URL "https://git.weaselab.dev/weaselab/conflict-set"

View File

@@ -664,6 +664,44 @@ 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]);
}
}
}
CALLGRIND_START_INSTRUMENTATION;
cs.addWrites(writes, numPointWrites + numRangeWrites, v);
CALLGRIND_STOP_INSTRUMENTATION;

11
Jenkinsfile vendored
View File

@@ -59,17 +59,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 {

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

@@ -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) {
@@ -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);

View File

@@ -70,11 +70,9 @@ struct __attribute__((__visibility__("default"))) ConflictSet {
/** The result of checking reads[i] is written in results[i] */
void check(const ReadRange *reads, Result *results, int count) const;
/** `writes` must be sorted ascending, and must not have adjacent or
* overlapping ranges. Reads intersecting writes where readVersion <
* `writeVersion` will result in `Conflict` (or `TooOld`, eventually).
* `writeVersion` must be greater than or equal to all previous write
* versions. */
/** Reads intersecting writes where readVersion < `writeVersion` will result
* in `Conflict` (or `TooOld`, eventually). `writeVersion` must be greater
* than or equal to all previous write versions. */
void addWrites(const WriteRange *writes, int count, int64_t writeVersion);
/** Reads where readVersion < oldestVersion will result in `TooOld`. Must be
@@ -161,11 +159,9 @@ void ConflictSet_check(const ConflictSet *cs,
const ConflictSet_ReadRange *reads,
ConflictSet_Result *results, int count);
/** `writes` must be sorted ascending, and must not have adjacent or
* overlapping ranges. Reads intersecting writes where readVersion <
* `writeVersion` will result in `Conflict` (or `TooOld`, eventually).
* `writeVersion` must be greater than or equal to all previous write versions.
*/
/** Reads intersecting writes where readVersion < `writeVersion` will result in
* `Conflict` (or `TooOld`, eventually). `writeVersion` must be greater than or
* equal to all previous write versions. */
void ConflictSet_addWrites(ConflictSet *cs,
const ConflictSet_WriteRange *writes, int count,
int64_t writeVersion);