Profiling DSVita
Prerequisite: .env with DSVITA_PI_HOST (see run-on-testbox skill for deploy/keys/rules).
perf works unprivileged on the box (perf_event_paranoid=-1). Decide first which of the
three tools answers the question:
| question | tool |
|---|---|
| which functions/blocks are hot | flat profile (perf map) |
| which instructions inside them | annotate (native) / jitdump (JIT blocks) |
| did my change help | A/B throughput — the only tool that catches fence/contention costs |
Builds
- Flat profile / JIT-block annotate:
--profile release-debug(optimized + debuginfo). - Native-code annotate with source lines: same build;
CARGO_PROFILE_RELEASE_DEBUG=2if using--release(envRUSTFLAGSreplaces.cargo/config.tomlrustflags — repeat-Ctarget-feature=+v7,+neon,+vfp3,+thumb2,+thumb-modeif you set it). - Callgraphs through the JIT are impossible (no fp chains in emitted code) — don't try; flat attribution plus jitdump is the workflow.
Flat profile
The emulator always writes /tmp/perf-<pid>.map on Linux (ARM9_<guest_pc> symbols).
perf record -F 997 -p <pid> -o out.data -- sleep 30
perf report -i out.data --comms cpu --no-children -s symbol -g none --percent-limit 0.05 --stdio
--comms cpu = the emulation thread. Other threads: audio_out (busy-wait pacing by
design, ignore its share), actual_main (GL present), process_3d.
Inside JIT blocks (jitdump)
Custom perf on the box: ~/linux-7.0/tools/perf/perf (built from kernel 7.0 with patched
util/genelf.c|h: thumb bit on the symbol, $t mapping symbol, EM_ARM/ELFCLASS32 forced —
if it's ever rebuilt, those patches must survive or thumb blocks disassemble as garbage).
DSVITA_JITDUMP=1 ./dsvita <rom> ... # writes /tmp/jit-<pid>.dump
perf record -F 997 -k CLOCK_MONOTONIC -p <pid> -o out.data -- sleep 30 # mono clock REQUIRED
perf inject --jit -i out.data -o out.jitted.data # -> /tmp/jitted-<pid>-N.so
perf annotate -i out.jitted.data --stdio -s ARM9_<guest_pc>
To find systematic emitter overhead, aggregate hot lines across the top ~25 blocks (loop:
report -s dso → readelf the .so for the symbol → annotate, keep lines ≥ N%).
Attribution caveats:
- One jitdump record per debug-info sub-block: a branch between records of the same compiled unit LOOKS cross-block but is local.
- High % on a cheap instruction right after a load = the load's latency (skid).
- Fence/lock/contention cost barely shows in samples at all (a lock that capped throughput 5x sat at ~1%). If a subsystem "can't be the problem" by sample share, A/B it anyway.
A/B throughput benchmark
Metric: per-second vblank count lines the emulator logs on stdout (uncapped = raw speed).
- Deploy both binaries under distinct names;
md5sumlocally first (stale-binary trap: never chain edit && build in one command). - Navigate to the SAME test scene at
-f 1(choreography timed against log-line counts breaks at uncapped speed), then press F10 to uncap live (F1–F9 = framelimit 1–9). N=$(wc -l < log); sleep 42; tail -n +$((N+1)) log | head -40→ mean ± sd.- Rules: back-to-back runs (thermal drift between sessions beats the effect size), audio-on and audio-off are different baselines, sd is typically ±7 — treat anything under ~2% as noise, and re-measure the baseline whenever a result surprises you.
Identifying hot guest blocks
Top ARM9_<pc> symbol → dump live guest memory through the fastmem mirror
(/proc/<pid>/mem at host 0x80000000 + guest_addr on Linux — catches runtime-loaded and
decompressed code the static rom misses) → objdump -D -b binary -m arm (add
-M force-thumb for thumb regions) → identify against the SDK sources or decompilation
projects before writing any HLE pattern. Read constants a pattern's handler needs from the
guest literal pool at runtime; never hardcode them.
The verdict rule
The dev box and the target are both out-of-order, but very different cores (A76-class vs the Vita's A9: far smaller OoO window, caches, and thread architecture). Wins here can be nil there (and vice versa — several target-positive changes measured neutral here). A perf change only counts after the target hardware measured it. One change per commit so each can be accepted or dropped independently.