From 6fcfb80e5a60152f58768d41b46ac59eda7a68ee Mon Sep 17 00:00:00 2001 From: BFE Engineering Date: Tue, 25 Aug 2026 14:15:21 -0600 Subject: [PATCH] bench: dependency-free sim micro-benchmarks + CI bench job (P4) benches/sim_bench.rs (harness=false, zero deps): fixed-iteration ns/op timing for hpmcu_tick, cru_poll, modbus_read_holding, rga_improcess, membus_poke_peek. Human timings to stdout, one JSON trend line per bench to stderr for CI capture. New CI `bench` job smoke-runs them. Regression-vs-history gating is future work. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_017wB8KB3MMQztRDXCMCkPrf --- .github/workflows/ci.yml | 13 +++++++ sim/Cargo.toml | 5 +++ sim/benches/sim_bench.rs | 78 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 96 insertions(+) create mode 100644 sim/benches/sim_bench.rs diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index cb11382..764aec6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -68,6 +68,19 @@ jobs: [ "$ran" = 1 ] || { echo "no driver MC/DC harnesses found"; exit 1; } exit $fail + bench: + # Smoke-run the sim micro-benchmarks and emit the ns/op trend JSON. Regression + # gating against stored history is future work (no flare-edge pattern to copy). + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: run sim benchmarks + working-directory: sim + run: | + cargo bench --locked --bench sim_bench 1> bench.txt 2> bench.json + echo "== timings =="; cat bench.txt + echo "== trend json =="; grep '"bench"' bench.json + badges: needs: [test] if: github.event_name == 'push' && github.ref == 'refs/heads/bringup' diff --git a/sim/Cargo.toml b/sim/Cargo.toml index 956ebde..d7e4f6e 100644 --- a/sim/Cargo.toml +++ b/sim/Cargo.toml @@ -12,3 +12,8 @@ path = "src/lib.rs" [dependencies] [dev-dependencies] + +# Dependency-free micro-benchmarks (fixed-iteration timing, no criterion tree). +[[bench]] +name = "sim_bench" +harness = false diff --git a/sim/benches/sim_bench.rs b/sim/benches/sim_bench.rs new file mode 100644 index 0000000..1175086 --- /dev/null +++ b/sim/benches/sim_bench.rs @@ -0,0 +1,78 @@ +//! Micro-benchmarks for the warden-sim hardware models. +//! +//! Dependency-free (`harness = false`): a fixed-iteration timing loop, so the sim +//! crate keeps zero deps and CI can capture a stable ns/op number per model with no +//! criterion tree to compile. Human-readable timings go to stdout; one JSON line per +//! benchmark goes to stderr for CI trend capture (`bench: … ns_per_op …`). +//! +//! Run: `cargo bench` (or `cargo run --release --bench sim_bench`). + +use std::time::Instant; +use warden_sim::{CruSim, HpmcuSim, MemBus, ModbusSlave, Rect, RgaSim, SimBus, Surface}; + +fn bench(name: &str, iters: u64, mut f: F) { + for _ in 0..(iters / 10).max(1) { + f(); // warm up + } + let t = Instant::now(); + for _ in 0..iters { + f(); + } + let ns = t.elapsed().as_nanos() as f64 / iters as f64; + println!("{name:<24} {ns:>9.1} ns/op ({iters} iters)"); + eprintln!("{{\"bench\":\"{name}\",\"ns_per_op\":{ns:.1},\"iters\":{iters}}}"); +} + +fn main() { + const N: u64 = 1_000_000; + + // HPMCU watchdog: one tick (mailbox poke + two peeks + state machine). + { + let mut h = HpmcuSim::new(SimBus::new(), 0, 0); + let mut now = 0u64; + bench("hpmcu_tick", N, || { + now += 1; + h.tick(now); + }); + } + + // CRU reset ladder: one poll. + { + let mut c = CruSim::new(SimBus::new()); + let mut now = 0u64; + bench("cru_poll", N, || { + now += 1; + let _ = c.poll(now); + }); + } + + // Modbus RTU: a read-holding-registers request -> response round trip. + { + let mut s = ModbusSlave::new(1, 16, 16); + s.set_holding(0, 0x1234); + let req = warden_sim::modbus::request(1, &[0x03, 0x00, 0x00, 0x00, 0x04]); + bench("modbus_read_holding", N, || { + let _ = s.handle_frame(&req); + }); + } + + // RGA: one improcess dispatch record (+ clear, so the log stays bounded). + { + let mut r = RgaSim::new(); + let sf = Surface { width: 720, height: 720, format: 0 }; + let rc = Rect { x: 0, y: 0, w: 720, h: 720 }; + bench("rga_improcess", N, || { + let _ = r.improcess(sf, sf, rc, rc); + r.clear(); + }); + } + + // MemBus: a poke + peek round trip (the /dev/mem seam's hot path). + { + let bus = SimBus::new(); + bench("membus_poke_peek", N, || { + bus.poke32(0x100, 0xdead_beef); + let _ = bus.peek32(0x100); + }); + } +}