1#include <xrpl/nodestore/Backend.h>
3#include <xrpl/basics/base_uint.h>
4#include <xrpl/nodestore/NodeObject.h>
5#include <xrpl/nodestore/Types.h>
7#include <benchmark/benchmark.h>
8#include <benchmarks/libxrpl/nodestore/NodeStoreBench.h>
23constexpr auto kPoolSizes = std::to_array<std::size_t>({1000, 10000, 100000});
24constexpr auto kThreadCounts = std::to_array<std::size_t>({1, 4, 8});
25constexpr std::size_t kBatchSize = 256;
26constexpr std::size_t kMissRatio = 5;
28constexpr std::string_view kNamePrefix =
"BM_Backend_";
29constexpr std::string_view kNameSeparator =
"/";
33 std::unique_ptr<BackendHarness> harness;
36 std::vector<uint256> missing;
37 std::vector<std::size_t> shuffle;
38 std::size_t avgPayload = 0;
46 missing = std::vector<uint256>{};
47 shuffle = std::vector<std::size_t>{};
69 std::string_view name;
70 std::function<void(SetupContext
const&)> setup;
71 std::function<void(IterateContext
const&)> iterate;
72 bool reportBytes =
false;
74 bool pinToPool =
false;
81Workload
const kInsert{
84 [](SetupContext
const& ctx) {
85 ctx.rs.present =
makePool(1, ctx.poolSize);
89 [](IterateContext
const& ctx) {
90 auto const& [rs, backend, index, poolSize] = ctx;
91 backend.store(rs.present[index % poolSize]);
102 [](SetupContext
const& ctx) {
103 ctx.rs.present =
makePool(1, ctx.poolSize);
108 [](IterateContext
const& ctx) {
109 auto const& [rs, backend, index, poolSize] = ctx;
110 std::shared_ptr<NodeObject> result;
111 backend.fetch(rs.present[index % poolSize]->getHash(), &result);
112 benchmark::DoNotOptimize(result);
118Workload
const kMissing{
120 .setup = [](SetupContext
const& ctx) { ctx.rs.missing =
makeMissingKeys(ctx.poolSize); },
122 [](IterateContext
const& ctx) {
123 auto const& [rs, backend, index, poolSize] = ctx;
124 std::shared_ptr<NodeObject> result;
125 backend.fetch(rs.missing[index % poolSize], &result);
126 benchmark::DoNotOptimize(result);
133Workload
const kMixed{
136 [](SetupContext
const& ctx) {
137 ctx.rs.present =
makePool(1, ctx.poolSize);
143 [](IterateContext
const& ctx) {
144 auto const& [rs, backend, index, poolSize] = ctx;
145 std::shared_ptr<NodeObject> result;
146 auto const pick = rs.shuffle[index % poolSize];
147 if (index % kMissRatio == 0)
149 backend.fetch(rs.missing[pick], &result);
153 backend.fetch(rs.present[pick]->getHash(), &result);
155 benchmark::DoNotOptimize(result);
167 [](SetupContext
const& ctx) {
168 ctx.rs.present =
makePool(1, ctx.poolSize);
169 ctx.rs.recent =
makePool(1, ctx.poolSize, ctx.poolSize);
174 [](IterateContext
const& ctx) {
175 auto const& [rs, backend, index, poolSize] = ctx;
176 auto const slot = index % poolSize;
177 auto const pick = rs.shuffle[slot];
179 std::shared_ptr<NodeObject> historical;
180 backend.fetch(rs.present[pick]->getHash(), &historical);
181 benchmark::DoNotOptimize(historical);
183 std::shared_ptr<NodeObject> recent;
184 backend.fetch(rs.recent[pick]->getHash(), &recent);
185 benchmark::DoNotOptimize(recent);
187 backend.store(rs.recent[slot]);
194makeRunner(Workload w, std::string cfg, std::shared_ptr<RunState> rs)
196 return [w = std::move(w), cfg = std::move(cfg), rs = std::move(rs)](benchmark::State& state) {
197 auto const poolSize =
static_cast<std::size_t
>(state.range(0));
198 if (state.thread_index() == 0)
202 SetupContext{.rs = *rs, .backend = *rs->harness->backend, .poolSize = poolSize});
205 std::size_t index = state.thread_index();
211 .backend = *rs->harness->backend,
213 .poolSize = poolSize});
214 index += state.threads();
218 benchmark::ClobberMemory();
220 state.SetItemsProcessed(state.iterations());
222 state.SetBytesProcessed(
static_cast<std::int64_t
>(state.iterations() * rs->avgPayload));
224 if (state.thread_index() == 0)
234 std::string
const cfg = bc.config;
235 std::string name{kNamePrefix};
237 name += kNameSeparator;
243 benchmark::RegisterBenchmark(name, makeRunner(w, cfg, rs))
244 ->RangeMultiplier(10)
245 ->Range(kPoolSizes.front(), kPoolSizes.back())
254 for (
auto const poolSize : kPoolSizes)
256 for (
auto const threads : kThreadCounts)
258 if (poolSize % threads != 0)
262 benchmark::RegisterBenchmark(name, makeRunner(w, cfg, rs))
264 ->Iterations(poolSize / threads)
265 ->Threads(
static_cast<int>(threads))
279 std::string
const cfg = bc.config;
280 std::string name{kNamePrefix};
281 name +=
"StoreBatch";
282 name += kNameSeparator;
284 for (
auto const poolSize : kPoolSizes)
286 auto const numBatches = poolSize / kBatchSize;
291 benchmark::RegisterBenchmark(
293 [rs, cfg](benchmark::State& state) {
294 auto const poolSize =
static_cast<std::size_t
>(state.range(0));
296 rs->present =
makePool(1, poolSize);
301 state.SkipWithError(
"pool smaller than one batch");
305 std::size_t index = 0;
308 rs->harness->backend->storeBatch(batches[index % batches.
size()]);
312 state.SetItemsProcessed(
static_cast<std::int64_t
>(state.iterations() * kBatchSize));
313 state.SetBytesProcessed(
314 static_cast<std::int64_t
>(state.iterations() * kBatchSize * rs->avgPayload));
318 ->Iterations(numBatches);
322[[maybe_unused]]
bool const kRegistered = [] {
323 auto const workloads = std::to_array({&kInsert, &kFetch, &kMissing, &kMixed, &kWork});
326 for (
auto const* w : workloads)
327 registerWorkload(bc, *w);
329 registerStoreBatch(bc);
std::vector< std::size_t > makeShuffle(std::size_t size, std::uint64_t seed)
std::vector< Batch > sliceFixedBatches(Batch const &pool, std::size_t batchSize)
std::vector< BackendConfig > const & backendConfigs()
void prepopulate(Backend &backend, Batch const &objects)
std::vector< uint256 > makeMissingKeys(std::size_t count)
std::vector< std::shared_ptr< NodeObject > > Batch
A batch of NodeObjects to write at once.
Batch makePool(std::uint8_t prefix, std::size_t count, std::size_t start=0)
std::size_t averagePayload(Batch const &pool)