Add benchmark
All checks were successful
Tests / Release [gcc] total: 363, passed: 363
GNU C Compiler (gcc) |Total|New|Outstanding|Fixed|Trend |:-:|:-:|:-:|:-:|:-: |0|0|0|0|:clap: Reference build: <a href="https://jenkins.weaselab.dev/job/weaselab/job/conflict-set/job/main/12//gcc">weaselab » conflict-set » main #12</a>
Tests / Coverage total: 361, passed: 361
weaselab/conflict-set/pipeline/head This commit looks good

This commit is contained in:
2024-02-11 09:16:04 -08:00
parent 0cae645c17
commit d60da4c087
3 changed files with 160 additions and 82 deletions

207
Bench.cpp
View File

@@ -1,5 +1,7 @@
#include "ConflictSet.h" #include "ConflictSet.h"
#include "Internal.h" #include "Internal.h"
#include <byteswap.h>
#include <cstdint>
#define ANKERL_NANOBENCH_IMPLEMENT #define ANKERL_NANOBENCH_IMPLEMENT
#include "third_party/nanobench.h" #include "third_party/nanobench.h"
@@ -15,94 +17,165 @@ std::string toKey(int n) {
} }
constexpr int kNumKeys = 100000; 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() { constexpr int kPrefixLen = 0;
ankerl::nanobench::Bench bench;
int readKey = kNumKeys / 2; std::span<const uint8_t> makeKey(Arena &arena, int index) {
auto result = std::span<uint8_t>{new (arena) uint8_t[4], 4};
ConflictSet cs(0); index = __builtin_bswap32(index);
for (int i = 0; i < kNumKeys; ++i) { memcpy(result.data(), &index, 4);
if (i != readKey) { return result;
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); });
} }
void benchPointerSet() { template <class ConflictSet_> void benchConflictSet() {
ankerl::nanobench::Bench bench; ankerl::nanobench::Bench bench;
ConflictSet_ cs{0};
constexpr int kNumPointers = 32; bench.batch(kOpsPerTx);
bench.batch(kNumPointers);
std::vector<void *> pointers; int64_t version = 0;
for (int i = 0; i < kNumPointers; ++i) {
pointers.push_back(malloc(1)); // Populate conflict set
Arena arena;
{
std::vector<ConflictSet::WriteRange> 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", [&]() { // I don't know why std::less didn't work /shrug
Arena arena; struct Less {
auto h = hashSet<void *>(arena); bool operator()(const std::span<const uint8_t> &lhs,
for (auto p : pointers) { const std::span<const uint8_t> &rhs) const {
h.insert(p); return lhs < rhs;
} }
}); };
auto points = set<std::span<const uint8_t>, Less>(arena);
bench.run("Create and destroy malloc hashset", [&]() { // Two points for each range read, one for each point read, and one for each
std::unordered_set<void *, MyHash<void *>> h; // point write
for (auto p : pointers) { while (points.size() < kOpsPerTx * 2 + 1) {
h.insert(p); // TODO don't use rand?
} points.insert(makeKey(arena, rand() % kNumKeys));
}); }
bench.run("Create and destroy Arena vector", [&]() { // Make short-circuiting non-trivial
Arena arena; {
auto h = vector<void *>(arena); std::vector<ConflictSet::WriteRange> writes;
for (auto p : pointers) { writes.reserve(kNumKeys);
h.push_back(p); 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; std::vector<ConflictSet::ReadRange> reads;
auto h = hashSet<void *>(arena); auto iter = points.begin();
bench.run("Find hashset", [&]() { for (int i = 0; i < kOpsPerTx; ++i) {
for (auto p : pointers) { ConflictSet::ReadRange r;
bench.doNotOptimizeAway(h.find(p)); 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<ConflictSet::ReadRange> 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<ConflictSet::WriteRange> 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", [&]() { std::vector<ConflictSet::WriteRange> writes;
for (auto p : pointers) { auto iter = points.begin();
bench.doNotOptimizeAway(std::find(pointers.begin(), pointers.end(), p)); 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) { bench.run("radix tree (range writes)", [&]() {
free(p); auto v = ++version;
for (auto &w : writes) {
w.writeVersion = v;
}
cs.addWrites(writes.data(), writes.size());
});
} }
} }
int main(void) { int main(void) { benchConflictSet<ConflictSet>(); }
// benchPointRead();
benchPointerSet();
}

View File

@@ -4,6 +4,7 @@
#include <algorithm> #include <algorithm>
#include <bit> #include <bit>
#include <cassert> #include <cassert>
#include <compare>
#include <cstdint> #include <cstdint>
#include <cstring> #include <cstring>
#include <inttypes.h> #include <inttypes.h>
@@ -21,15 +22,6 @@
// ==================== BEGIN IMPLEMENTATION ==================== // ==================== BEGIN IMPLEMENTATION ====================
int compare(std::span<const uint8_t> lhs, std::span<const uint8_t> rhs) {
int cl = std::min<int>(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<uint8_t> strincMutate(std::span<uint8_t> str, bool &ok) { std::span<uint8_t> strincMutate(std::span<uint8_t> str, bool &ok) {
int index; int index;
for (index = str.size() - 1; index >= 0; index--) for (index = str.size() - 1; index >= 0; index--)
@@ -793,7 +785,7 @@ bool checkRangeRead(Node *n, const std::span<const uint8_t> begin,
} }
bool first = true; 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) { first = false) {
if (iter->entryPresent) { if (iter->entryPresent) {
if (!first && iter->entry.rangeVersion > readVersion) { if (!first && iter->entry.rangeVersion > readVersion) {
@@ -811,11 +803,11 @@ bool checkRangeRead(Node *n, const std::span<const uint8_t> begin,
#endif #endif
bool ok = true; bool ok = true;
auto rangeEnd = strincMutate(searchPath, ok); auto rangeEnd = strincMutate(searchPath, ok);
int c; auto c = std::strong_ordering::equal;
if (!ok) { if (!ok) {
goto iterate; goto iterate;
} }
c = compare(rangeEnd, end); c = rangeEnd <=> end;
--rangeEnd.back(); --rangeEnd.back();
if (c == 0) { if (c == 0) {

View File

@@ -4,6 +4,7 @@
#include <bit> #include <bit>
#include <cassert> #include <cassert>
#include <compare>
#include <cstddef> #include <cstddef>
#include <cstdint> #include <cstdint>
#include <cstdlib> #include <cstdlib>
@@ -19,6 +20,18 @@
#define DEBUG_VERBOSE 0 #define DEBUG_VERBOSE 0
[[nodiscard]] inline auto
operator<=>(const std::span<const uint8_t> &lhs,
const std::span<const uint8_t> &rhs) noexcept {
int cl = std::min<int>(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 // 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. // want to exclude from coverage since it's only testing related.
@@ -181,9 +194,9 @@ template <class T> using Vector = std::vector<T, ArenaAlloc<T>>;
template <class T> auto vector(Arena &arena) { template <class T> auto vector(Arena &arena) {
return Vector<T>(ArenaAlloc<T>(&arena)); return Vector<T>(ArenaAlloc<T>(&arena));
} }
template <class T> using Set = std::set<T, std::less<T>, ArenaAlloc<T>>; template <class T, class C> using Set = std::set<T, C, ArenaAlloc<T>>;
template <class T> auto set(Arena &arena) { template <class T, class C = std::less<T>> auto set(Arena &arena) {
return Set<T>(ArenaAlloc<T>(&arena)); return Set<T, C>(ArenaAlloc<T>(&arena));
} }
template <class T> struct MyHash; template <class T> struct MyHash;