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
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:
207
Bench.cpp
207
Bench.cpp
@@ -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();
|
|
||||||
}
|
|
@@ -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) {
|
||||||
|
19
Internal.h
19
Internal.h
@@ -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;
|
||||||
|
Reference in New Issue
Block a user