From eec6bea7ee74ee6b313d8981b9b9bc0350eacf7f Mon Sep 17 00:00:00 2001 From: Matthew Date: Thu, 23 Apr 2026 13:51:22 -0500 Subject: [PATCH] Benchmark book snapshot --- benchmarks/CMakeLists.txt | 4 +- benchmarks/bench_book_snapshot.cpp | 169 +++++++++++++++++++++++++++++ 2 files changed, 172 insertions(+), 1 deletion(-) create mode 100644 benchmarks/bench_book_snapshot.cpp diff --git a/benchmarks/CMakeLists.txt b/benchmarks/CMakeLists.txt index a17533e..ca418a4 100644 --- a/benchmarks/CMakeLists.txt +++ b/benchmarks/CMakeLists.txt @@ -6,6 +6,7 @@ add_executable(bench_matching_engine bench_matching_engine.cpp) add_executable(bench_risk_manager bench_risk_manager.cpp) add_executable(bench_execution_gateway bench_execution_gateway.cpp) add_executable(bench_feed_handler bench_feed_handler.cpp) +add_executable(bench_book_snapshot bench_book_snapshot.cpp) # Link libraries for each foreach(bench_target @@ -15,7 +16,8 @@ foreach(bench_target bench_matching_engine bench_risk_manager bench_execution_gateway - bench_feed_handler) + bench_feed_handler + bench_book_snapshot) target_link_libraries(${bench_target} velox_core benchmark::benchmark diff --git a/benchmarks/bench_book_snapshot.cpp b/benchmarks/bench_book_snapshot.cpp new file mode 100644 index 0000000..0f5b2c5 --- /dev/null +++ b/benchmarks/bench_book_snapshot.cpp @@ -0,0 +1,169 @@ +#include +#include +#include +#include "velox/book/order_book.hpp" +#include "velox/book/book_snapshot.hpp" +#include "lockfree/pool.hpp" +#include + +using namespace velox; + +class BookSnapshotBench { +public: + BookSnapshotBench() : pool(std::make_unique>()) { + book = std::make_unique("AAPL"); + // Add some bids and asks to make the snapshot non‑trivial + for (int i = 0; i < 10; ++i) { + auto bid = create_order(i, OrderSide::BUY, 10000 + i * 10, 100); + book->add_order(bid); + auto ask = create_order(1000 + i, OrderSide::SELL, 10100 + i * 10, 100); + book->add_order(ask); + } + manager = std::make_unique(5); // capture top 5 levels + manager->update(*book); + } + + Order* create_order(uint64_t id, OrderSide side, int64_t price, uint32_t qty) { + auto order = pool->acquire(); + order->order_id = id; + order->side = side; + order->price = price; + order->quantity = qty; + order->remaining_quantity = qty; + order->filled_quantity = 0; + std::strncpy(order->symbol, "AAPL", 7); + Order* raw = order.get(); + owned_orders.push_back(std::move(order)); + return raw; + } + + std::unique_ptr> pool; + std::unique_ptr book; + std::unique_ptr manager; + std::vector> owned_orders; +}; + +static void BM_Snapshot_Update(benchmark::State& state) { + BookSnapshotBench bench; + for (auto _ : state) { + bench.manager->update(*bench.book); + benchmark::DoNotOptimize(bench.manager); + } +} +BENCHMARK(BM_Snapshot_Update); + +static void BM_Snapshot_Get(benchmark::State& state) { + BookSnapshotBench bench; + bench.manager->update(*bench.book); // initial snapshot + for (auto _ : state) { + const BookSnapshot* snap = bench.manager->get_snapshot(); + benchmark::DoNotOptimize(snap); + bench.manager->release_snapshot(snap); + } +} +BENCHMARK(BM_Snapshot_Get); + +static void BM_Snapshot_GetAndTouch(benchmark::State& state) { + BookSnapshotBench bench; + bench.manager->update(*bench.book); + for (auto _ : state) { + const BookSnapshot* snap = bench.manager->get_snapshot(); + int64_t bid = snap->best_bid; + int64_t ask = snap->best_ask; + uint32_t depth = snap->bid_depth; + // touch a few price levels + if (!snap->bids.empty()) { + benchmark::DoNotOptimize(snap->bids[0].price); + } + if (!snap->asks.empty()) { + benchmark::DoNotOptimize(snap->asks[0].price); + } + benchmark::DoNotOptimize(bid); + benchmark::DoNotOptimize(ask); + benchmark::DoNotOptimize(depth); + bench.manager->release_snapshot(snap); + } +} +BENCHMARK(BM_Snapshot_GetAndTouch); + +static void BM_Snapshot_Concurrent(benchmark::State& state) { + const int num_readers = state.range(0); + BookSnapshotBench bench; + bench.manager->update(*bench.book); + + std::vector readers; + std::atomic stop{false}; + std::atomic total_ops{0}; + + // Start reader threads + for (int i = 0; i < num_readers; ++i) { + readers.emplace_back([&]() { + while (!stop.load(std::memory_order_relaxed)) { + const BookSnapshot* snap = bench.manager->get_snapshot(); + if (snap) { + // Touch a field to simulate real work + benchmark::DoNotOptimize(snap->best_bid); + bench.manager->release_snapshot(snap); + total_ops.fetch_add(1, std::memory_order_relaxed); + } + } + }); + } + + // Let the system stabilise + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + + // Run the benchmark: measure operations over a fixed time window + for (auto _ : state) { + uint64_t before = total_ops.load(std::memory_order_relaxed); + // Run for a short, fixed duration + auto start = std::chrono::high_resolution_clock::now(); + while (std::chrono::duration_cast( + std::chrono::high_resolution_clock::now() - start) < std::chrono::milliseconds(10)) { + // spin – wait for the time window to expire + } + uint64_t after = total_ops.load(std::memory_order_relaxed); + state.SetIterationTime(0.01); // 10 ms + state.SetItemsProcessed(after - before); + } + + stop = true; + for (auto& t : readers) t.join(); +} +BENCHMARK(BM_Snapshot_Concurrent)->Arg(1)->Arg(2)->Arg(4)->Arg(8)->Arg(16); + +static void BM_Snapshot_UpdateThroughput(benchmark::State& state) { + BookSnapshotBench bench; + for (auto _ : state) { + bench.manager->update(*bench.book); + benchmark::DoNotOptimize(bench.manager); + } + state.SetItemsProcessed(state.iterations()); +} +BENCHMARK(BM_Snapshot_UpdateThroughput); + +static void BM_Snapshot_GetThroughput(benchmark::State& state) { + BookSnapshotBench bench; + bench.manager->update(*bench.book); + for (auto _ : state) { + const BookSnapshot* snap = bench.manager->get_snapshot(); + benchmark::DoNotOptimize(snap); + bench.manager->release_snapshot(snap); + } + state.SetItemsProcessed(state.iterations()); +} +BENCHMARK(BM_Snapshot_GetThroughput); + +static void BM_Snapshot_UpdateLatency(benchmark::State& state) { + BookSnapshotBench bench; + for (auto _ : state) { + auto start = std::chrono::high_resolution_clock::now(); + bench.manager->update(*bench.book); + auto end = std::chrono::high_resolution_clock::now(); + auto ns = std::chrono::duration_cast(end - start).count(); + benchmark::DoNotOptimize(ns); + } +} +BENCHMARK(BM_Snapshot_UpdateLatency); + +BENCHMARK_MAIN(); \ No newline at end of file