Skip to content

perf(agc): fold the occlusion dumps of a queued store run once - #7777

Closed
teepean wants to merge 1 commit into
boykopovar:mainfrom
teepean:perf/one-fold-per-store-run
Closed

teepean wants to merge 1 commit into
boykopovar:mainfrom
teepean:perf/one-fold-per-store-run

Conversation

@teepean

@teepean teepean commented Oct 19, 2026

Copy link
Copy Markdown

What

A queued store run (Recorder::closeStoreRun) records each group's plain stores and then its occlusion counter dumps. Recorder::QueuedWriteGroups starts a new group at every store after a dump, so a label the title writes after its dumps lands after them (#5232).

Stray (PPSA02100, Unreal Engine 4) writes three things per query slot:

  • a 256-byte clear of the slot;
  • the begin and end dumps into it;
  • an 8-byte 1 just after the slot.

The flag is such a label, so every slot became its own group. A run held about 80 writes in about 20 groups, and each group cost a barrier plus a fold of three SampleDumps.comp dispatches with a barrier before each. That came to about 240,000 label-run barriers per 10 s and 17 ms of GPU time per frame (APS5_PROFILE_GPU=1, about 107 runs per frame).

Recorder::QueuedWriteOrder now records a run's writes in three parts:

  1. the stores before the first dump, and the stores a later dump of the run overlaps (the clears of the slots);
  2. every dump, in one fold;
  3. the other stores, in their order.

What stays as before:

  • A store after a dump that no later dump overlaps (a flag or any other label) still lands after every dump before it.
  • Those stores keep their order among themselves.

What changes: a later slot's clear and dumps can now land before an earlier slot's flag. The flag only lands later than it did, after all of the run's dumps.

The run keeps its old groups when the reordering could change the final bytes:

  • a clear would pass a store it overlaps;
  • a clear that the later dumps cover only in part overlaps an earlier dump.

@xXPBSXx asked about the per-slot cost on #5232. @johnmaia, @Tamalero: could you check Epic Mickey: Rebrushed and High on Life with this? I don't have either title.

Tested

