6 Commits
Author SHA1 Message Date
andrew c9d742b696 Make explicit the precondition that versions must be <= latest version
Tests / Clang total: 2843, passed: 2843
Clang |Total|New|Outstanding|Fixed|Trend |:-:|:-:|:-:|:-:|:-: |0|0|0|0|:clap:
Tests / 64 bit versions total: 2843, passed: 2843
Tests / Debug total: 2841, failed: 1, passed: 2840
Tests / SIMD fallback total: 2843, passed: 2843
Tests / Release [gcc] total: 2843, passed: 2843
Tests / Release [gcc,aarch64] total: 2119, passed: 2119
Tests / Coverage total: 2136, failed: 1, passed: 2135
weaselab/conflict-set/pipeline/head There was a failure building this commit
2024-08-24 13:43:39 -07:00
andrew 795ae7cb01 Internal.h doesn't need to include <vector> 2024-08-24 10:38:07 -07:00
andrew 849e2d3e5c Remove unused code 2024-08-22 22:31:36 -07:00
andrew 1560037680 Remove unused struct 2024-08-22 17:55:54 -07:00
andrew 764c31bbc8 Run skip list gc at 200% entry insertion rate 2024-08-22 17:26:08 -07:00
andrew ee3361952a Use better rng in skip list
low bits of lcg have low periods
2024-08-22 17:00:15 -07:00
4 changed files with 62 additions and 56 deletions
+4 -2
View File
@@ -3149,6 +3149,7 @@ struct __attribute__((visibility("hidden"))) ConflictSet::Impl {
tls.impl = this; tls.impl = this;
int64_t check_byte_accum = 0; int64_t check_byte_accum = 0;
for (int i = 0; i < count; ++i) { for (int i = 0; i < count; ++i) {
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 begin = std::span<const uint8_t>(r.begin.p, r.begin.len);
@@ -3299,6 +3300,7 @@ struct __attribute__((visibility("hidden"))) ConflictSet::Impl {
} }
void setOldestVersion(int64_t o) { void setOldestVersion(int64_t o) {
assert(o <= newestVersionFullPrecision);
if (o <= oldestVersionFullPrecision) { if (o <= oldestVersionFullPrecision) {
return; return;
} }
@@ -3758,13 +3760,13 @@ std::string getSearchPath(Node *n) {
fprintf(file, fprintf(file,
" k_%p [label=\"m=%" PRId64 " p=%" PRId64 " r=%" PRId64 " k_%p [label=\"m=%" PRId64 " p=%" PRId64 " r=%" PRId64
"\n%s\", pos=\"%d,%d!\"];\n", "\n%s\", pos=\"%d,%d!\"];\n",
(void *)n, maxVersion(n).toInt64(), (void *)n, n->parent == nullptr ? -1 : maxVersion(n).toInt64(),
n->entry.pointVersion.toInt64(), n->entry.pointVersion.toInt64(),
n->entry.rangeVersion.toInt64(), n->entry.rangeVersion.toInt64(),
getPartialKeyPrintable(n).c_str(), x, y); getPartialKeyPrintable(n).c_str(), x, y);
} else { } else {
fprintf(file, " k_%p [label=\"m=%" PRId64 "\n%s\", pos=\"%d,%d!\"];\n", fprintf(file, " k_%p [label=\"m=%" PRId64 "\n%s\", pos=\"%d,%d!\"];\n",
(void *)n, maxVersion(n).toInt64(), (void *)n, n->parent == nullptr ? -1 : maxVersion(n).toInt64(),
getPartialKeyPrintable(n).c_str(), x, y); getPartialKeyPrintable(n).c_str(), x, y);
} }
x += kSeparation; x += kSeparation;
-1
View File
@@ -20,7 +20,6 @@ using namespace weaselab;
#include <thread> #include <thread>
#include <unordered_set> #include <unordered_set>
#include <utility> #include <utility>
#include <vector>
#include <callgrind.h> #include <callgrind.h>
+40 -41
View File
@@ -25,6 +25,7 @@
#include <algorithm> #include <algorithm>
#include <span> #include <span>
#include <vector>
std::span<const uint8_t> keyAfter(Arena &arena, std::span<const uint8_t> key) { std::span<const uint8_t> keyAfter(Arena &arena, std::span<const uint8_t> key) {
auto result = auto result =
@@ -115,15 +116,6 @@ bool operator==(const KeyInfo &lhs, const KeyInfo &rhs) {
return !(lhs < rhs || rhs < lhs); 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 { struct SortTask {
int begin; int begin;
int size; int size;
@@ -183,13 +175,6 @@ void sortPoints(std::vector<KeyInfo> &points) {
} }
} }
static thread_local uint32_t g_seed = 0;
static inline int skfastrand() {
g_seed = g_seed * 1664525L + 1013904223L;
return g_seed;
}
static int compare(const StringRef &a, const StringRef &b) { static int compare(const StringRef &a, const StringRef &b) {
int c = memcmp(a.data(), b.data(), std::min(a.size(), b.size())); int c = memcmp(a.data(), b.data(), std::min(a.size(), b.size()));
if (c < 0) if (c < 0)
@@ -215,20 +200,24 @@ struct ReadConflictRange {
} }
}; };
static constexpr int MaxLevels = 26;
struct RandomLevel {
explicit RandomLevel(uint32_t seed) : seed(seed) {}
int next() {
int result = __builtin_clz(seed | (uint32_t(-1) >> (MaxLevels - 1)));
seed = seed * 1664525L + 1013904223L;
return result;
}
private:
uint32_t seed;
};
class SkipList { class SkipList {
private: private:
static constexpr int MaxLevels = 26; RandomLevel randomLevel{0};
int randomLevel() const {
uint32_t i = uint32_t(skfastrand()) >> (32 - (MaxLevels - 1));
int level = 0;
while (i & 1) {
i >>= 1;
level++;
}
assert(level < MaxLevels);
return level;
}
// Represent a node in the SkipList. The node has multiple (i.e., level) // Represent a node in the SkipList. The node has multiple (i.e., level)
// pointers to other nodes, and keeps a record of the max versions for each // pointers to other nodes, and keeps a record of the max versions for each
@@ -426,18 +415,23 @@ public:
} }
void swap(SkipList &other) { std::swap(header, other.header); } void swap(SkipList &other) { std::swap(header, other.header); }
void addConflictRanges(const Finger *fingers, int rangeCount, // Returns the change in the number of entries
Version version) { int64_t addConflictRanges(const Finger *fingers, int rangeCount,
Version version) {
int64_t result = rangeCount;
for (int r = rangeCount - 1; r >= 0; r--) { for (int r = rangeCount - 1; r >= 0; r--) {
const Finger &startF = fingers[r * 2]; const Finger &startF = fingers[r * 2];
const Finger &endF = fingers[r * 2 + 1]; const Finger &endF = fingers[r * 2 + 1];
if (endF.found() == nullptr) if (endF.found() == nullptr) {
++result;
insert(endF, endF.finger[0]->getMaxVersion(0)); insert(endF, endF.finger[0]->getMaxVersion(0));
}
remove(startF, endF); result -= remove(startF, endF);
insert(startF, version); insert(startF, version);
} }
return result;
} }
void detectConflicts(ReadConflictRange *ranges, int count, void detectConflicts(ReadConflictRange *ranges, int count,
@@ -567,9 +561,10 @@ public:
} }
private: private:
void remove(const Finger &start, const Finger &end) { // Returns the number of entries removed
int64_t remove(const Finger &start, const Finger &end) {
if (start.finger[0] == end.finger[0]) if (start.finger[0] == end.finger[0])
return; return 0;
Node *x = start.finger[0]->getNext(0); Node *x = start.finger[0]->getNext(0);
@@ -578,17 +573,20 @@ private:
if (start.finger[i] != end.finger[i]) if (start.finger[i] != end.finger[i])
start.finger[i]->setNext(i, end.finger[i]->getNext(i)); start.finger[i]->setNext(i, end.finger[i]->getNext(i));
int64_t result = 0;
while (true) { while (true) {
Node *next = x->getNext(0); Node *next = x->getNext(0);
x->destroy(); x->destroy();
++result;
if (x == end.finger[0]) if (x == end.finger[0])
break; break;
x = next; x = next;
} }
return result;
} }
void insert(const Finger &f, Version version) { void insert(const Finger &f, Version version) {
int level = randomLevel(); int level = randomLevel.next();
// std::cout << std::string((const char*)value,length) << " level: " << // std::cout << std::string((const char*)value,length) << " level: " <<
// level << std::endl; // level << std::endl;
Node *x = Node::create(f.value, level); Node *x = Node::create(f.value, level);
@@ -704,8 +702,6 @@ private:
}; };
}; };
struct SkipListConflictSet {};
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),
@@ -775,17 +771,20 @@ struct __attribute__((visibility("hidden"))) ConflictSet::Impl {
StringRef values[stripeSize]; StringRef values[stripeSize];
int64_t writeVersions[stripeSize / 2]; int64_t writeVersions[stripeSize / 2];
int ss = stringCount - (stripes - 1) * stripeSize; int ss = stringCount - (stripes - 1) * stripeSize;
int64_t entryDelta = 0;
for (int s = stripes - 1; s >= 0; s--) { for (int s = stripes - 1; s >= 0; s--) {
for (int i = 0; i * 2 < ss; ++i) { for (int i = 0; i * 2 < ss; ++i) {
const auto &w = combinedWriteConflictRanges[s * stripeSize / 2 + i]; const auto &w = combinedWriteConflictRanges[s * stripeSize / 2 + i];
values[i * 2] = w.first; values[i * 2] = w.first;
values[i * 2 + 1] = w.second; values[i * 2 + 1] = w.second;
keyUpdates += 3;
} }
skipList.find(values, fingers, temp, ss); skipList.find(values, fingers, temp, ss);
skipList.addConflictRanges(fingers, ss / 2, writeVersion); entryDelta += skipList.addConflictRanges(fingers, ss / 2, writeVersion);
ss = stripeSize; ss = stripeSize;
} }
// Run gc at least 200% the rate we're inserting entries
keyUpdates += std::max<int64_t>(entryDelta, 0) * 2;
} }
void setOldestVersion(int64_t oldestVersion) { void setOldestVersion(int64_t oldestVersion) {
@@ -795,7 +794,7 @@ struct __attribute__((visibility("hidden"))) ConflictSet::Impl {
int temp; int temp;
std::span<const uint8_t> key = removalKey; std::span<const uint8_t> key = removalKey;
skipList.find(&key, &finger, &temp, 1); skipList.find(&key, &finger, &temp, 1);
skipList.removeBefore(oldestVersion, finger, std::exchange(keyUpdates, 10)); skipList.removeBefore(oldestVersion, finger, std::exchange(keyUpdates, 0));
removalArena = Arena(); removalArena = Arena();
removalKey = copyToArena( removalKey = copyToArena(
removalArena, {finger.getValue().data(), finger.getValue().size()}); removalArena, {finger.getValue().data(), finger.getValue().size()});
@@ -804,7 +803,7 @@ struct __attribute__((visibility("hidden"))) ConflictSet::Impl {
int64_t totalBytes = 0; int64_t totalBytes = 0;
private: private:
int64_t keyUpdates = 10; int64_t keyUpdates = 0;
Arena removalArena; Arena removalArena;
std::span<const uint8_t> removalKey; std::span<const uint8_t> removalKey;
int64_t oldestVersion; int64_t oldestVersion;
+18 -12
View File
@@ -54,8 +54,9 @@ struct __attribute__((__visibility__("default"))) ConflictSet {
/** `end` having length 0 denotes that this range is the single key {begin}. /** `end` having length 0 denotes that this range is the single key {begin}.
* Otherwise this denotes the range [begin, end), and begin must be < end */ * Otherwise this denotes the range [begin, end), and begin must be < end */
Key end; Key end;
/** `readVersion` older than the the oldestVersion or the version of the /** `readVersion` older than the oldestVersion or the version of the
* latest call to `addWrites` minus two billion will result in `TooOld` */ * latest call to `addWrites` minus two billion will result in `TooOld`.
* Must be <= the version of the latest call to `addWrites` */
int64_t readVersion; int64_t readVersion;
}; };
@@ -72,11 +73,13 @@ struct __attribute__((__visibility__("default"))) ConflictSet {
/** Reads intersecting writes where readVersion < `writeVersion` will result /** Reads intersecting writes where readVersion < `writeVersion` will result
* in `Conflict` (or `TooOld`, eventually). `writeVersion` must be greater * in `Conflict` (or `TooOld`, eventually). `writeVersion` must be greater
* than or equal to all previous write versions. */ * than or equal to all previous write versions. Call `addWrites` with `count`
* zero to only advance the version. */
void addWrites(const WriteRange *writes, int count, int64_t writeVersion); void addWrites(const WriteRange *writes, int count, int64_t writeVersion);
/** Reads where readVersion < oldestVersion will result in `TooOld`. Must be /** Reads where readVersion < `oldestVersion` will result in `TooOld`. Must be
* greater than or equal to all previous oldest versions. */ * greater than or equal to all previous oldest versions. Must be <= the
* version of the latest call to `addWrites` */
void setOldestVersion(int64_t oldestVersion); void setOldestVersion(int64_t oldestVersion);
/** Reads where readVersion < oldestVersion will result in `TooOld`. There are /** Reads where readVersion < oldestVersion will result in `TooOld`. There are
@@ -170,8 +173,9 @@ typedef struct {
/** `end` having length 0 denotes that this range is the single key {begin}. /** `end` having length 0 denotes that this range is the single key {begin}.
* Otherwise this denotes the range [begin, end), and begin must be < end */ * Otherwise this denotes the range [begin, end), and begin must be < end */
ConflictSet_Key end; ConflictSet_Key end;
/** `readVersion` older than the the oldestVersion or the version of the /** `readVersion` older than the oldestVersion or the version of the
* latest call to `addWrites` minus two billion will result in `TooOld` */ * latest call to `addWrites` minus two billion will result in `TooOld`.
* Must be <= the version of the latest call to `addWrites` */
int64_t readVersion; int64_t readVersion;
} ConflictSet_ReadRange; } ConflictSet_ReadRange;
@@ -188,15 +192,17 @@ void ConflictSet_check(const ConflictSet *cs,
const ConflictSet_ReadRange *reads, const ConflictSet_ReadRange *reads,
ConflictSet_Result *results, int count); ConflictSet_Result *results, int count);
/** Reads intersecting writes where readVersion < `writeVersion` will result in /** Reads intersecting writes where readVersion < `writeVersion` will result
* `Conflict` (or `TooOld`, eventually). `writeVersion` must be greater than or * in `Conflict` (or `TooOld`, eventually). `writeVersion` must be greater
* equal to all previous write versions. */ * than or equal to all previous write versions. Call `addWrites` with `count`
* zero to only advance the version. */
void ConflictSet_addWrites(ConflictSet *cs, void ConflictSet_addWrites(ConflictSet *cs,
const ConflictSet_WriteRange *writes, int count, const ConflictSet_WriteRange *writes, int count,
int64_t writeVersion); int64_t writeVersion);
/** Reads where readVersion < oldestVersion will result in `TooOld`. Must be /** Reads where readVersion < `oldestVersion` will result in `TooOld`. Must be
* greater than or equal to all previous oldest versions. */ * greater than or equal to all previous oldest versions. Must be <= the
* version of the latest call to `addWrites` */
void ConflictSet_setOldestVersion(ConflictSet *cs, int64_t oldestVersion); void ConflictSet_setOldestVersion(ConflictSet *cs, int64_t oldestVersion);
/** Reads where readVersion < oldestVersion will result in `TooOld`. There are /** Reads where readVersion < oldestVersion will result in `TooOld`. There are