Skip to content

perf(agc): a DCC key proof over untracked memory holds until the next ordering point - #6209

Open
TheAggie wants to merge 4 commits into
boykopovar:mainfrom
TheAggie:perf/agc-untracked-key-proofs
Open

TheAggie wants to merge 4 commits into
boykopovar:mainfrom
TheAggie:perf/agc-untracked-key-proofs

Conversation

@TheAggie

@TheAggie TheAggie commented Oct 15, 2026 •

Copy link
Copy Markdown
Contributor

What

Stacked on #6187; only the last two commits are this PR. Independent of #6192, except that both add a test at the same place in CMakeLists.txt and tests/Recorder.cpp, a trivial conflict for whichever lands second.

ProvedClearKeys and ProvedCurrentDccKeys skip the scan of a surface's DCC keys while the key range is unchanged since the last scan, proved by CollectWrites and UnchangedSince. Over keys the write watch does not cover (on Windows, every host-pointer import of the guest mapping, #1441), CollectWrites returns 0, so no proof is ever kept and every use scans the keys again.

In RetroArch's Flycast core (PPSA99169) on Windows: 3.7 million key scans per 10 s (72 GB read, 2.2 s), with 0 proved and every one counted unstable.

  • Over untracked keys both functions take a stamp: the collect epoch, NewestStamp (driver stores into the keys), ForgetStamp (perf(agc): an untracked comparison memo holds until memory it covers is forgotten #6187) and the recorder's newest pending write note over the keys. A scan that is otherwise stable becomes the proof with that stamp, and a later call with the same stamp returns the proved keys without scanning.
  • A collect epoch of 0 or a forget in progress gives no stamp, so the scan runs as before.
  • The rule is the one untracked storage images already follow (compareUntracked, docs/dev/ARCHITECTURE.md): a CPU store into untracked memory is seen at the next ordering point. Pending GPU writes still make a scan unstable, as before.

Tested

Windows 11, NVIDIA GeForce RTX 5070 Ti, MinGW-w64 GCC 15.2.0, on main 8cd937e29 with #6187; rebased onto main 92e50418f with #6187 (only a CMake test list conflicted), where the full suite passes 1276 of 1296, 14 skipped, failing only the known tests below and agc_driver_multisample_fill_host_only, which passes when run alone.

  • ctest: 1233 of 1249 pass, 14 skipped. guest_call_trace_abort and windows_backtrace_report fail, and fail the same way on plain main here. agc_driver_prepared_graphics_prebuild_0/_1 and undeliverable_host_exception fail under my test runner because it skips their environment changes and pass pattern; run with those, they pass.
  • New test agc_untracked_key_proofs (agc_driver_recorder_tests --untracked-key-proofs-only): keys in an untracked allocation. In one collect epoch the second ProvedClearKeys and the second ProvedCurrentDccKeys come from the proof; after a store into the keys and a new epoch the keys are scanned again and read as changed. Without this change the test fails with "a second key proof over untracked memory in the same collect epoch scanned the keys again".
  • RetroArch (PPSA99169), Flycast, Crazy Taxi attract demo, on the local stack described in perf(agc): an untracked comparison memo holds until memory it covers is forgotten #6187: key scans per 10 s went from 3.7 million (0 proved) to about 40,000 (2.38 million proved), and the time spent scanning from 2.2 s to 38 ms. Unprofiled audio, two runs each: the ProvedClearKeys change alone did not move it measurably; with the ProvedCurrentDccKeys change as well, 65.3% and 66.9% of real time against 68.0% and 68.0% (that stack also had perf(agc): fast revalidation over untracked memory proves a surface by its stamp until the next ordering point #6192). I have no measurement of main alone.
  • Linux is untested; on Linux most key ranges are write-watched and keep the existing path.

Related: #6060 keeps Windows imports of private write-watched memory on GetWriteWatch when a start-up probe allows it; key ranges it keeps watched never reach this path.

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(agc): an untracked comparison memo holds until memory it covers is forgotten #6187
  • AI-assisted: yes. Written with Claude Code under my direction.

🤖 Generated with Claude Code

@github-actions github-actions Bot added build agc libSceAgc* needs rebase Conflicts with main, rebase needed labels Oct 15, 2026
This was referenced Oct 15, 2026
TheAggie and others added 4 commits October 15, 2026 10:07
…is forgotten

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…son memo

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… ordering point

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…t epoch

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@TheAggie
TheAggie force-pushed the perf/agc-untracked-key-proofs branch from daf7c89 to b00f936 Compare October 15, 2026 16:22
@github-actions github-actions Bot removed the needs rebase Conflicts with main, rebase needed label Oct 15, 2026
@ps54all

ps54all commented Oct 16, 2026

Copy link
Copy Markdown

Data point on Windows from a game: Metal Gear Solid 4 (PPSA18194), gameplay from a save, i9-10850K, RTX 3080 10 GB (610.74), MinGW GCC 15.2, Release. Local stack: #6188, #6187 + #6192, #6273, #6077, and this PR's code commit (8f2c04e) on top; A/B is the same stack without it, 900 s each, APS5_PROFILE_DRAW=1, medians over 10 s gameplay windows. A local diagnostic splits the draw thread's render-target lookups by LookupOutcomes row.

without with this PR
fps 7.5-8.2 8.3-9.1
draws per 10 s / draw thread per draw 179.6k / 44 us 204.9k / 38 us
readTarget per 10 s 1796 ms 741 ms
target-lookup DCC key scans per 10 s 115.7k, 671 ms 132.1k, 83 ms
refresh unchanged (contains the scan) 1017 ms 454 ms

The key ranges here sit in Windows host imports smaller than 1 MiB, so they were never write-watched and every target lookup scanned them twice (the refresh, then materializeRegisterClear's ProvedCurrentDccKeys). HUD text and the rest of the picture look the same as without it (still shots at t600/t900). No errors over the run.

Tested with AI assistance (Claude Code).

@Rajkamalcode

Copy link
Copy Markdown
Contributor

SWAG (PPSA22102) check: the untracked population is small here, no hot-path reduction

Stack tested: #6187 + #6209 combined at 4cb0fd041 (base 448cf78c6), prx SHA-256 prefix E28ABA5A5C33DA72; baseline prx 5AB0413B6D02CBFC. Same tree, only the stack differs.

Measured

Setup: Squirrel with a Gun 01.000.000, stock settings (0156186E), stock scene, fixed pad schedule, APS5_SAMPLE_WORKER + APS5_PROFILE_DRAW=1, 360 s both runs. Baseline 22,952 samples (mid snapshot 17,933); stack 22,939 (17,932); worker thread 99.9% busy in both.

Worker sampler, inclusive share of samples (function anywhere in the stack, all PCs aggregated, counted once per stack). Baseline final/mid2 -> stack final/mid2:

function baseline stack
ShaderResources::Complete 24.12 / 23.15 % 22.14 / 22.17 %
ShaderResources::buildComplete 24.82 / 23.77 % 22.89 / 22.81 %
resolveImageBinding 13.57 / 12.84 % 13.21 / 11.91 %
cachedStorageTexture 14.91 / 14.21 % 14.15 / 13.04 %
CompletePendingCpuWrites 10.18 / 9.81 % 6.70 / 7.61 %
SyncThrough 12.63 / 12.91 % 10.27 / 11.81 %
UploadFinish 11.05 / 10.76 % 9.50 / 10.76 %
DrawPipeline::Enqueue 16.97 / 16.75 % 14.38 / 13.49 %
Recorder::Sync 3.54 / 3.64 % 7.35 / 8.99 %
VulkanDevice::Dispatch 13.25 / 13.47 % 15.92 / 16.80 %
DrawPipeline::Drain 22.40 / 22.39 % 23.38 / 22.99 %
GuestMemory::GpuMutexType::lock 7.04 / 6.40 % 7.08 / 6.27 %

Method note: this aggregates every PC of a function. My earlier #7947 comment quoted only the hottest PC, which understates multi-PC functions there (e.g. buildComplete is 24.8% aggregate, not 13.6%).

Untracked population, per 10 s window: [textures] untracked snapshots kept/compared: 215/46 baseline vs 174/42 stack. [buffers] host imports under 1 MiB: baseline ~6235 settle reads, 6223-6226 back in the write watch, 3 kept compared under a storage image; stack ~6334, 6329-6332 back in the watch, 4 kept compared. The stack run also logs the proof work in [draw] pipelined: 2,583,788 read ranges and 6,992 stores held per 10 s; the baseline does not print these counters.

Timing, same setup, steady tail medians (last 20 x 10 s windows): readTarget 134.3 -> 163.0 us/draw, resources 104.9 -> 130.2, record 28.7 -> 32.4, prepare 6.1 -> 7.7, commit 260.4 -> 305.3 us, enqueue wait 110.1 -> 114.7. Caveat: the two runs' scene trajectories diverged (draws per 10 s window differ by up to ~2x in the first ~100 s; the two stock repeats show the same class of divergence), so these deltas are not clean PR attribution from one pair.

AI-generated analysis (not measured)

The proof only pays when target key ranges are not write-watched. On SWAG nearly all sub-1 MiB host imports are back in the write watch (6223-6332 of ~6240), so there is almost nothing for the memo/proof to elide, unlike the MGS4 ranges in ps54all's comment. What changes instead is when the worker blocks: DCC path counts that do involve untracked comparisons go down (Complete, buildComplete, cachedStorageTexture, CompletePendingCpuWrites, snapshots compared 46->42), while Recorder::Sync and VulkanDevice::Dispatch go up by more than that. Net: the hot path (ShaderResources::Complete 22-24%, storage-texture binding work) is not reduced; on this title the change reads as correctness/coverage with a small overhead, not a perf win. I would keep MGS4 as the perf case and treat SWAG as neutral. No further A/B planned unless you want one; if useful I can run a low-settings variant (fewer dispatches, more draw/commit path) or a dedicated 4-run paired timing.

Artifacts (I will drag them in): swag-run26-baseline-symbolized.txt, swag-run27-stack-symbolized.txt, swag-stack-agg.txt, swag-stack-timing.txt, swag-run27-stack-worker.collapsed.txt.

@Rajkamalcode

Copy link
Copy Markdown
Contributor

This branch has not been deployed

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

Labels

agc libSceAgc* build needs rebase Conflicts with main, rebase needed perf numbers missing perf pull request without a before and after summary line perf Performance change

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants