4.8 KiB
nigig-traffic perf baseline (plan Phase 6.3)
Machine: MacBook Pro, Apple Silicon (darwin x86_64 binary under Rosetta —
target/debug/nigig-traffic: Mach-O 64-bit executable x86_64), debug
profile. Two consecutive runs, cargo test -p nigig-traffic --test perf -- --test-threads=1 --nocapture. Catalog: 47 scenarios (the old
"36+" copy is a floor, still true).
build_world_for — median µs over 20 reps
| Category | Run 1 | Note |
|---|---|---|
| Lanes | 2 | |
| Roundabout | 11 | 16 ring segs + island |
| Parking | 5 | 4 bays |
| UTurns | 4 | |
| Signs | 3 | |
| Priority | 2 | |
| Hazards | 3 | |
| Speed | 2 |
Single-digit microseconds everywhere. World build is not a cost center — no caching/baking work is justified.
tick — 600 ticks/scenario, idle input, per-tick MEAN within scenario
Diagnostic only: debug profile, idle input, CPU submission/tick timing, no GPU/rendering measurement, Rosetta-translated x86_64 binary. These numbers must NEVER be presented as a release pass (TRAFFIC-15).
| Run 1 | Run 2 | |
|---|---|---|
| median-of-means | 14 µs | 26 µs |
| slowest #1 | haz_rain 501 µs | lane_bus_lane 450 µs |
| slowest #2 | pri_yield_car 255 µs | pri_yield_car 386 µs |
| slowest #3 | rb_multi_exits 248 µs | ut_single_turn 257 µs |
Reading, per HPC ch.5.6: the slowest set moves between runs
(haz_rain/rb_multi_exits appear once each) — that is noise (debug
build, shared machine), not a scenario-intrinsic cost. pri_yield_car
ranks top-3 twice; it spawns a moving AI car, so it is the prime suspect
for the only genuine above-median cost — unconfirmed until a pinned,
quiescent re-run. No optimization is authorized off this table alone.
Live run (NIGIG_TRAFFIC_STATS=1, same machine, windowed)
traffic stats: tick us p50=48 p95=270 p99=5761 (n=300)
traffic stats: draw us p50=69 p95=563 p99=4182 (n=300)
traffic stats: tick us p50=55 p95=332 p99=1839 (n=300)
traffic stats: draw us p50=76 p95=663 p99=4272 (n=300)
traffic stats: tick us p50=59 p95=561 p99=3681 (n=300)
Live tick p50 (~55µs) runs hotter than the harness median — it shares the machine with the window server and includes rig/camera work around the timed region. p99 spikes are millisecond-scale frame hitches (scheduler / allocator / display-link, not sim code): the next profiling step is an Instruments sample attributing p99, not optimizing p50.
Blocked: release-vs-size profile decision (Phase 9)
release currently sets opt-level = "z" with no LTO. Whether the game
binary wants opt-level = 3 (throughput over size) must be measured on
the Phase-6 bench — but the workspace-wide fork rev bump broke
compilation of every crate (upstream b9a083c references a never-landed
isolate refactor: enter_isolate/leave_isolate/IsolateEntry used in
4 files, defined in 0; plus a 4-vs-5-arg camera_boom_limit drift), so
no release numbers exist yet. Decision and both measurements go here
once the workspace builds again. Do not choose on aesthetics.
Release evidence (TRAFFIC-15) — BLOCKED, do not ship without this
No release-profile, active-input, rendered/GPU measurement exists yet: the workspace pin does not compile (TRAFFIC-P0-01), so no release build of any Traffic target exists. When the pin is coherent, fill this section by running on declared desktop + mobile hardware:
cargo test --locked -p nigig-traffic --test perf_release -- --test-threads=1 --nocapture
NIGIG_TRAFFIC_STATS=1 cargo run --locked --release -p nigig-traffic
Required budgets (§6): sim p99 ≤ 2 ms/tick on max-content scenario; main-thread non-render event p95 ≤ 8 ms; frame p95 ≤ 16.7 ms desktop 60 Hz / ≤ 33.3 ms mobile 30 Hz; input-to-visible p95 ≤ 50 ms; scenario load p95 ≤ 100 ms warm; ≤ 200 draw submissions; ≤ 500k visible tris; texture ≤ 256 MiB desktop / 128 MiB mobile; 30-minute active soak with no unbounded growth; suspend/resume; 10k-attempt persistence. Debug/Rosetta/idle/CPU-only numbers above are historical diagnostics, never a release pass.
Conclusions authorizing later phases
- Tick ≈ 15–25µs median (debug). At 60Hz the budget is 16,600µs — the sim consumes ~0.1%. There is no throughput emergency.
- Costs are latency-shaped (lookups, allocations, branches over dozens of entities), never compute-shaped. Phases 7–8 target exactly that; hand-written SIMD is off the table (documented anti-goal).
- Next measurement before any Phase 7 code: quiescent release-profile
re-run + Instruments sample confirming
step_worldvsevaluatevs text-shaping split.