Add reference implementation
This commit is contained in:
128
ConflictSet.cpp
128
ConflictSet.cpp
@@ -4,8 +4,10 @@
|
||||
#include <cassert>
|
||||
#include <compare>
|
||||
#include <cstring>
|
||||
#include <map>
|
||||
#include <random>
|
||||
#include <span>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
@@ -466,9 +468,32 @@ void lastLeqMulti(Arena &arena, Node *root, std::span<Key> keys,
|
||||
fprintf(file, "}\n");
|
||||
}
|
||||
|
||||
[[maybe_unused]] void printLogical(std::string &result, Node *node) {
|
||||
for (auto iter = extrema(node, false); iter != nullptr;) {
|
||||
auto *next = ::next(iter, true);
|
||||
std::string key;
|
||||
for (auto c : std::string_view((const char *)(iter + 1), iter->len)) {
|
||||
key += "x";
|
||||
key += "0123456789abcdef"[c / 16];
|
||||
key += "0123456789abcdef"[c % 16];
|
||||
}
|
||||
if (iter->pointVersion == iter->rangeVersion) {
|
||||
result += key + " -> " + std::to_string(iter->pointVersion) + "\n";
|
||||
} else {
|
||||
result += key + " -> " + std::to_string(iter->pointVersion) + "\n";
|
||||
if (next == nullptr ||
|
||||
(std::string_view((const char *)(next + 1), iter->len) !=
|
||||
(std::string((const char *)(iter + 1), iter->len) +
|
||||
std::string("\x00", 1)))) {
|
||||
result += key + "x00 -> " + std::to_string(iter->rangeVersion) + "\n";
|
||||
}
|
||||
}
|
||||
iter = next;
|
||||
}
|
||||
}
|
||||
|
||||
[[maybe_unused]] Key toKey(Arena &arena, int n) {
|
||||
constexpr int kMaxLength = 4;
|
||||
// TODO use arena allocation
|
||||
int i = kMaxLength;
|
||||
uint8_t *itoaBuf = new (arena) uint8_t[kMaxLength];
|
||||
memset(itoaBuf, '0', kMaxLength);
|
||||
@@ -479,6 +504,19 @@ void lastLeqMulti(Arena &arena, Node *root, std::span<Key> keys,
|
||||
return Key{itoaBuf, kMaxLength};
|
||||
}
|
||||
|
||||
[[maybe_unused]] Key toKeyAfter(Arena &arena, int n) {
|
||||
constexpr int kMaxLength = 4;
|
||||
int i = kMaxLength;
|
||||
uint8_t *itoaBuf = new (arena) uint8_t[kMaxLength + 1];
|
||||
memset(itoaBuf, '0', kMaxLength);
|
||||
itoaBuf[kMaxLength] = 0;
|
||||
do {
|
||||
itoaBuf[--i] = "0123456789abcdef"[n % 16];
|
||||
n /= 16;
|
||||
} while (n);
|
||||
return Key{itoaBuf, kMaxLength + 1};
|
||||
}
|
||||
|
||||
// Recompute maxVersion, and propagate up the tree as necessary
|
||||
// TODO interleave this? Will require careful analysis for correctness, and the
|
||||
// performance gains may not be worth it.
|
||||
@@ -588,8 +626,6 @@ bool checkInvariants(Node *node) {
|
||||
checkMaxVersion(node, success);
|
||||
checkParentPointers(node, success);
|
||||
|
||||
// TODO Compare logical contents of map with
|
||||
// reference implementation
|
||||
return success;
|
||||
}
|
||||
|
||||
@@ -823,21 +859,105 @@ namespace std {
|
||||
void __throw_length_error(const char *) { abort(); }
|
||||
} // namespace std
|
||||
|
||||
namespace {
|
||||
struct ReferenceImpl {
|
||||
explicit ReferenceImpl(int64_t oldestVersion) : oldestVersion(oldestVersion) {
|
||||
writeVersionMap[""] = oldestVersion;
|
||||
}
|
||||
void check(const ConflictSet::ReadRange *reads, ConflictSet::Result *results,
|
||||
int count) const {
|
||||
for (int i = 0; i < count; ++i) {
|
||||
if (reads[i].readVersion < oldestVersion) {
|
||||
results[i] = ConflictSet::TooOld;
|
||||
continue;
|
||||
}
|
||||
auto begin =
|
||||
std::string((const char *)reads[i].begin.p, reads[i].begin.len);
|
||||
auto end =
|
||||
reads[i].end.len == 0
|
||||
? begin + std::string("\x00", 1)
|
||||
: std::string((const char *)reads[i].end.p, reads[i].end.len);
|
||||
int64_t maxVersion = oldestVersion;
|
||||
for (auto iter = --writeVersionMap.upper_bound(begin),
|
||||
endIter = writeVersionMap.lower_bound(end);
|
||||
iter != endIter; ++iter) {
|
||||
maxVersion = std::max(maxVersion, iter->second);
|
||||
}
|
||||
results[i] = maxVersion > reads[i].readVersion ? ConflictSet::Conflict
|
||||
: ConflictSet::Commit;
|
||||
}
|
||||
}
|
||||
void addWrites(const ConflictSet::WriteRange *writes, int count) {
|
||||
for (int i = 0; i < count; ++i) {
|
||||
auto begin =
|
||||
std::string((const char *)writes[i].begin.p, writes[i].begin.len);
|
||||
auto end =
|
||||
writes[i].end.len == 0
|
||||
? begin + std::string("\x00", 1)
|
||||
: std::string((const char *)writes[i].end.p, writes[i].end.len);
|
||||
auto writeVersion = writes[i].writeVersion;
|
||||
auto prevVersion = (--writeVersionMap.upper_bound(end))->second;
|
||||
for (auto iter = writeVersionMap.lower_bound(begin),
|
||||
endIter = writeVersionMap.lower_bound(end);
|
||||
iter != endIter;) {
|
||||
iter = writeVersionMap.erase(iter);
|
||||
}
|
||||
writeVersionMap[begin] = writeVersion;
|
||||
writeVersionMap[end] = prevVersion;
|
||||
}
|
||||
}
|
||||
|
||||
void setOldestVersion(int64_t oldestVersion) {
|
||||
this->oldestVersion = oldestVersion;
|
||||
}
|
||||
|
||||
void printLogical(std::string &result) {
|
||||
for (const auto &[k, v] : writeVersionMap) {
|
||||
std::string key;
|
||||
for (auto c : k) {
|
||||
key += "x";
|
||||
key += "0123456789abcdef"[c / 16];
|
||||
key += "0123456789abcdef"[c % 16];
|
||||
}
|
||||
result += key + " -> " + std::to_string(v) + "\n";
|
||||
}
|
||||
}
|
||||
|
||||
int64_t oldestVersion;
|
||||
std::map<std::string, int64_t> writeVersionMap;
|
||||
};
|
||||
} // namespace
|
||||
|
||||
#ifdef ENABLE_TESTS
|
||||
|
||||
int main(void) {
|
||||
int64_t writeVersion = 0;
|
||||
ConflictSet::Impl cs{writeVersion};
|
||||
ReferenceImpl refImpl{writeVersion};
|
||||
Arena arena;
|
||||
constexpr int kNumKeys = 100;
|
||||
constexpr int kNumKeys = 10;
|
||||
auto *write = new (arena) ConflictSet::WriteRange[kNumKeys];
|
||||
for (int i = 0; i < kNumKeys; ++i) {
|
||||
write[i].begin = toKey(arena, i);
|
||||
write[i].end = toKeyAfter(arena, i);
|
||||
write[i].end.len = 0;
|
||||
write[i].writeVersion = ++writeVersion;
|
||||
}
|
||||
cs.addWrites(write, kNumKeys);
|
||||
refImpl.addWrites(write, kNumKeys);
|
||||
debugPrintDot(stdout, cs.root);
|
||||
bool success = checkInvariants(cs.root);
|
||||
std::string logicalMap;
|
||||
std::string referenceLogicalMap;
|
||||
printLogical(logicalMap, cs.root);
|
||||
refImpl.printLogical(referenceLogicalMap);
|
||||
if (logicalMap != referenceLogicalMap) {
|
||||
fprintf(stderr,
|
||||
"Logical map not equal to reference logical map.\n\nActual:\n"
|
||||
"%s\nExpected:\n%s\n",
|
||||
logicalMap.c_str(), referenceLogicalMap.c_str());
|
||||
success = false;
|
||||
}
|
||||
return success ? 0 : 1;
|
||||
}
|
||||
#endif
|
Reference in New Issue
Block a user