Linux (CachyOS, kernel 7.2.10, scx_lavd), Ryzen 9 9950X, RTX 3090 (NVIDIA 615.78.08), GCC 16.2.1, Release, 3840x2160. Stray at the same save, standing at the start of Dead City, stock engine.ini, one run at a time. Measured at 38a43ec3d on main 676d423; the head is the same change rebased onto 6b8228b (#7489, merged in between, changes the run's leading barrier, not these lines).

One run of each with APS5_PROFILE_GPU=1, last 4 windows of 10 s:

label runs per frame label-run GPU time per frame label-run barriers per 10 s all recorded barriers per 10 s
main 676d423 107-109 17.1-17.9 ms 220,719-256,266 379,124-441,511
this change 106-108 5.2-5.4 ms 22,106-23,425 193,956-204,756

Scored runs, main and this change alternating, with APS5_TRACE_SECONDS=185:20 and no other APS5_TRACE_*/APS5_PROFILE_* variables:

main 676d423, scx_lavd frame p50 (ms) GPU busy per present (ms)
main 155.8, 166.6, 166.4 82.9, 88.6, 90.3
this change 158.4, 166.6, 176.3, 176.9 69.1, 72.9, 80.7, 78.0

Stray is CPU-bound in this scene (queue worker 0 is 97 to 98 % busy in every run), so the GPU busy time per present falls by about 12 ms while the frame time stays within the spread between runs. Queue worker 0's draw packets took 103-105 ms per frame in these main runs and 108-111 ms with the change; runs of one build earlier the same day spread from 102 to 121 ms, and main 676d423 took 112 ms in another run, so I read that as spread too. Left out: one main run that hit the #5372 import race (about 1 fps for the whole window). The first run of each build in this batch was a frame-dump run (below).

The shape of Stray's runs comes from a local diagnostic (not part of this PR) that logged every 499th run with dumps: in 59 of 67 logged runs, 20 to 21 slots of clear, begin dump, end dump and flag followed each other (about 80 writes in about 20 groups), most of them ending with the next slot's clear and begin dump; the others were shorter runs or a single dump. A sample is on the release.

Traces, summaries and digests: https://github.com/teepean/AnyPS5/releases/tag/perf-one-fold-store-runs-20261019 (each scored run's trace, aps5-summary.jsonl and digest.md, the [gputime], [barriers] and [samples] lines of the two profiled runs, the store-run sample and frame 29). The tools/trace_digest.py output for the pair with the same frame p50:

`main` 676d423 (run of2b)

seconds 185:20 from present 2892: 120 presents, frame p50 166.6 / p90 203.2 / p99 211.9 / max 243.3 ms, GPU busy 88.6 ms per present
NVIDIA GeForce RTX 3090, NVIDIA 615.78.08, Linux 7.2.10-1-cachyos, 3840x2160, commit 676d423, dropped: yes

{"commit":"676d423968a1","dirty":false,"env":{"APS5_PAD_INPUT_FILE":"<path>","APS5_TRACE_DIR":"<path>","APS5_TRACE_SECONDS":"185:20"},"gpu":"NVIDIA GeForce RTX 3090","driver":"NVIDIA 615.78.08","os":"Linux 7.2.10-1-cachyos","resolution":"3840x2160","window":{"spec":"seconds 185:20","first_present":2892,"ms":20033.154},"presents":120,"frame_ms":{"p50":166.581,"p90":203.177,"p99":211.926,"max":243.313},"gpu_busy_ms_per_present":88.622,"worker_busy_pct":{"queue worker 0x0":97.625,"queue worker 0x20":99.318},"top_phases_ms_per_frame":{"Driver.Packet":328.215,"Driver.Packet.memory_wait":165.253,"Driver.Packet.draw":103.127,"Driver.Draw":102.405,"Graphics.Draw":81.429,"Graphics.Draw.draw_and_resource_release":69.855,"Driver.Draw.shader_compile_and_link":67.859,"Shader.DrawStage":59.671},"trace_dropped":true,"trace_dropped_threads":{"queue worker 0x0":{"events_full_after_s":4.517,"events_lost":882067,"totals_full":false},"draw committer":{"events_full_after_s":17.442,"events_lost":38424,"totals_full":false}},"trace":"aps5-trace-676d423968a1-2264195.json.gz"}

Slowest frames (largest slices inside each, inclusive)

  • present 2916: 243.3 ms at 4048 ms: Driver.Packet (queue worker 0x20) 243.2, Driver.Packet.memory_wait (queue worker 0x20) 242.7, Driver.Packet (queue worker 0x0) 192.5, Driver.Packet.draw (queue worker 0x0) 143.8
  • present 2960: 211.9 ms at 11410 ms: Driver.Packet (queue worker 0x20) 211.8, Driver.Packet.memory_wait (queue worker 0x20) 209.9, batch (gpu) 91.9, Graphics.Draw (draw committer) 60.9
  • present 3003: 210.1 ms at 18348 ms: Driver.Packet (queue worker 0x20) 210.0, Driver.Packet.memory_wait (queue worker 0x20) 208.1, batch (gpu) 93.0, Driver.Packet.dispatch (queue worker 0x20) 1.8
  • present 2977: 209.1 ms at 14451 ms: Driver.Packet (queue worker 0x20) 209.0, Driver.Packet.memory_wait (queue worker 0x20) 207.1, batch (gpu) 92.5, Graphics.Draw (draw committer) 60.2
  • present 3010: 208.4 ms at 19657 ms: Driver.Packet (queue worker 0x20) 208.3, Driver.Packet.memory_wait (queue worker 0x20) 206.3, batch (gpu) 90.9, Driver.Packet.prepare_memory (queue worker 0x20) 1.4

Threads (ms per frame)

thread busy wait untraced in slices < 20 us largest
queue worker 0x0 26.7 (16 %) 0.1 (0 %) 140.1 52.7 Driver.Packet 26.7, Driver.EndSubmission 0.1
draw committer 47.6 (29 %) 0.0 (0 %) 119.3 63.2 Graphics.Draw 46.4, Recorder.Submit 0.8
presenter 0.6 (0 %) 0.2 (0 %) 166.1 0.1 VideoOut.Flip 0.8, Driver.Present 0.0
queue worker 0x20 0.4 (0 %) 165.4 (99 %) 1.2 0.1 Driver.Packet 165.7, Driver.EndSubmission 0.0
gpu 89.4 (54 %) 0.0 (0 %) 77.6 0.0 batch 89.4

Largest slices (ms per frame, inclusive, all threads): Driver.Packet 192.5, Driver.Packet.memory_wait 165.2, batch 89.4, Graphics.Draw 46.4, Graphics.Draw.draw_and_resource_release 35.3, Driver.Packet.draw 19.5, Driver.Draw 19.3, Driver.Draw.shader_compile_and_link 10.3, Resources.Complete 6.5, Shader.DrawStage 6.0

Longest waits

  • Driver.Packet.memory_wait (queue worker 0x20) 243.5 ms at 3973 ms, present 2915
  • Driver.Packet.memory_wait (queue worker 0x20) 209.3 ms at 11334 ms, present 2959
  • Driver.Packet.memory_wait (queue worker 0x20) 207.7 ms at 7855 ms, present 2938
  • Driver.Packet.memory_wait (queue worker 0x20) 207.2 ms at 18272 ms, present 3002
  • Driver.Packet.memory_wait (queue worker 0x20) 205.7 ms at 13989 ms, present 2974
  • Driver.Packet.memory_wait (queue worker 0x20) 205.1 ms at 3768 ms, present 2914
  • Driver.Packet.memory_wait (queue worker 0x20) 204.4 ms at 13784 ms, present 2973
  • Driver.Packet.memory_wait (queue worker 0x20) 204.4 ms at 19583 ms, present 3009
  • Driver.Packet.memory_wait (queue worker 0x20) 203.9 ms at 2340 ms, present 2905
  • Driver.Packet.memory_wait (queue worker 0x20) 203.2 ms at 1931 ms, present 2903

Largest GPU batches (GPU ranges inside, and the CPU slices that recorded each, inclusive)

  • batch 11612: 6.0 ms at 14453 ms, present 2977: GPU no ranges; recorded by no CPU slices
  • batch 2606: 5.9 ms at 3163 ms, present 2911: GPU no ranges; recorded by Graphics.Draw (draw committer) 0.3, Graphics.Draw.draw_and_resource_release (draw committer) 0.2
  • batch 1933: 5.8 ms at 2415 ms, present 2906: GPU no ranges; recorded by Recorder.Sync (queue worker 0x0) 6.1, Driver.Packet (queue worker 0x0) 0.4, Driver.Packet.dispatch (queue worker 0x0) 0.4, Driver.Dispatch (queue worker 0x0) 0.4
  • batch 9550: 5.8 ms at 11817 ms, present 2962: GPU no ranges; recorded by no CPU slices
  • batch 13119: 5.8 ms at 16064 ms, present 2988: GPU no ranges; recorded by no CPU slices
this change at 38a43ec3d (run of2o)

seconds 185:20 from present 3253: 122 presents, frame p50 166.6 / p90 195.5 / p99 214.9 / max 232.8 ms, GPU busy 72.9 ms per present
NVIDIA GeForce RTX 3090, NVIDIA 615.78.08, Linux 7.2.10-1-cachyos, 3840x2160, commit 38a43ec3d31e, dropped: yes

{"commit":"38a43ec3d31e","dirty":false,"env":{"APS5_PAD_INPUT_FILE":"<path>","APS5_TRACE_DIR":"<path>","APS5_TRACE_SECONDS":"185:20"},"gpu":"NVIDIA GeForce RTX 3090","driver":"NVIDIA 615.78.08","os":"Linux 7.2.10-1-cachyos","resolution":"3840x2160","window":{"spec":"seconds 185:20","first_present":3253,"ms":20107.456},"presents":122,"frame_ms":{"p50":166.601,"p90":195.521,"p99":214.892,"max":232.815},"gpu_busy_ms_per_present":72.907,"worker_busy_pct":{"queue worker 0x0":97.601,"queue worker 0x20":99.117},"top_phases_ms_per_frame":{"Driver.Packet":323.552,"Driver.Packet.memory_wait":162.777,"Driver.Packet.draw":110.209,"Driver.Draw":109.465,"Graphics.Draw":87.073,"Graphics.Draw.draw_and_resource_release":74.785,"Driver.Draw.shader_compile_and_link":70.233,"Shader.DrawStage":61.808},"trace_dropped":true,"trace_dropped_threads":{"queue worker 0x0":{"events_full_after_s":4.336,"events_lost":966919,"totals_full":false},"draw committer":{"events_full_after_s":16.551,"events_lost":59325,"totals_full":false}},"trace":"aps5-trace-38a43ec3d31e-2259816.json.gz"}

Slowest frames (largest slices inside each, inclusive)

  • present 3267: 232.8 ms at 2554 ms: Driver.Packet (queue worker 0x20) 232.7, Driver.Packet.memory_wait (queue worker 0x20) 232.5, Driver.Packet (queue worker 0x0) 178.4, Driver.Packet.draw (queue worker 0x0) 142.2
  • present 3313: 214.9 ms at 9768 ms: Driver.Packet (queue worker 0x20) 214.8, Driver.Packet.memory_wait (queue worker 0x20) 214.3, batch (gpu) 75.4, Graphics.Draw (draw committer) 66.3
  • present 3319: 206.4 ms at 10812 ms: Driver.Packet (queue worker 0x20) 206.3, Driver.Packet.memory_wait (queue worker 0x20) 206.1, batch (gpu) 74.9, Graphics.Draw (draw committer) 72.3
  • present 3333: 203.5 ms at 13175 ms: Driver.Packet (queue worker 0x20) 203.5, Driver.Packet.memory_wait (queue worker 0x20) 203.3, batch (gpu) 75.7, Graphics.Draw (draw committer) 62.3
  • present 3328: 203.3 ms at 12292 ms: Driver.Packet (queue worker 0x20) 203.2, Driver.Packet.memory_wait (queue worker 0x20) 202.7, Graphics.Draw (draw committer) 72.1, batch (gpu) 72.0

Threads (ms per frame)

thread busy wait untraced in slices < 20 us largest
queue worker 0x0 24.1 (15 %) 0.1 (0 %) 140.6 53.3 Driver.Packet 24.1, Driver.EndSubmission 0.1
draw committer 48.6 (30 %) 0.0 (0 %) 116.2 60.5 Graphics.Draw 48.0, Recorder.Submit 0.3
presenter 0.6 (0 %) 0.2 (0 %) 164.0 0.1 VideoOut.Flip 0.8, Driver.Present 0.0
queue worker 0x20 0.4 (0 %) 162.9 (99 %) 1.5 0.1 Driver.Packet 163.3, Driver.EndSubmission 0.0
gpu 72.3 (44 %) 0.0 (0 %) 92.5 0.0 batch 72.3

Largest slices (ms per frame, inclusive, all threads): Driver.Packet 187.4, Driver.Packet.memory_wait 162.8, batch 72.3, Graphics.Draw 48.0, Graphics.Draw.draw_and_resource_release 37.0, Driver.Packet.draw 19.3, Driver.Draw 19.1, Driver.Draw.shader_compile_and_link 9.8, Shader.DrawStage 5.8, Resources.Complete 4.3

Longest waits

  • Driver.Packet.memory_wait (queue worker 0x20) 229.6 ms at 2480 ms, present 3266
  • Driver.Packet.memory_wait (queue worker 0x20) 205.7 ms at 9695 ms, present 3312
  • Driver.Packet.memory_wait (queue worker 0x20) 202.7 ms at 12216 ms, present 3327
  • Driver.Packet.memory_wait (queue worker 0x20) 202.5 ms at 10736 ms, present 3318
  • Driver.Packet.memory_wait (queue worker 0x20) 202.4 ms at 9902 ms, present 3313
  • Driver.Packet.memory_wait (queue worker 0x20) 199.5 ms at 10939 ms, present 3319
  • Driver.Packet.memory_wait (queue worker 0x20) 198.3 ms at 13298 ms, present 3333
  • Driver.Packet.memory_wait (queue worker 0x20) 197.7 ms at 120 ms, present 3253
  • Driver.Packet.memory_wait (queue worker 0x20) 196.0 ms at 7159 ms, present 3297
  • Driver.Packet.memory_wait (queue worker 0x20) 196.0 ms at 1790 ms, present 3262

Largest GPU batches (GPU ranges inside, and the CPU slices that recorded each, inclusive)

  • batch 9504: 4.0 ms at 11346 ms, present 3322: GPU no ranges; recorded by no CPU slices
  • batch 7276: 4.0 ms at 8768 ms, present 3306: GPU no ranges; recorded by no CPU slices
  • batch 15094: 3.9 ms at 18180 ms, present 3363: GPU no ranges; recorded by no CPU slices
  • batch 3578: 3.8 ms at 4281 ms, present 3279: GPU no ranges; recorded by Driver.Packet (queue worker 0x0) 2.9, Driver.Packet.dispatch (queue worker 0x0) 2.7, Driver.Dispatch (queue worker 0x0) 2.7, Vulkan.Dispatch (queue worker 0x0) 2.7
  • batch 14388: 3.8 ms at 17324 ms, present 3357: GPU no ranges; recorded by no CPU slices

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 under Silent stubs in their library's technical debt file
  • 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: none
  • Fixes: none
  • Unblocks: none (performance of PPSA02100, Stray (PPSA02100) on Linux #2461)
  • GPUs tested: NVIDIA GeForce RTX 3090, driver 615.78.08, Linux (CachyOS, kernel 7.2.10)
  • AI-assisted: yes

🤖 Generated with Claude Code

A queued store run records each group's plain stores and then its
occlusion counter dumps, and any store after a dump starts a new group,
so a label the title writes after its dumps lands after them. A title
that clears each query slot, dumps into it and then writes a flag after
it gets a group per slot that way: a barrier, then three fold dispatches
with a barrier before each.

The run's writes are now recorded in three parts where that keeps the
final bytes: the stores before the first dump and the stores that a
later dump overlaps (the clears of the slots), then every dump in one
fold, then the other stores in their order. A store after a dump that
no later dump overlaps still lands after every dump before it, and those
stores keep their order. A clear that only a later dump covers in part
must not overlap an earlier dump, and no clear may overlap a store it
moves ahead of; otherwise the run keeps its groups.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@github-actions github-actions Bot added perf Performance change unblocks title Names a title ID on the Unblocks line open PR limit Closed over the open PR limit: 1 without merged PRs, 20 with at least 1, none from 20 labels Oct 19, 2026
@github-actions

Copy link
Copy Markdown

Thank you for your contribution! Authors with no pull requests merged into main can have 1 open pull request at a time, authors with at least 1 merged can have 20, and from 20 merged there is no limit. This PR has been automatically closed and labeled "open PR limit" because that limit was reached. This is not a judgment of the quality of your changes: reopen it once one of your open pull requests is merged or closed.

@github-actions github-actions Bot closed this Oct 19, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

open PR limit Closed over the open PR limit: 1 without merged PRs, 20 with at least 1, none from 20 perf Performance change unblocks title Names a title ID on the Unblocks line

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant