From b00c44e25a577efd6a08dc8da6be5f3000298f56 Mon Sep 17 00:00:00 2001 From: Yingfan Guo <115908952+sentomk@users.noreply.github.com> Date: Fri, 31 Jul 2026 03:39:32 +0000 Subject: [PATCH] ci(bench): add ScaleN scalability suite and fix filter --- .github/workflows/ci.yml | 16 +- bench/CMakeLists.txt | 6 +- bench/bench_scale.cpp | 759 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 777 insertions(+), 4 deletions(-) create mode 100644 bench/bench_scale.cpp diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0ff6ced..a914494 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -132,15 +132,19 @@ jobs: -DPTN_SKIP_COMPILER_CHECK=ON - name: Build - run: cmake --build build --target ptn_bench --parallel + run: cmake --build build --target ptn_bench ptn_bench_scale --parallel - name: Run benchmarks run: | mkdir -p bench_results build/bench/ptn_bench \ - --benchmark_filter="VariantMixed$|VariantAltHot$|VariantMixedGuarded$|VariantAltHotGuarded$|ProtocolRouter$|CommandParser$|PacketMixed$|PacketMixedHeavyBind$|LiteralMatch$|LiteralMatchRDense$" \ + --benchmark_filter="VariantMixed|VariantAltHot|VariantMixedGuarded|VariantAltHotGuarded|ProtocolRouter|CommandParser|PacketMixed|PacketMixedHeavyBind" \ --benchmark_out=bench_results/ptn_bench.json \ --benchmark_out_format=json + build/bench/ptn_bench_scale \ + --benchmark_filter="ScaleN" \ + --benchmark_out=bench_results/ptn_bench_scale.json \ + --benchmark_out_format=json - name: Upload benchmark results uses: actions/upload-artifact@v4 @@ -160,10 +164,16 @@ jobs: run: | python scripts/bench_single_report.py \ --input bench_results/ptn_bench.json \ - --include "VariantMixed$|VariantAltHot$|VariantMixedGuarded$|VariantAltHotGuarded$|ProtocolRouter$|CommandParser$|PacketMixed$|PacketMixedHeavyBind$|LiteralMatch$|LiteralMatchRDense$" \ + --include "VariantMixed|VariantAltHot|VariantMixedGuarded|VariantAltHotGuarded|ProtocolRouter|CommandParser|PacketMixed|PacketMixedHeavyBind" \ --outdir docs/assets/bench \ --prefix latest \ --title "Patternia vs Standard C++" + python scripts/bench_single_report.py \ + --input bench_results/ptn_bench_scale.json \ + --include "ScaleN" \ + --outdir docs/assets/bench \ + --prefix scale \ + --title "Variant Dispatch Scalability (4-32 alternatives)" - name: Commit and push chart update run: | diff --git a/bench/CMakeLists.txt b/bench/CMakeLists.txt index f4316db..ceb8288 100644 --- a/bench/CMakeLists.txt +++ b/bench/CMakeLists.txt @@ -30,7 +30,11 @@ add_executable(ptn_bench_variant bench_variant.cpp ) -foreach(_ptn_bench_target IN ITEMS ptn_bench ptn_bench_lit ptn_bench_evolution ptn_bench_variant) +add_executable(ptn_bench_scale + bench_scale.cpp +) + +foreach(_ptn_bench_target IN ITEMS ptn_bench ptn_bench_lit ptn_bench_evolution ptn_bench_variant ptn_bench_scale) target_link_libraries(${_ptn_bench_target} PRIVATE patternia benchmark::benchmark diff --git a/bench/bench_scale.cpp b/bench/bench_scale.cpp new file mode 100644 index 0000000..0b049f6 --- /dev/null +++ b/bench/bench_scale.cpp @@ -0,0 +1,759 @@ +// bench_scale.cpp — Variant dispatch scalability benchmarks. +// +// Measures Patternia vs std::visit vs switch(index) across 4, 8, 16, +// and 32 alternatives, using a realistic event-type hierarchy. + +#include + +#include +#include +#include +#include + +#include "ptn/patternia.hpp" + +namespace { + + // --- Realistic event types (game engine / message broker) --- + + struct EvtConnect { + uint32_t session_id; + }; + struct EvtDisconnect { + uint32_t session_id; + uint16_t reason; + }; + struct EvtMessage { + uint32_t session_id; + uint16_t type; + uint16_t length; + }; + struct EvtAck { + uint32_t session_id; + uint32_t seq; + }; + struct EvtPing { + uint64_t timestamp; + }; + struct EvtPong { + uint64_t timestamp; + }; + struct EvtError { + uint16_t code; + bool fatal; + }; + struct EvtTimeout { + uint32_t session_id; + uint32_t ms; + }; + struct EvtSubscribe { + uint32_t session_id; + uint16_t channel; + }; + struct EvtUnsubscribe { + uint32_t session_id; + uint16_t channel; + }; + struct EvtPublish { + uint16_t channel; + uint16_t length; + }; + struct EvtHeartbeat { + uint64_t timestamp; + uint32_t session_id; + }; + struct EvtResize { + uint32_t session_id; + uint16_t width; + uint16_t height; + }; + struct EvtInput { + uint16_t key; + uint16_t modifiers; + }; + struct EvtFocus { + uint32_t session_id; + bool gained; + }; + struct EvtRender { + uint64_t frame; + uint16_t pass; + }; + struct EvtAudio { + uint16_t stream; + uint16_t volume; + }; + struct EvtConfig { + uint16_t key; + int32_t value; + }; + struct EvtShutdown { + uint8_t mode; + bool force; + }; + struct EvtLog { + uint16_t level; + uint16_t facility; + }; + struct EvtMetrics { + uint32_t count; + uint32_t latency_us; + }; + struct EvtNotify { + uint32_t session_id; + uint16_t priority; + }; + struct EvtStream { + uint32_t session_id; + uint16_t stream_id; + bool open; + }; + struct EvtSync { + uint64_t timestamp; + uint32_t session_id; + }; + struct EvtQuery { + uint16_t type; + uint16_t limit; + uint32_t session_id; + }; + struct EvtResponse { + uint16_t status; + uint16_t length; + }; + struct EvtAuth { + uint32_t session_id; + uint16_t method; + bool success; + }; + struct EvtEncrypt { + uint16_t algo; + uint16_t key_id; + }; + struct EvtDecrypt { + uint16_t algo; + uint16_t key_id; + }; + struct EvtCompress { + uint16_t algo; + uint16_t level; + }; + struct EvtCache { + uint16_t op; + uint32_t size; + }; + struct EvtRetry { + uint32_t session_id; + uint8_t attempt; + }; + + using Evt4 = std:: + variant; + using Evt8 = std::variant; + using Evt16 = std::variant; + using Evt32 = std::variant; + + // ---- Patternia dispatchers ---- + + int scaleN_ptn4(const Evt4 &e) { + using namespace ptn; + using ptn::pat::is; + return match(e) + | PTN_ON(is() >> 1, + is() >> 2, + is() >> 3, + is() >> 4, + __ >> 0); + } + + int scaleN_ptn8(const Evt8 &e) { + using namespace ptn; + using ptn::pat::is; + return match(e) + | PTN_ON(is() >> 1, + is() >> 2, + is() >> 3, + is() >> 4, + is() >> 5, + is() >> 6, + is() >> 7, + is() >> 8, + __ >> 0); + } + + int scaleN_ptn16(const Evt16 &e) { + using namespace ptn; + using ptn::pat::is; + return match(e) + | PTN_ON(is() >> 1, + is() >> 2, + is() >> 3, + is() >> 4, + is() >> 5, + is() >> 6, + is() >> 7, + is() >> 8, + is() >> 9, + is() >> 10, + is() >> 11, + is() >> 12, + is() >> 13, + is() >> 14, + is() >> 15, + is() >> 16, + __ >> 0); + } + + int scaleN_ptn32(const Evt32 &e) { + using namespace ptn; + using ptn::pat::is; + return match(e) + | PTN_ON(is() >> 1, + is() >> 2, + is() >> 3, + is() >> 4, + is() >> 5, + is() >> 6, + is() >> 7, + is() >> 8, + is() >> 9, + is() >> 10, + is() >> 11, + is() >> 12, + is() >> 13, + is() >> 14, + is() >> 15, + is() >> 16, + is() >> 17, + is() >> 18, + is() >> 19, + is() >> 20, + is() >> 21, + is() >> 22, + is() >> 23, + is() >> 24, + is() >> 25, + is() >> 26, + is() >> 27, + is() >> 28, + is() >> 29, + is() >> 30, + is() >> 31, + is() >> 32, + __ >> 0); + } + + // ---- StdVisit dispatchers ---- + + int scaleN_visit4(const Evt4 &e) { + return std::visit( + [](const auto &ev) -> int { + using T = std::decay_t; + if constexpr (std::is_same_v) + return 1; + else if constexpr (std::is_same_v) + return 2; + else if constexpr (std::is_same_v) + return 3; + else if constexpr (std::is_same_v) + return 4; + else + return 0; + }, + e); + } + + int scaleN_visit8(const Evt8 &e) { + return std::visit( + [](const auto &ev) -> int { + using T = std::decay_t; + if constexpr (std::is_same_v) + return 1; + else if constexpr (std::is_same_v) + return 2; + else if constexpr (std::is_same_v) + return 3; + else if constexpr (std::is_same_v) + return 4; + else if constexpr (std::is_same_v) + return 5; + else if constexpr (std::is_same_v) + return 6; + else if constexpr (std::is_same_v) + return 7; + else if constexpr (std::is_same_v) + return 8; + else + return 0; + }, + e); + } + + int scaleN_visit16(const Evt16 &e) { + return std::visit( + [](const auto &ev) -> int { + using T = std::decay_t; + if constexpr (std::is_same_v) + return 1; + else if constexpr (std::is_same_v) + return 2; + else if constexpr (std::is_same_v) + return 3; + else if constexpr (std::is_same_v) + return 4; + else if constexpr (std::is_same_v) + return 5; + else if constexpr (std::is_same_v) + return 6; + else if constexpr (std::is_same_v) + return 7; + else if constexpr (std::is_same_v) + return 8; + else if constexpr (std::is_same_v) + return 9; + else if constexpr (std::is_same_v) + return 10; + else if constexpr (std::is_same_v) + return 11; + else if constexpr (std::is_same_v) + return 12; + else if constexpr (std::is_same_v) + return 13; + else if constexpr (std::is_same_v) + return 14; + else if constexpr (std::is_same_v) + return 15; + else if constexpr (std::is_same_v) + return 16; + else + return 0; + }, + e); + } + + int scaleN_visit32(const Evt32 &e) { + return std::visit( + [](const auto &ev) -> int { + using T = std::decay_t; + if constexpr (std::is_same_v) + return 1; + else if constexpr (std::is_same_v) + return 2; + else if constexpr (std::is_same_v) + return 3; + else if constexpr (std::is_same_v) + return 4; + else if constexpr (std::is_same_v) + return 5; + else if constexpr (std::is_same_v) + return 6; + else if constexpr (std::is_same_v) + return 7; + else if constexpr (std::is_same_v) + return 8; + else if constexpr (std::is_same_v) + return 9; + else if constexpr (std::is_same_v) + return 10; + else if constexpr (std::is_same_v) + return 11; + else if constexpr (std::is_same_v) + return 12; + else if constexpr (std::is_same_v) + return 13; + else if constexpr (std::is_same_v) + return 14; + else if constexpr (std::is_same_v) + return 15; + else if constexpr (std::is_same_v) + return 16; + else if constexpr (std::is_same_v) + return 17; + else if constexpr (std::is_same_v) + return 18; + else if constexpr (std::is_same_v) + return 19; + else if constexpr (std::is_same_v) + return 20; + else if constexpr (std::is_same_v) + return 21; + else if constexpr (std::is_same_v) + return 22; + else if constexpr (std::is_same_v) + return 23; + else if constexpr (std::is_same_v) + return 24; + else if constexpr (std::is_same_v) + return 25; + else if constexpr (std::is_same_v) + return 26; + else if constexpr (std::is_same_v) + return 27; + else if constexpr (std::is_same_v) + return 28; + else if constexpr (std::is_same_v) + return 29; + else if constexpr (std::is_same_v) + return 30; + else if constexpr (std::is_same_v) + return 31; + else if constexpr (std::is_same_v) + return 32; + else + return 0; + }, + e); + } + + // ---- SwitchIndex dispatchers ---- + + int scaleN_switch4(const Evt4 &e) { + switch (e.index()) { + case 0: + return 1; + case 1: + return 2; + case 2: + return 3; + case 3: + return 4; + default: + return 0; + } + } + + int scaleN_switch8(const Evt8 &e) { + switch (e.index()) { + case 0: + return 1; + case 1: + return 2; + case 2: + return 3; + case 3: + return 4; + case 4: + return 5; + case 5: + return 6; + case 6: + return 7; + case 7: + return 8; + default: + return 0; + } + } + + int scaleN_switch16(const Evt16 &e) { + switch (e.index()) { + case 0: + return 1; + case 1: + return 2; + case 2: + return 3; + case 3: + return 4; + case 4: + return 5; + case 5: + return 6; + case 6: + return 7; + case 7: + return 8; + case 8: + return 9; + case 9: + return 10; + case 10: + return 11; + case 11: + return 12; + case 12: + return 13; + case 13: + return 14; + case 14: + return 15; + case 15: + return 16; + default: + return 0; + } + } + + int scaleN_switch32(const Evt32 &e) { + switch (e.index()) { + case 0: + return 1; + case 1: + return 2; + case 2: + return 3; + case 3: + return 4; + case 4: + return 5; + case 5: + return 6; + case 6: + return 7; + case 7: + return 8; + case 8: + return 9; + case 9: + return 10; + case 10: + return 11; + case 11: + return 12; + case 12: + return 13; + case 13: + return 14; + case 14: + return 15; + case 15: + return 16; + case 16: + return 17; + case 17: + return 18; + case 18: + return 19; + case 19: + return 20; + case 20: + return 21; + case 21: + return 22; + case 22: + return 23; + case 23: + return 24; + case 24: + return 25; + case 25: + return 26; + case 26: + return 27; + case 27: + return 28; + case 28: + return 29; + case 29: + return 30; + case 30: + return 31; + case 31: + return 32; + default: + return 0; + } + } + + // ---- Workloads ---- + + const std::vector &wl4() { + static const std::vector data = { + Evt4{std::in_place_index<0>, EvtConnect{1}}, + Evt4{std::in_place_index<1>, EvtDisconnect{1, 100}}, + Evt4{std::in_place_index<2>, EvtMessage{1, 200, 10}}, + Evt4{std::in_place_index<3>, EvtAck{1, 0}}, + Evt4{std::in_place_index<0>, EvtConnect{2}}, + Evt4{std::in_place_index<2>, EvtMessage{2, 201, 20}}, + Evt4{std::in_place_index<1>, EvtDisconnect{2, 101}}, + Evt4{std::in_place_index<3>, EvtAck{2, 1}}, + }; + return data; + } + + const std::vector &wl8() { + static const std::vector data = { + Evt8{std::in_place_index<0>, EvtConnect{1}}, + Evt8{std::in_place_index<1>, EvtDisconnect{1, 100}}, + Evt8{std::in_place_index<2>, EvtMessage{1, 200, 10}}, + Evt8{std::in_place_index<3>, EvtAck{1, 0}}, + Evt8{std::in_place_index<4>, EvtPing{1000}}, + Evt8{std::in_place_index<5>, EvtPong{1001}}, + Evt8{std::in_place_index<6>, EvtError{500, false}}, + Evt8{std::in_place_index<7>, EvtTimeout{1, 3000}}, + Evt8{std::in_place_index<0>, EvtConnect{2}}, + Evt8{std::in_place_index<4>, EvtPing{1002}}, + Evt8{std::in_place_index<2>, EvtMessage{2, 201, 20}}, + Evt8{std::in_place_index<6>, EvtError{501, true}}, + Evt8{std::in_place_index<3>, EvtAck{2, 1}}, + Evt8{std::in_place_index<7>, EvtTimeout{2, 5000}}, + Evt8{std::in_place_index<1>, EvtDisconnect{2, 102}}, + Evt8{std::in_place_index<5>, EvtPong{1003}}, + }; + return data; + } + + const std::vector &wl16() { + static const std::vector data = { + Evt16{std::in_place_index<0>, EvtConnect{1}}, + Evt16{std::in_place_index<3>, EvtAck{1, 0}}, + Evt16{std::in_place_index<6>, EvtError{500, false}}, + Evt16{std::in_place_index<9>, EvtUnsubscribe{1, 5}}, + Evt16{std::in_place_index<12>, EvtResize{1, 1920, 1080}}, + Evt16{std::in_place_index<1>, EvtDisconnect{1, 100}}, + Evt16{std::in_place_index<4>, EvtPing{1000}}, + Evt16{std::in_place_index<7>, EvtTimeout{1, 3000}}, + Evt16{std::in_place_index<10>, EvtPublish{5, 100}}, + Evt16{std::in_place_index<13>, EvtInput{65, 0}}, + Evt16{std::in_place_index<2>, EvtMessage{1, 200, 10}}, + Evt16{std::in_place_index<5>, EvtPong{1001}}, + Evt16{std::in_place_index<8>, EvtSubscribe{1, 3}}, + Evt16{std::in_place_index<11>, EvtHeartbeat{2000, 1}}, + Evt16{std::in_place_index<14>, EvtFocus{1, true}}, + Evt16{std::in_place_index<15>, EvtRender{1, 0}}, + }; + return data; + } + + const std::vector &wl32() { + static const std::vector data = { + Evt32{std::in_place_index<0>, EvtConnect{1}}, + Evt32{std::in_place_index<15>, EvtRender{1, 0}}, + Evt32{std::in_place_index<7>, EvtTimeout{1, 3000}}, + Evt32{std::in_place_index<23>, EvtSync{1000, 1}}, + Evt32{std::in_place_index<3>, EvtAck{1, 0}}, + Evt32{std::in_place_index<19>, EvtLog{2, 10}}, + Evt32{std::in_place_index<11>, EvtHeartbeat{2000, 1}}, + Evt32{std::in_place_index<27>, EvtEncrypt{1, 100}}, + Evt32{std::in_place_index<6>, EvtError{500, false}}, + Evt32{std::in_place_index<31>, EvtRetry{1, 1}}, + Evt32{std::in_place_index<2>, EvtMessage{1, 200, 10}}, + Evt32{std::in_place_index<16>, EvtAudio{1, 80}}, + Evt32{std::in_place_index<9>, EvtUnsubscribe{1, 5}}, + Evt32{std::in_place_index<25>, EvtResponse{200, 50}}, + Evt32{std::in_place_index<1>, EvtDisconnect{1, 100}}, + Evt32{std::in_place_index<20>, EvtMetrics{100, 500}}, + Evt32{std::in_place_index<12>, EvtResize{1, 1920, 1080}}, + Evt32{std::in_place_index<28>, EvtDecrypt{1, 100}}, + Evt32{std::in_place_index<4>, EvtPing{1000}}, + Evt32{std::in_place_index<17>, EvtConfig{1, 42}}, + Evt32{std::in_place_index<8>, EvtSubscribe{1, 3}}, + Evt32{std::in_place_index<24>, EvtQuery{1, 100, 1}}, + Evt32{std::in_place_index<10>, EvtPublish{5, 100}}, + Evt32{std::in_place_index<30>, EvtCache{1, 1024}}, + Evt32{std::in_place_index<5>, EvtPong{1001}}, + Evt32{std::in_place_index<21>, EvtNotify{1, 1}}, + Evt32{std::in_place_index<13>, EvtInput{65, 0}}, + Evt32{std::in_place_index<26>, EvtAuth{1, 0, true}}, + Evt32{std::in_place_index<14>, EvtFocus{1, true}}, + Evt32{std::in_place_index<22>, EvtStream{1, 0, true}}, + Evt32{std::in_place_index<18>, EvtShutdown{0, false}}, + Evt32{std::in_place_index<29>, EvtCompress{1, 6}}, + }; + return data; + } + + template + static void + run_wl(benchmark::State &state, const std::vector &wl, F fn) { + std::size_t idx = 0; + int acc = 0; + for (auto _ : state) { + const T *item = &wl[idx]; + benchmark::DoNotOptimize(item); + acc += fn(*item); + benchmark::DoNotOptimize(acc); + benchmark::ClobberMemory(); + ++idx; + if (idx == wl.size()) { + idx = 0; + } + } + benchmark::DoNotOptimize(acc); + state.SetItemsProcessed(state.iterations()); + } + +#define DEF_BENCH(Name, Wl, Fn) \ + static void BM_##Name(benchmark::State &state) { \ + run_wl(state, Wl(), Fn); \ + } \ + BENCHMARK(BM_##Name) \ + ->Unit(benchmark::kNanosecond) \ + ->MinTime(0.5) \ + ->Repetitions(20) \ + ->ReportAggregatesOnly(true) + + // ScaleN/4 + DEF_BENCH(PatterniaPipe_ScaleN4, wl4, scaleN_ptn4); + DEF_BENCH(StdVisit_ScaleN4, wl4, scaleN_visit4); + DEF_BENCH(SwitchIndex_ScaleN4, wl4, scaleN_switch4); + + // ScaleN/8 + DEF_BENCH(PatterniaPipe_ScaleN8, wl8, scaleN_ptn8); + DEF_BENCH(StdVisit_ScaleN8, wl8, scaleN_visit8); + DEF_BENCH(SwitchIndex_ScaleN8, wl8, scaleN_switch8); + + // ScaleN/16 + DEF_BENCH(PatterniaPipe_ScaleN16, wl16, scaleN_ptn16); + DEF_BENCH(StdVisit_ScaleN16, wl16, scaleN_visit16); + DEF_BENCH(SwitchIndex_ScaleN16, wl16, scaleN_switch16); + + // ScaleN/32 + DEF_BENCH(PatterniaPipe_ScaleN32, wl32, scaleN_ptn32); + DEF_BENCH(StdVisit_ScaleN32, wl32, scaleN_visit32); + DEF_BENCH(SwitchIndex_ScaleN32, wl32, scaleN_switch32); + +} // namespace