From 2776138c936ecc055a771069a6c91be41e38f514 Mon Sep 17 00:00:00 2001 From: "Jonathan B. Coe" Date: Sun, 5 Apr 2026 11:56:55 +0100 Subject: [PATCH 1/2] Add direct object baselines and branch-prediction-resistant jitter to protocol benchmarks --- protocol_benchmark.cc | 125 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 125 insertions(+) diff --git a/protocol_benchmark.cc b/protocol_benchmark.cc index 9ed3cd7..8496849 100644 --- a/protocol_benchmark.cc +++ b/protocol_benchmark.cc @@ -15,9 +15,27 @@ struct ALike { int count() { return 42; } }; +struct ALikeToo { + std::string_view name() const { return "ALikeToo"; } + + int count() { return 99; } +}; + // Member function call benchmarks +static void Direct_Call(benchmark::State& state) { + ALike a; + benchmark::DoNotOptimize(a); + for (auto _ : state) { + benchmark::DoNotOptimize(a.name()); + benchmark::DoNotOptimize(a.count()); + } +} + +BENCHMARK(Direct_Call); + static void Protocol_Virtual_Call(benchmark::State& state) { xyz::protocol p(std::in_place_type); + benchmark::DoNotOptimize(p); for (auto _ : state) { benchmark::DoNotOptimize(p.name()); benchmark::DoNotOptimize(p.count()); @@ -28,6 +46,7 @@ BENCHMARK(Protocol_Virtual_Call); static void Protocol_Manual_Call(benchmark::State& state) { xyz::protocol p(std::in_place_type); + benchmark::DoNotOptimize(p); for (auto _ : state) { benchmark::DoNotOptimize(p.name()); benchmark::DoNotOptimize(p.count()); @@ -37,6 +56,16 @@ static void Protocol_Manual_Call(benchmark::State& state) { BENCHMARK(Protocol_Manual_Call); // Copy construction benchmarks +static void Direct_Copy(benchmark::State& state) { + ALike a; + for (auto _ : state) { + ALike copy(a); + benchmark::DoNotOptimize(copy); + } +} + +BENCHMARK(Direct_Copy); + static void Protocol_Virtual_Copy(benchmark::State& state) { xyz::protocol p(std::in_place_type); for (auto _ : state) { @@ -58,6 +87,18 @@ static void Protocol_Manual_Copy(benchmark::State& state) { BENCHMARK(Protocol_Manual_Copy); // Move construction/assignment benchmarks +static void Direct_Move(benchmark::State& state) { + ALike a; + for (auto _ : state) { + ALike moved(std::move(a)); + benchmark::DoNotOptimize(moved); + a = std::move(moved); + benchmark::DoNotOptimize(a); + } +} + +BENCHMARK(Direct_Move); + static void Protocol_Virtual_Move(benchmark::State& state) { xyz::protocol p(std::in_place_type); for (auto _ : state) { @@ -83,6 +124,18 @@ static void Protocol_Manual_Move(benchmark::State& state) { BENCHMARK(Protocol_Manual_Move); // Swap benchmarks +static void Direct_Swap(benchmark::State& state) { + ALike a1; + ALike a2; + for (auto _ : state) { + std::swap(a1, a2); + benchmark::DoNotOptimize(a1); + benchmark::DoNotOptimize(a2); + } +} + +BENCHMARK(Direct_Swap); + static void Protocol_Virtual_Swap(benchmark::State& state) { xyz::protocol p1(std::in_place_type); xyz::protocol p2(std::in_place_type); @@ -108,6 +161,15 @@ static void Protocol_Manual_Swap(benchmark::State& state) { BENCHMARK(Protocol_Manual_Swap); // Construction and Destruction benchmarks +static void Direct_CtorDtor(benchmark::State& state) { + for (auto _ : state) { + ALike a; + benchmark::DoNotOptimize(a); + } +} + +BENCHMARK(Direct_CtorDtor); + static void Protocol_Virtual_CtorDtor(benchmark::State& state) { for (auto _ : state) { xyz::protocol p(std::in_place_type); @@ -130,6 +192,7 @@ BENCHMARK(Protocol_Manual_CtorDtor); static void ProtocolView_Virtual_Call(benchmark::State& state) { ALike alike; xyz::protocol_view view(alike); + benchmark::DoNotOptimize(view); for (auto _ : state) { benchmark::DoNotOptimize(view.name()); benchmark::DoNotOptimize(view.count()); @@ -141,6 +204,7 @@ BENCHMARK(ProtocolView_Virtual_Call); static void ProtocolView_Manual_Call(benchmark::State& state) { ALike alike; xyz::protocol_view view(alike); + benchmark::DoNotOptimize(view); for (auto _ : state) { benchmark::DoNotOptimize(view.name()); benchmark::DoNotOptimize(view.count()); @@ -152,6 +216,7 @@ BENCHMARK(ProtocolView_Manual_Call); static void RawPointer_Call(benchmark::State& state) { ALike alike; ALike* ptr = &alike; + benchmark::DoNotOptimize(ptr); for (auto _ : state) { benchmark::DoNotOptimize(ptr->name()); benchmark::DoNotOptimize(ptr->count()); @@ -160,6 +225,66 @@ static void RawPointer_Call(benchmark::State& state) { BENCHMARK(RawPointer_Call); +// Jitter benchmarks to defeat branch prediction +static void ProtocolView_Virtual_Call_Jitter(benchmark::State& state) { + ALike a1; + ALikeToo a2; + xyz::protocol_view views[2] = { + xyz::protocol_view(a1), + xyz::protocol_view(a2)}; + + benchmark::DoNotOptimize(views); + + int i = 0; + for (auto _ : state) { + auto& view = views[i & 1]; + benchmark::DoNotOptimize(view.name()); + benchmark::DoNotOptimize(view.count()); + ++i; + } +} + +BENCHMARK(ProtocolView_Virtual_Call_Jitter); + +static void ProtocolView_Manual_Call_Jitter(benchmark::State& state) { + ALike a1; + ALikeToo a2; + xyz::protocol_view views[2] = { + xyz::protocol_view(a1), + xyz::protocol_view(a2)}; + + benchmark::DoNotOptimize(views); + + int i = 0; + for (auto _ : state) { + auto& view = views[i & 1]; + benchmark::DoNotOptimize(view.name()); + benchmark::DoNotOptimize(view.count()); + ++i; + } +} + +BENCHMARK(ProtocolView_Manual_Call_Jitter); + +static void RawPointer_Call_Jitter(benchmark::State& state) { + ALike a1; + ALike a2; // Raw pointer array must be of the same type, so this is just to + // measure the array overhead + ALike* ptrs[2] = {&a1, &a2}; + + benchmark::DoNotOptimize(ptrs); + + int i = 0; + for (auto _ : state) { + auto* ptr = ptrs[i & 1]; + benchmark::DoNotOptimize(ptr->name()); + benchmark::DoNotOptimize(ptr->count()); + ++i; + } +} + +BENCHMARK(RawPointer_Call_Jitter); + } // namespace BENCHMARK_MAIN(); From ba2ec52795c78298df7c78d47042af3268cf370d Mon Sep 17 00:00:00 2001 From: "Jonathan B. Coe" Date: Sun, 5 Apr 2026 12:35:08 +0100 Subject: [PATCH 2/2] Change loop index type from int to size_t to address review comment --- protocol_benchmark.cc | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/protocol_benchmark.cc b/protocol_benchmark.cc index 8496849..e5ded73 100644 --- a/protocol_benchmark.cc +++ b/protocol_benchmark.cc @@ -235,7 +235,7 @@ static void ProtocolView_Virtual_Call_Jitter(benchmark::State& state) { benchmark::DoNotOptimize(views); - int i = 0; + size_t i = 0; for (auto _ : state) { auto& view = views[i & 1]; benchmark::DoNotOptimize(view.name()); @@ -255,7 +255,7 @@ static void ProtocolView_Manual_Call_Jitter(benchmark::State& state) { benchmark::DoNotOptimize(views); - int i = 0; + size_t i = 0; for (auto _ : state) { auto& view = views[i & 1]; benchmark::DoNotOptimize(view.name()); @@ -274,7 +274,7 @@ static void RawPointer_Call_Jitter(benchmark::State& state) { benchmark::DoNotOptimize(ptrs); - int i = 0; + size_t i = 0; for (auto _ : state) { auto* ptr = ptrs[i & 1]; benchmark::DoNotOptimize(ptr->name());