diff --git a/Bench.cpp b/Bench.cpp index 684e694..be23834 100644 --- a/Bench.cpp +++ b/Bench.cpp @@ -1,5 +1,7 @@ #include "ConflictSet.h" #include "Internal.h" +#include +#include #define ANKERL_NANOBENCH_IMPLEMENT #include "third_party/nanobench.h" @@ -15,94 +17,165 @@ std::string toKey(int n) { } constexpr int kNumKeys = 100000; +// A range read, a point read, and a point write. Range writes can erase +// keys, and we don't want to change the number of keys stored in the +// conflict set. +constexpr int kOpsPerTx = 100; -void benchPointRead() { - ankerl::nanobench::Bench bench; +constexpr int kPrefixLen = 0; - int readKey = kNumKeys / 2; - - ConflictSet cs(0); - for (int i = 0; i < kNumKeys; ++i) { - if (i != readKey) { - continue; - } - auto key = toKey(i); - ConflictSet::WriteRange w; - w.begin.p = (const uint8_t *)key.data(); - w.begin.len = key.size(); - w.end.len = 0; - w.writeVersion = 1; - cs.addWrites(&w, 1); - } - - auto key = toKey(readKey); - ConflictSet::ReadRange r; - r.begin.p = (const uint8_t *)key.data(); - r.begin.len = key.size(); - r.end.len = 0; - r.readVersion = 0; - ConflictSet::Result result; - bench.run("point read", [&]() { cs.check(&r, &result, 1); }); +std::span makeKey(Arena &arena, int index) { + auto result = std::span{new (arena) uint8_t[4], 4}; + index = __builtin_bswap32(index); + memcpy(result.data(), &index, 4); + return result; } -void benchPointerSet() { +template void benchConflictSet() { ankerl::nanobench::Bench bench; + ConflictSet_ cs{0}; - constexpr int kNumPointers = 32; - bench.batch(kNumPointers); + bench.batch(kOpsPerTx); - std::vector pointers; - for (int i = 0; i < kNumPointers; ++i) { - pointers.push_back(malloc(1)); + int64_t version = 0; + + // Populate conflict set + Arena arena; + { + std::vector writes; + writes.reserve(kNumKeys); + for (int i = 0; i < kNumKeys; ++i) { + auto key = makeKey(arena, i); + ConflictSet::WriteRange conflict; + conflict.begin.p = key.data(); + conflict.begin.len = key.size(); + conflict.end.len = 0; + conflict.writeVersion = version + 1; + writes.push_back(conflict); + } + cs.addWrites(writes.data(), writes.size()); + ++version; } - bench.run("Create and destroy Arena hashset", [&]() { - Arena arena; - auto h = hashSet(arena); - for (auto p : pointers) { - h.insert(p); + // I don't know why std::less didn't work /shrug + struct Less { + bool operator()(const std::span &lhs, + const std::span &rhs) const { + return lhs < rhs; } - }); + }; + auto points = set, Less>(arena); - bench.run("Create and destroy malloc hashset", [&]() { - std::unordered_set> h; - for (auto p : pointers) { - h.insert(p); - } - }); + // Two points for each range read, one for each point read, and one for each + // point write + while (points.size() < kOpsPerTx * 2 + 1) { + // TODO don't use rand? + points.insert(makeKey(arena, rand() % kNumKeys)); + } - bench.run("Create and destroy Arena vector", [&]() { - Arena arena; - auto h = vector(arena); - for (auto p : pointers) { - h.push_back(p); + // Make short-circuiting non-trivial + { + std::vector writes; + writes.reserve(kNumKeys); + for (int i = 0; i < kNumKeys; ++i) { + auto key = makeKey(arena, i); + if (points.find(key) != points.end()) { + continue; + } + ConflictSet::WriteRange conflict; + conflict.begin.p = key.data(); + conflict.begin.len = key.size(); + conflict.end.len = 0; + conflict.writeVersion = version + 1; + writes.push_back(conflict); } - }); + cs.addWrites(writes.data(), writes.size()); + ++version; + } { - Arena arena; - auto h = hashSet(arena); - bench.run("Find hashset", [&]() { - for (auto p : pointers) { - bench.doNotOptimizeAway(h.find(p)); + std::vector reads; + auto iter = points.begin(); + for (int i = 0; i < kOpsPerTx; ++i) { + ConflictSet::ReadRange r; + r.begin.p = iter->data(); + r.begin.len = iter->size(); + r.end.len = 0; + r.readVersion = version - 1; + reads.push_back(r); + ++iter; + } + + auto *results = new (arena) ConflictSet::Result[kOpsPerTx]; + + bench.run("radix tree (point reads)", + [&]() { cs.check(reads.data(), results, kOpsPerTx); }); + } + + { + std::vector reads; + auto iter = points.begin(); + for (int i = 0; i < kOpsPerTx; ++i) { + auto begin = *iter++; + auto end = *iter++; + ConflictSet::ReadRange r; + r.begin.p = begin.data(); + r.begin.len = begin.size(); + r.end.p = end.data(); + r.end.len = end.size(); + r.readVersion = version - 1; + reads.push_back(r); + } + + auto *results = new (arena) ConflictSet::Result[kOpsPerTx]; + + bench.run("radix tree (range reads)", + [&]() { cs.check(reads.data(), results, kOpsPerTx); }); + } + + { + std::vector writes; + auto iter = points.begin(); + for (int i = 0; i < kOpsPerTx; ++i) { + ConflictSet::WriteRange w; + w.begin.p = iter->data(); + w.begin.len = iter->size(); + w.end.len = 0; + writes.push_back(w); + ++iter; + } + + bench.run("radix tree (point writes)", [&]() { + auto v = ++version; + for (auto &w : writes) { + w.writeVersion = v; } + cs.addWrites(writes.data(), writes.size()); }); } { - bench.run("Find vector", [&]() { - for (auto p : pointers) { - bench.doNotOptimizeAway(std::find(pointers.begin(), pointers.end(), p)); - } - }); - } + std::vector writes; + auto iter = points.begin(); + for (int i = 0; i < kOpsPerTx - 1; ++i) { + auto begin = *iter++; + auto end = *iter++; + ConflictSet::WriteRange w; + w.begin.p = begin.data(); + w.begin.len = begin.size(); + w.end.p = end.data(); + w.end.len = end.size(); + writes.push_back(w); + } - for (auto p : pointers) { - free(p); + bench.run("radix tree (range writes)", [&]() { + auto v = ++version; + for (auto &w : writes) { + w.writeVersion = v; + } + cs.addWrites(writes.data(), writes.size()); + }); } } -int main(void) { - // benchPointRead(); - benchPointerSet(); -} \ No newline at end of file +int main(void) { benchConflictSet(); } \ No newline at end of file diff --git a/ConflictSet.cpp b/ConflictSet.cpp index 7a0d10d..9b375fd 100644 --- a/ConflictSet.cpp +++ b/ConflictSet.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include #include @@ -21,15 +22,6 @@ // ==================== BEGIN IMPLEMENTATION ==================== -int compare(std::span lhs, std::span rhs) { - int cl = std::min(lhs.size(), rhs.size()); - int c = cl == 0 ? 0 : memcmp(lhs.data(), rhs.data(), cl); - if (c != 0) { - return c; - } - return int(lhs.size()) - int(rhs.size()); -} - std::span strincMutate(std::span str, bool &ok) { int index; for (index = str.size() - 1; index >= 0; index--) @@ -793,7 +785,7 @@ bool checkRangeRead(Node *n, const std::span begin, } bool first = true; - for (auto *iter = left.n; iter != nullptr && compare(searchPath, end) < 0; + for (auto *iter = left.n; iter != nullptr && searchPath < end; first = false) { if (iter->entryPresent) { if (!first && iter->entry.rangeVersion > readVersion) { @@ -811,11 +803,11 @@ bool checkRangeRead(Node *n, const std::span begin, #endif bool ok = true; auto rangeEnd = strincMutate(searchPath, ok); - int c; + auto c = std::strong_ordering::equal; if (!ok) { goto iterate; } - c = compare(rangeEnd, end); + c = rangeEnd <=> end; --rangeEnd.back(); if (c == 0) { diff --git a/Internal.h b/Internal.h index 47d3476..4942b0e 100644 --- a/Internal.h +++ b/Internal.h @@ -4,6 +4,7 @@ #include #include +#include #include #include #include @@ -19,6 +20,18 @@ #define DEBUG_VERBOSE 0 +[[nodiscard]] inline auto +operator<=>(const std::span &lhs, + const std::span &rhs) noexcept { + int cl = std::min(lhs.size(), rhs.size()); + if (cl > 0) { + if (auto c = memcmp(lhs.data(), rhs.data(), cl) <=> 0; c != 0) { + return c; + } + } + return lhs.size() <=> rhs.size(); +} + // This header contains code that we want to reuse outside of ConflictSet.cpp or // want to exclude from coverage since it's only testing related. @@ -181,9 +194,9 @@ template using Vector = std::vector>; template auto vector(Arena &arena) { return Vector(ArenaAlloc(&arena)); } -template using Set = std::set, ArenaAlloc>; -template auto set(Arena &arena) { - return Set(ArenaAlloc(&arena)); +template using Set = std::set>; +template > auto set(Arena &arena) { + return Set(ArenaAlloc(&arena)); } template struct MyHash;