Skip to content

StaticRecomp: skip guest busy-wait loops the analyzer can identify - #19

Open
dougchansan wants to merge 1 commit into
ExpansionPak:moderngekko-vendorfrom
dougchansan:feat/busy-wait-idle-detection
Open

StaticRecomp: skip guest busy-wait loops the analyzer can identify#19
dougchansan wants to merge 1 commit into
ExpansionPak:moderngekko-vendorfrom
dougchansan:feat/busy-wait-idle-detection

Conversation

@dougchansan

Copy link
Copy Markdown

The dispatcher already returns the rest of its slice to CoreTiming when the guest reaches MAIN_STATICRECOMP_IDLE_PC, but that only covers loops configured ahead of time. Every other idle loop is spun through in real time.

A dispatch that returns to the address it entered at is a self-loop. Running PPCAnalyst over that address tells us whether it is an idle loop (a branch with branchIsIdleLoop back to the entry); if it is, there is nothing to gain from executing it until the next event.

  • result cached per address, invalidated wherever guest code can change — ClearCache and SMC re-verification
  • skipped entirely when a debugger or branch watch is active, since both want every block to actually execute
  • 44 lines across 4 files, no new dependencies (analyzer, code_block, m_code_buffer all come from JitBase)

Measurements

x86-64, static recompilation active, frames advanced in a fixed 20s window. The same binary is used for both arms, with detection toggled at runtime, so nothing but this behaviour differs.

title / scene detection on off ratio t 95% CI
Skyward Sword — gameplay 796.2 (n=6) 571.8 1.392 10.47 1.317–1.467
Luigi's Mansion — foyer 751.9 (n=8) 672.1 1.119 3.58 1.052–1.185
Mario Kart DD — bench 1033.5 (n=6) 965.3 1.071 2.64 1.017–1.124
Pokémon Colosseum — 2 scenes 0.98–1.01 neutral

No title regressed. On Skyward Sword the arms do not overlap at all: the worst detection-on run (730 frames) still beats the best off run (606).

Why these numbers can be trusted

The noise floor was measured rather than assumed, by supplying the same module as both control and arm so the true ratio is exactly 1.0000:

n=13 null comparisons across 4 titles / 6 scenes
12 of 13 within +/-1.72%
 1 of 13 at +14.97%  (one scene; 3 repeats of it then gave 1.0164 / 0.9972 / 1.0019)

Single runs therefore carry a rare ~15% excursion, and every figure above is a mean over repeated runs rather than one measurement.

Three confounds had to be controlled before the effect was measurable at all, each of which produced plausible-looking but wrong numbers:

  • host input reaching the guest. With no device bound and BackgroundInput = False the detection-on arm spreads 651–810 frames; without that pinning the same arm spread 829–7157, an 8.6x range.
  • Wii extension config. A Wii title reads its extension from WiimoteNew.ini, not from the savestate, so omitting Extension = Nunchuk leaves Skyward Sword sitting on a "connect the Nunchuk" prompt. That measured +119% and was an artifact of the dialog, not of this change.
  • one emulator at a time. A second concurrent instance compressed the same Skyward Sword effect from +39% to +30%.

Honest characterisation

The win is opportunistic: detection only fires when a dispatch happens to re-enter at the loop head. That shows up as higher variance in the detection-on arm (sd 46–60, against 18–25 with it off). It is a consistent gain, not a uniform one, and Colosseum shows it can also be simply neutral.

The dispatcher already hands the rest of its slice back to CoreTiming when the
guest reaches MAIN_STATICRECOMP_IDLE_PC, but that only covers loops configured
ahead of time. Everything else is spun through in real time.

A dispatch that returns to the address it entered at is a self-loop. Running
PPCAnalyst over that address identifies whether it is an idle loop (a branch
with branchIsIdleLoop back to the entry), and if so there is nothing to gain
from executing it until the next event. The result is cached per address and
invalidated wherever guest code can change: ClearCache and SMC re-verification.
Analysis is skipped entirely when a debugger or branch watch is active, since
both want every block to actually execute.

Measured on x86-64 with static recompilation active, frames advanced in a fixed
20s window, identical binary in both arms with detection toggled at runtime, so
nothing else differs. Input was pinned (no host device bound, BackgroundInput
off) and only one emulator ran at a time:

  title                     detection on   off    ratio    t      95% CI
  Skyward Sword  gameplay   796.2 (n=6)   571.8   1.392   10.47   1.317-1.467
  Luigi's Mansion foyer     751.9 (n=8)   672.1   1.119    3.58   1.052-1.185
  Mario Kart DD  bench     1033.5 (n=6)   965.3   1.071    2.64   1.017-1.124
  Pokemon Colosseum         (2 scenes)            0.98-1.01      neutral

No title regressed. The harness noise floor was measured separately by running
the same module as both control and arm: 12 of 13 such null comparisons landed
within +/-1.72% of 1.0, so the three wins above sit well outside it.

The win is opportunistic by nature -- detection only fires when a dispatch
happens to re-enter at the loop head -- which shows up as higher variance in
the detection-on arm (sd 46-60 vs 18-25 off). It is a consistent gain, not a
uniform one.
@dougchansan

Copy link
Copy Markdown
Author

Note on the measurement environment, since it came up while working on ModernGekko #34.

Every number in this PR was taken with MODERNGEKKO_CACHE_AFFINITY unset, which on a 9950X3D leaves the emulated CPU thread free to migrate off the V-Cache die. Enabling it is worth about +25% in absolute speed on that machine.

The ratios here are unaffected. I checked this directly rather than assuming it: pinning lifts the C and LLVM arms by the same amount (+13.2% each on Colosseum, n=6 per cell), and the arm/control ratio moves +0.6%, which is noise. Both arms carried the same handicap, so the comparisons stand exactly as reported.

Two things worth knowing for anyone reproducing these figures:

  • Absolute speeds understate the machine. Re-running with pinning on will produce higher numbers in both arms, not a different conclusion.
  • Pinning also cuts run-to-run spread sharply — one Colosseum scene went from ±0.297 to ±0.015 standard error over three samples. That makes replication cheaper, so I would recommend it for any re-measurement even though it does not change the answer.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant