Skip to content

perf(recompiler): load the written-page slot before the CAS - #1280

Merged
adriadam10 merged 4 commits into
boykopovar:mainfrom
TheAggie:perf/recompiler-bda-note-load
Oct 9, 2026
Merged

adriadam10 merged 4 commits into
boykopovar:mainfrom
TheAggie:perf/recompiler-bda-note-load

Conversation

@TheAggie

@TheAggie TheAggie commented Oct 7, 2026

Copy link
Copy Markdown
Contributor

What

Depends on #1265. Until it merges, this PR also shows its three commits; only the last commit is new here.

note_bda_write runs a device-scope OpAtomicCompareExchange on every probe, also when the slot already holds the page. The written-page table lives in the BDA fault buffer, which is host-visible memory, so every BDA store pays an atomic across PCIe. Almost every store hits a page that an earlier store already noted.

  • Each probe now loads the slot with a relaxed OpAtomicLoad and runs the compare-exchange only when the slot does not hold the page.
  • The slots probed and their order, the pages recorded, and the overflow report are unchanged. If the load sees a stale 0 or another page, the compare-exchange runs exactly as before.

Why the CAS is the cost

DOOM homebrew PPSA99666 v1.1.1 (lowbit/ps5-doom, GPL-2.0) presents through a small compute kernel: 1080 workgroups of 64, each reads palette-indexed bytes and stores 1920 pixels through raw pointers, about 2.07 M BDA stores per frame. It waits 500 ms for the frame's fence and otherwise falls back to its CPU scaler.

GPU time per dispatch DOOM
main 174 ms (APS5_PROFILE_GPU) GPU path, 5.5 FPS
#1265 248 ms (APS5_SYNC_DISPATCH, packet time) frame 3 exceeds 500 ms, CPU fallback
#1265 + this 8.1 ms (APS5_PROFILE_GPU, ~590 dispatches per 10 s) GPU path, 60 FPS (display refresh)

Experiments on main before writing this:

  • Allocating the fault buffer DEVICE_LOCAL | HOST_VISIBLE | HOST_COHERENT (ReBAR) instead of system memory gave the same gain: about 170 ms to about 10 ms per frame.
  • Moving the BDA range table to device-local memory as well changed nothing measurable, so the table lookups are not the cost.
  • APS5_SPIRV_OPT=full changed nothing.

This change keeps both buffers where they are and needs no ReBAR.

Tested

Windows 11 Pro (26200), MinGW-w64 GCC 15.2.0 (WinLibs r7), Release, NVIDIA GeForce RTX 5070 Ti, Ryzen 7 7800X3D.

  • All 195 agc_* and shader_* tests pass on this branch.
  • Built with ANYPS5_ENABLE_SPIRV_TOOLS=ON, which validates every module before and after optimization: flat_store, global_store_addtid, global_atomics, global_atomics64, global_atomics_float, global_atomics_lanes, global_atomic_csub, flat_cache_bits, global_vcc_base, flat_loads, d16_memory and the BDA tests pass (13 tests).
  • On main, the same change before perf(recompiler): loop the BDA byte stores and written-page probes #1265 also passed all 396 registered tests.
  • Tests were run from the commands and environments in the generated CTestTestfile.cmake files, because antivirus blocks ctest.exe on this machine.
  • DOOM measured as in the table. The dumped request (APS5_DUMP_SHADERS, shader_202a81800.req, 304 code words) replays with agc_shader_replay; I can attach it.

Not run on AMD, Intel or Linux.

Checklist

  • Based on current main; no other open PR implements the same functions
  • One topic per PR; follow-ups go in a new PR
  • No comments in code except technical debt
  • Unimplemented paths throw (NotImplemented_nid_no_patch); silent stubs are listed in TechnicalDebt
  • No notes, investigation .md files or images added to the repository (attach them to this PR)
  • New third-party code is a submodule built from source
  • Generic behaviour, not specific to one title
  • Depends on: perf(recompiler): loop the BDA byte stores and written-page probes #1265
  • AI-assisted: yes

🤖 Generated with Claude Code

popcoder565 and others added 4 commits October 7, 2026 16:18
Every 8- and 16-bit BDA store and the unaligned path of every 32-bit store emitted one get_bda_write_pointer lookup and one note_bda_write per byte at the store site, five write lookups per 32-bit store. They now call write_bda_bytes, which loops over the bytes and does, per byte and in order, the same lookup, the store when it is mapped and the write note, so faults are recorded exactly as before. On an RTX 3060 the agc_driver_flat_store binaries shrink from 496,256 to 138,240 bytes (wave32) and from 928,384 to 225,152 bytes (wave64), and the cold run from 19.7 s to 2.2 s.
note_bda_write unrolled its 8 compare-exchange probes, and drivers inline it at every BDA store and atomic. The probes now run in a loop that tries the same slots in the same order and stops at the same slot, so the recorded pages and the overflow flag are unchanged. With the byte store helper, the agc_driver_flat_store binaries on an RTX 3060 go from 138,240 to 75,648 bytes (wave32) and from 225,152 to 130,560 bytes (wave64), and the cold run from 2.2 s to 1.2 s.
… path

