kobodl/BENCH.md
Ville Lindholm 869056c5d2
Phase 0: profiling scaffolding for DOD refactor
Adds a tiny zone-based profiler (prof.{h,cpp}) enabled via KOBO_PROF=1.
Instruments run_game with zones for myship.move, enemies.move (split into
realize and move_pass), hit_structure, and hit_bolt. Adds KOBO_STRESS=N
to keep the enemy pool topped up with rocks for stable per-frame timing.

Why: the entity/render pipeline refactor in BENCH.md needs a comparable
baseline before any layout changes. Without a stress workload the natural
gameplay enemy count is too noisy to compare across commits.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2026-05-29 13:31:31 +03:00

2.8 KiB

Kobo Deluxe DOD Refactor — Benchmark Scoreboard

Each row is one phase of the refactor (see ~/.claude/plans/can-you-explain-the-noble-wilkes.md). Numbers are median ns/frame from KOBO_PROF=1 KOBO_STRESS=1500 ./build/kobodl, ~1000 frames, release build (-O2 -g).

How to capture a baseline

cd build
cmake --build . -j
KOBO_PROF=1 KOBO_STRESS=1500 ./kobodl
# play through a stage; profile dumps every 600 frames to stderr.
# Redirect stderr to a file if you want to keep it:
KOBO_PROF=1 KOBO_STRESS=1500 ./kobodl 2>prof.log

The profiler dumps a table per zone with calls, total_ms, ns/call. Record the ns/call for these zones (they are the moving parts the refactor targets):

Zone What it measures
run_game Full game tick
myship.move Player + bolt update
enemies.move Realize pass + move pass combined
enemies.realize Just the realize-reserved pass
enemies.move_pass Just the per-enemy move pass (the hot loop)
hit_structure Player vs map + bolt vs map
hit_bolt Bolt-vs-enemy scan (called from inside enemy move)

Scoreboard

Phase run_game myship.move enemies.move_pass hit_bolt Notes
0 — baseline ? ? ? ? TBD — user to capture
1 — hot/cold split
2 — SoA pool
3 — bucket dispatch
4 — flipped bolt collision
5 — render decouple
6 — SIMD integrator (opt.)

Notes

  • KOBO_STRESS=N keeps the pool topped up with rocks; N=1500 sits well below ENEMY_MAX=2048 so the spawner doesn't thrash on full-pool checks.
  • The stress spawner uses pubrand, which is acceptable for benchmarking but will not produce identical demos across builds — that's fine, we only care about steady-state cost.
  • For a stable run, start the game, get into a stage, then let it sit for ~1000 frames before reading numbers. The first dump (frame 600) will include startup costs and should be discarded.