References: ref-docs/research-report.md "Principal engineering challenges";
docs/scheduler.md; docs/architecture.md.
- Headless core: ≤ ~2 ms per emulated frame (the RustyNES headless budget) so the frontend has slack for present + run-ahead + netplay rollback. The SNES does more per frame than the NES (two CPUs, the variable cycle, HDMA), so treat 2 ms as a target to defend, not a given.
- Real-time: sustain 60.0988 Hz (NTSC) / 50.0070 Hz (PAL) frame pacing in the frontend without underruns on the lock-free audio ring.
Per ref-docs/research-report.md §§1–5, the per-cycle cost is dominated by:
- The CPU bus access dispatch — every CPU cycle queries the region map for its 6/8/12
speed (
docs/scheduler.md§access-speed-map). Keep this a branch-light table lookup, not a match cascade. - PPU pixel emission — the per-dot background / sprite / color-math pipeline at 341 dots × 262 lines. This is the analog of RustyNES's "PPU pixel emission" hot path.
- The SPC700 resync accumulator — cheap per step, but the per-port-access and
per-scanline sync points are on the hot path (
docs/apu.md§async-resync). Keep the accumulator arithmetic integer and inline. - DMA/HDMA byte loops — content-dependent; the per-line HDMA budget (≤466 clk) must not allocate.
- Hot paths (
Cpu::step,Ppu::tick, the resync, mapper register access): no allocations, prefer fixed arrays, profile (cargo bench+perf record) before adding abstractions. - Measure first: never optimize without a Criterion baseline; gate any "perf" change on a
≥ measurable Criterion delta + byte-identical output (the determinism contract,
docs/adr/0004). releaseprofile islto = "fat",codegen-units = 1,panic = "abort"(seeCargo.toml).
cargo bench -p rustysnes-cpu/-ppu/-apu/-core(Criterion) per crate. The integration-level headless-frame benchmark is landed (crates/rustysnes-core/benches/headless_frame.rs) — seedocs/benchmarks.mdfor the actual measured number and how to reproduce it. Per-crate hot-path benchmarks (CPU dispatch, PPU per-dot emission, the SPC700 resync) are not yet split out.perf recordon a headless replay of a known ROM for the integration hot path — not yet run;docs/benchmarks.md's current baseline is Criterion wall-clock only, no flamegraph yet.- Landed (
v1.0.0): a frame-time regression gate in CI (.github/workflows/ci.yml'sbenchjob,scripts/bench_regression_check.sh), mirroring the RustyNES pattern — runsheadless_frameon release-tag pushes and asserts the steady-state mean stays under an absolute 10 ms/frame ceiling (~60% of the 16.64 ms NTSC deadline, ~3x the measuredv0.4.0baseline). An absolute ceiling, not a tight %-regression check, deliberately — shared CI runners vary by tens of percent run-to-run, so a percentage gate would flake; use local Criterion--save-baseline/--baselinecomparisons (the script's own header comment) for a tighter before/after read. - Landed (
v1.19.0 "Afterburner"): an optional PGO/BOLT pipeline for the shippingrustysnesbinary —scripts/pgo/run.sh(instrument → train against the committed permissive ROM corpus viacrates/rustysnes-test-harness/src/bin/pgo_trainer.rs→ optimized rebuild) plus.github/workflows/pgo.yml(workflow_dispatch+ release-tag push; never on the PR gate — an instrument+train+rebuild cycle is far too slow for that). Promotion requires both: a measured> 3%headless_frame_steady_stateCriterion speedup over the plain release build on the same runner, and a byte-identical re-run of the full--features test-romsoracle under the PGO-merged profile (citesdocs/adr/0004's determinism contract — PGO changes inlining/code layout, never proves correctness by assertion). An optional Linux-only BOLT post-link stage chains onto an already-promoted PGO binary, best-effort (skips cleanly ifllvm-boltis unavailable on the runner). Verified for real in this development environment: the full instrument → train (60 frames × the 5-ROM committed corpus) → optimized-rebuild pipeline produces a genuine, runningrustysnesbinary, andcargo pgo optimize test -- --workspace --release --features test-roms(scoped torustysnes-test-harnesslocally) passes cleanly under the PGO-merged profile — but the A/B speedup on that short local run did not clear the>3%bar (aheadless_frame_steady_statemean of ~3.65 ms either way), so promotion is correctly gated off locally; whether CI's real3600-frame training run clears the bar on a release tag is left topgo.yml's own gate to decide honestly, not asserted here.
- Whether the variable-cycle dispatch wants a precomputed per-bank speed table (256 banks × region) vs a computed predicate — benchmark in Phase 1.
- Whether the SPC resync's once-per-scanline forced sync is frequent enough to bound latency without hurting throughput — tune in Phase 3.