The declaration, the state field and the definition of write_bda_bytes now sit next to EmitBdaStore and the other BDA write functions instead of next to the dword read functions, which boykopovar#1267 extends at the same lines. No change to the emitted code.
note_bda_write ran a device-scope compare-exchange on every probe, also
when the slot already held the page. The table lives in host-visible
memory, so each BDA store paid an atomic across PCIe, and nearly every
store hits a page an earlier store noted. Load the slot first and only
compare-exchange when it does not hold the page. The slots probed, the
pages recorded and the overflow report are unchanged.

DOOM (PPSA99666) presents through a compute kernel that stores about two
million pixels through raw pointers per frame: 248 ms per dispatch
before, 8.1 ms after, on an RTX 5070 Ti.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@johnmaia

johnmaia commented Oct 8, 2026

Copy link
Copy Markdown
Contributor

Measured on AMD and Linux, which the description lists as not run: Ubuntu 24.04.5 (kernel 6.8), GCC 13.3, Release, AMD Radeon RX 7600 (RADV NAVI33, Mesa 25.2.8, resizable BAR on). This PR's head is 1dca7b4f (including #1265), merged onto main 6b56175a.

DOOM homebrew PPSA99666 v1.1.1, built from source, relinked with --exclude-sce-module libc.prx. A test plan of frames 1500, a warm-up run first and then a measured run. GPU time is from APS5_PROFILE_GPU, in two 10 s windows of about 600 presents each:

GPU time per 10 s per present DOOM
main 876 / 865 ms 1.45 ms GPU path, 60 FPS
#1265 863 / 858 ms 1.43 ms GPU path, 60 FPS
#1265 + this 792 / 785 ms 1.31 ms GPU path, 60 FPS
  • On RADV the compare-exchange that this PR avoids is cheap, so main is already about 120× faster per present than the 174 ms measured on the RTX 5070 Ti. The fault buffer is in the same memory as on Windows: Buffer picks host-visible, coherent, cached memory, which is system memory, not the ReBAR heap. The PR still takes 9% off the presenter's GPU time here, and nothing regressed.

  • Code size with APS5_PIPELINE_STATS=1 and cold caches (largest pipeline). As expected, the extra load per probe adds a little:

    • flat_store: 126,612 → 141,368 B.
    • global_vcc_base: 195,996 → 207,616 B.
    • global_store_addtid: 82,636 → 93,356 B.
    • global_atomics: 51,016 → 56,840 B.

    The full table on perf(recompiler): loop the BDA byte stores and written-page probes #1265 is here.

  • Full ctest: 479/479 pass on this branch, the same as on main, with the GPU tests running on the RX 7600.

@fisherjoey

Copy link
Copy Markdown
Contributor

Linux results, since this was tested on Windows only. Everything passes here.

Nobara 44 (Linux 7.2), GCC 16.2.1, Release, BUILD_TESTING=ON. This PR merged onto main 29e533a.

main 29e533a main + this PR
RTX 5070 Ti, driver 595.91.07, ctest -R agc_driver -j1 183 / 183 183 / 183
RADV, Ryzen 5 7600X iGPU (gfx1036), Mesa 26.2.1, same 183 / 183 183 / 183
CPU tests (-E agc_driver) 279 / 280 279 / 280

The CPU failure is guest_dynamic_loader, which fails the same way on main.

@adriadam10

Copy link
Copy Markdown
Collaborator

Checked the diff against the original probe loop: same slots in the same order, same done and overflow behaviour; a stale 0 or another page in the load just falls through to the compare-exchange as before. Built on current main (#1265 is merged, so only this commit is new): ctest 551/551 green on Linux. Also confirmed by @johnmaia (RX 7600, RADV) and @fisherjoey (RTX 5070 Ti and RADV). Merging once CI is green.

Note: no existing test fails if the page is never recorded through this path (I forced the "slot already holds the page" check to true and agc_driver_flat_store, global_atomics, global_store_addtid still passed). Not a regression of this PR, but a test pinning the written-page table would be welcome in a follow-up.

@adriadam10
adriadam10 merged commit a12eda1 into boykopovar:main Oct 9, 2026
4 checks passed
SleglJan pushed a commit to SleglJan/AnyPS5 that referenced this pull request Oct 10, 2026
…note-load

perf(recompiler): load the written-page slot before the CAS
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

shader core/shader

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants