Skip to content

perf(agc): find the address space regions under pending writes by search - #5465

Merged
oneandonlydean merged 1 commit into
boykopovar:mainfrom
winstrex:perf/space-pending-writes
Oct 14, 2026
Merged

oneandonlydean merged 1 commit into
boykopovar:mainfrom
winstrex:perf/space-pending-writes

Conversation

@winstrex

@winstrex winstrex commented Oct 13, 2026 •

Copy link
Copy Markdown
Contributor

What

An address-based build that takes the cached address space completes pending CPU writes over the space's regions in GuestBufferMemory::UploadFinish. It called CompletePendingCpuWrites once per base region: each call copied the draw writer list, read the label count and scanned every write of the open and in-flight batches. The space holds a few thousand regions, so on The Pathless's main menu (about 45 address-based builds per frame) that walk took 189 µs per build, 8.4 ms per frame.

Now UploadFinish takes the pending writes once (Recorder::PendingWriteRanges: the writes of the open batch and of the batches in flight, the same set PendingWriteOverlaps reads), finds the base regions they overlap by binary search (OverlappedRanges; the base regions are sorted and disjoint), and calls CompletePendingCpuWrites for those regions only, in ascending order as before. A region no pending write overlaps returned from that function before doing anything, so it is skipped without a call. When a call syncs, the pending writes are taken again and the walk continues after that region, since the completions it ran may have retired writes or noted new ones. CompletePendingCpuWrites is unchanged except that it returns whether it synced. The build's own regions keep the old loop.

Tested

Windows 11, RTX 5070 Ti, WinLibs GCC 15.2.0 r7, Release.

  • New agc_space_pending_writes (tests/Recorder.cpp):

    • for pending writes in the open batch and in a batch in flight (overlapping, adjacent and disjoint ones), every range of a sorted list is listed by OverlappedRanges exactly when PendingWriteOverlaps reports it, from several starting indices;
    • over a real cached address space, a build completes a label pending inside one of the space's ranges, does not complete one in a gap between them, completes two labels in two batches where the first sync leaves the second, and completes a label that a completion action notes while the first sync runs.
    • Each of these fails when the change is mutated: an inclusive overlap test, ignoring the start index, leaving out the open or the in-flight writes, skipping the walk, stopping after the first sync, and keeping the stale candidates after a sync.
  • Full ctest (Windows, RTX 5070 Ti): 1097 of 1098 pass, 8 skipped. agc_driver_wave32_subgroup fails (thread 0 s_bcnt1 of a VCC compare is 0, expected 8) and fails the same way here with this change reverted.

  • python tools/check_conventions.py --base upstream/main passes.

  • CI on the fork, same steps as build.yml (run 38451464704): Linux and Windows pass, agc_space_pending_writes included. The first Linux attempt lost guest_ampr_threads (mprotect overlaps host-only guest thread stack pages, nothing this change touches); the rerun passed.

  • The Pathless (PPSA01825, 02.000.000), main menu, main 61b8742 + fix(libScePlayGo): answer chunk ids from the package layout #4491 (needed to reach the menu then; merged since), APS5_TRACE_SECONDS window of 12 s, warm caches, 5 valid runs per side alternated base/change (runs lost to the Wwise crash of Windows: resuming a write fault overwrites the guest red zone when AVX state is live #361 repeated):

    frame time FPS Resources.Complete on queue worker 0x0
    main 111.9 ± 5.1 ms 8.95 ± 0.42 28.6 ± 1.6 ms per frame
    this PR 100.1 ± 3.6 ms 10.00 ± 0.36 18.1 ± 1.6 ms per frame

    The change was faster in each of the five base/change pairs (by 5.3 to 15.4 ms). One APS5_PROFILE_DRAW run per side: the draws' upload phase went from 827 ms to 44 ms per 10 s (8.4 to 0.4 ms per frame, 189 to 9.5 µs per address-based build), and CompletePendingCpuWrites from 4.7 % to 0.1 % of the worker's samples; the zones the change does not touch stay within run-to-run noise. The rest of the frame-time difference (about 3 ms above the upload phase) I count as noise: the base runs drifted from 104 to 116 ms during the batch.
    Summary lines of the ten runs, main first (commit is the local merge of main 61b8742 + fix(libScePlayGo): answer chunk ids from the package layout #4491, 60c13b89eb20, and the same with this PR, 1e9e0534448d); the ten traces are in the attached zip.

    aps5-summary.jsonl
    {"commit":"60c13b89eb20","dirty":false,"env":{"APS5_TRACE_DIR":"<path>","APS5_TRACE_SECONDS":"75:12"},"gpu":"NVIDIA GeForce RTX 5070 Ti","driver":"NVIDIA 591.86","os":"Windows 10.0.26300","resolution":"2560x1440","window":{"spec":"seconds 75:12","first_present":1154,"ms":12059.495},"presents":116,"frame_ms":{"p50":102.378,"p90":127.644,"p99":145.705,"max":146.311},"gpu_busy_ms_per_present":30.490,"worker_busy_pct":{"queue worker 0x20":8.814,"queue worker 0x0":99.418},"top_phases_ms_per_frame":{"Driver.Packet":105.710,"Driver.Packet.draw":85.243,"Driver.Draw":85.117,"Driver.Draw.draw_and_resource_release":71.088,"Graphics.Draw":70.972,"Graphics.Draw.draw_and_resource_release":60.172,"Resources.Complete":26.729,"Driver.Packet.dispatch":12.116},"trace_dropped":false,"trace":"aps5-trace-60c13b89eb20-23104.json.gz"}
    {"commit":"60c13b89eb20","dirty":false,"env":{"APS5_TRACE_DIR":"<path>","APS5_TRACE_SECONDS":"75:12"},"gpu":"NVIDIA GeForce RTX 5070 Ti","driver":"NVIDIA 591.86","os":"Windows 10.0.26300","resolution":"2560x1440","window":{"spec":"seconds 75:12","first_present":1114,"ms":12038.136},"presents":109,"frame_ms":{"p50":115.757,"p90":132.226,"p99":139.853,"max":145.986},"gpu_busy_ms_per_present":31.577,"worker_busy_pct":{"queue worker 0x0":99.380,"queue worker 0x20":7.794},"top_phases_ms_per_frame":{"Driver.Packet":111.911,"Driver.Packet.draw":90.746,"Driver.Draw":90.616,"Driver.Draw.draw_and_resource_release":75.558,"Graphics.Draw":75.440,"Graphics.Draw.draw_and_resource_release":64.200,"Resources.Complete":27.998,"Driver.Packet.dispatch":12.222},"trace_dropped":false,"trace":"aps5-trace-60c13b89eb20-23968.json.gz"}
    {"commit":"60c13b89eb20","dirty":false,"env":{"APS5_TRACE_DIR":"<path>","APS5_TRACE_SECONDS":"75:12"},"gpu":"NVIDIA GeForce RTX 5070 Ti","driver":"NVIDIA 591.86","os":"Windows 10.0.26300","resolution":"2560x1440","window":{"spec":"seconds 75:12","first_present":1129,"ms":12010.656},"presents":106,"frame_ms":{"p50":118.603,"p90":138.749,"p99":156.054,"max":159.533},"gpu_busy_ms_per_present":31.601,"worker_busy_pct":{"queue worker 0x0":99.409,"queue worker 0x20":10.023},"top_phases_ms_per_frame":{"Driver.Packet":117.451,"Driver.Packet.draw":93.830,"Driver.Draw":93.697,"Driver.Draw.draw_and_resource_release":78.523,"Graphics.Draw":78.406,"Graphics.Draw.draw_and_resource_release":67.224,"Resources.Complete":29.535,"Driver.Packet.dispatch":14.214},"trace_dropped":false,"trace":"aps5-trace-60c13b89eb20-40800.json.gz"}
    {"commit":"60c13b89eb20","dirty":false,"env":{"APS5_TRACE_DIR":"<path>","APS5_TRACE_SECONDS":"75:12"},"gpu":"NVIDIA GeForce RTX 5070 Ti","driver":"NVIDIA 591.86","os":"Windows 10.0.26300","resolution":"2560x1440","window":{"spec":"seconds 75:12","first_present":1165,"ms":12022.881},"presents":104,"frame_ms":{"p50":119.809,"p90":138.123,"p99":151.670,"max":151.904},"gpu_busy_ms_per_present":30.993,"worker_busy_pct":{"queue worker 0x0":99.397,"queue worker 0x20":7.830},"top_phases_ms_per_frame":{"Driver.Packet":117.171,"Driver.Packet.draw":96.447,"Driver.Draw":96.319,"Driver.Draw.draw_and_resource_release":82.082,"Graphics.Draw":81.970,"Graphics.Draw.draw_and_resource_release":71.490,"Resources.Complete":30.577,"Driver.Packet.dispatch":11.625},"trace_dropped":false,"trace":"aps5-trace-60c13b89eb20-32004.json.gz"}
    {"commit":"60c13b89eb20","dirty":false,"env":{"APS5_TRACE_DIR":"<path>","APS5_TRACE_SECONDS":"75:12"},"gpu":"NVIDIA GeForce RTX 5070 Ti","driver":"NVIDIA 591.86","os":"Windows 10.0.26300","resolution":"2560x1440","window":{"spec":"seconds 75:12","first_present":1145,"ms":12007.479},"presents":103,"frame_ms":{"p50":120.530,"p90":144.194,"p99":173.763,"max":196.046},"gpu_busy_ms_per_present":32.180,"worker_busy_pct":{"queue worker 0x0":99.388,"queue worker 0x20":8.857},"top_phases_ms_per_frame":{"Driver.Packet":119.781,"Driver.Packet.draw":96.910,"Driver.Draw":96.773,"Driver.Draw.draw_and_resource_release":81.119,"Graphics.Draw":80.992,"Graphics.Draw.draw_and_resource_release":69.907,"Resources.Complete":30.033,"Driver.Packet.dispatch":13.639},"trace_dropped":false,"trace":"aps5-trace-60c13b89eb20-7356.json.gz"}
    {"commit":"1e9e0534448d","dirty":false,"env":{"APS5_TRACE_DIR":"<path>","APS5_TRACE_SECONDS":"75:12"},"gpu":"NVIDIA GeForce RTX 5070 Ti","driver":"NVIDIA 591.86","os":"Windows 10.0.26300","resolution":"2560x1440","window":{"spec":"seconds 75:12","first_present":1148,"ms":12028.253},"presents":122,"frame_ms":{"p50":98.490,"p90":118.462,"p99":126.636,"max":127.788},"gpu_busy_ms_per_present":31.245,"worker_busy_pct":{"queue worker 0x0":99.318,"queue worker 0x20":8.683},"top_phases_ms_per_frame":{"Driver.Packet":100.239,"Driver.Packet.draw":80.672,"Driver.Draw":80.555,"Driver.Draw.draw_and_resource_release":66.321,"Graphics.Draw":66.218,"Graphics.Draw.draw_and_resource_release":54.846,"Resources.Complete":17.755,"Driver.Packet.dispatch":11.430},"trace_dropped":false,"trace":"aps5-trace-1e9e0534448d-30124.json.gz"}
    {"commit":"1e9e0534448d","dirty":false,"env":{"APS5_TRACE_DIR":"<path>","APS5_TRACE_SECONDS":"75:12"},"gpu":"NVIDIA GeForce RTX 5070 Ti","driver":"NVIDIA 591.86","os":"Windows 10.0.26300","resolution":"2560x1440","window":{"spec":"seconds 75:12","first_present":1200,"ms":12073.413},"presents":127,"frame_ms":{"p50":90.506,"p90":115.425,"p99":135.842,"max":143.176},"gpu_busy_ms_per_present":31.072,"worker_busy_pct":{"queue worker 0x0":99.337,"queue worker 0x20":8.149},"top_phases_ms_per_frame":{"Driver.Packet":95.458,"Driver.Packet.draw":76.653,"Driver.Draw":76.535,"Driver.Draw.draw_and_resource_release":61.938,"Graphics.Draw":61.833,"Graphics.Draw.draw_and_resource_release":50.627,"Resources.Complete":16.775,"Driver.Draw.shader_compile_and_link":11.416},"trace_dropped":false,"trace":"aps5-trace-1e9e0534448d-40848.json.gz"}
    {"commit":"1e9e0534448d","dirty":false,"env":{"APS5_TRACE_DIR":"<path>","APS5_TRACE_SECONDS":"75:12"},"gpu":"NVIDIA GeForce RTX 5070 Ti","driver":"NVIDIA 591.86","os":"Windows 10.0.26300","resolution":"2560x1440","window":{"spec":"seconds 75:12","first_present":1157,"ms":12023.160},"presents":121,"frame_ms":{"p50":94.971,"p90":122.183,"p99":135.668,"max":201.477},"gpu_busy_ms_per_present":30.257,"worker_busy_pct":{"queue worker 0x0":99.346,"queue worker 0x20":9.364},"top_phases_ms_per_frame":{"Driver.Packet":101.452,"Driver.Packet.draw":80.428,"Driver.Draw":80.305,"Driver.Draw.draw_and_resource_release":65.921,"Graphics.Draw":65.814,"Graphics.Draw.draw_and_resource_release":54.781,"Resources.Complete":18.745,"Driver.Packet.dispatch":11.777},"trace_dropped":false,"trace":"aps5-trace-1e9e0534448d-5576.json.gz"}
    {"commit":"1e9e0534448d","dirty":false,"env":{"APS5_TRACE_DIR":"<path>","APS5_TRACE_SECONDS":"75:12"},"gpu":"NVIDIA GeForce RTX 5070 Ti","driver":"NVIDIA 591.86","os":"Windows 10.0.26300","resolution":"2560x1440","window":{"spec":"seconds 75:12","first_present":1146,"ms":12024.201},"presents":116,"frame_ms":{"p50":101.545,"p90":131.275,"p99":139.565,"max":141.801},"gpu_busy_ms_per_present":30.118,"worker_busy_pct":{"queue worker 0x0":99.358,"queue worker 0x20":10.351},"top_phases_ms_per_frame":{"Driver.Packet":106.962,"Driver.Packet.draw":84.570,"Driver.Draw":84.451,"Driver.Draw.draw_and_resource_release":69.939,"Graphics.Draw":69.833,"Graphics.Draw.draw_and_resource_release":58.878,"Resources.Complete":21.015,"Driver.Packet.dispatch":13.657},"trace_dropped":false,"trace":"aps5-trace-1e9e0534448d-22632.json.gz"}
    {"commit":"1e9e0534448d","dirty":false,"env":{"APS5_TRACE_DIR":"<path>","APS5_TRACE_SECONDS":"75:12"},"gpu":"NVIDIA GeForce RTX 5070 Ti","driver":"NVIDIA 591.86","os":"Windows 10.0.26300","resolution":"2560x1440","window":{"spec":"seconds 75:12","first_present":1173,"ms":12047.155},"presents":116,"frame_ms":{"p50":102.155,"p90":125.793,"p99":134.683,"max":165.122},"gpu_busy_ms_per_present":30.303,"worker_busy_pct":{"queue worker 0x0":99.312,"queue worker 0x20":10.192},"top_phases_ms_per_frame":{"Driver.Packet":106.640,"Driver.Packet.draw":83.417,"Driver.Draw":83.273,"Driver.Draw.draw_and_resource_release":66.882,"Graphics.Draw":66.761,"Graphics.Draw.draw_and_resource_release":53.328,"Resources.Complete":17.979,"Driver.Packet.dispatch":13.561},"trace_dropped":false,"trace":"aps5-trace-1e9e0534448d-33268.json.gz"}
    
  • LEGO Horizon Adventures (PPSA14632, 01.004.000), intro, main 61b8742, 3 valid runs per side: 127.7 ± 1.6 against 128.9 ± 2.2 ms per frame, no change (2 address-based builds per frame).

  • Hades (PPSA03355), ANIMAL WELL (PPSA22520), Balatro (PPSA21402) and Dead Cells (PPSA15552), main 61b8742 against this PR, 90 s each with 8 dumped frames: the same points reached (the first three keep running at their menus, Dead Cells stops at the same NGS2: rack id 0x4001 of Dead Cells (PPSA15552): NGS2 rack id 0x4001 is not implemented #3553), the same set of [gpu] log lines, ANIMAL WELL's frames identical; Hades's and Balatro's frames differ only where their animations (Hades's logo fade-in, Balatro's animated menu) were when dumped, which I checked by eye.

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
  • Unblocks: none
  • GPUs tested: NVIDIA GeForce RTX 5070 Ti, driver 591.86, Windows 11
  • AI-assisted: yes

space-pending-writes-traces.zip

@CnRJay CnRJay left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks correct to me, and the measurement is solid: five alternated pairs, summary lines and traces, with Resources.Complete as the phase that moved.

I checked the equivalence the description relies on. CompletePendingCpuWrites returns early when !recorder->PendingWriteOverlaps(address, bytes) (ShaderResources.cpp, after the writer and label pre-checks). So skipping base regions that no pending write overlaps drops only calls that did nothing. Re-taking the pending writes after a sync and continuing past that region handles writes that the completion retired or noted. The mutation tests pin both.

One small follow-up idea, not needed for this PR: each remaining call still repeats the pre-checks that don't depend on the region (DrawCopiedWriters() copy, PendingCompletionLabels(), the copiedWriters emptiness test). Hoisting them out of the UploadFinish walk, and skipping the walk entirely when there are no copied writers, draw writers or labels, would remove the PendingWriteRanges snapshot in the common case too.

@oneandonlydean oneandonlydean self-assigned this Oct 14, 2026
@github-actions github-actions Bot added the needs rebase Conflicts with main, rebase needed label Oct 14, 2026
@winstrex
winstrex force-pushed the perf/space-pending-writes branch from 1eedbd6 to 492d61b Compare October 14, 2026 06:27
@winstrex
winstrex force-pushed the perf/space-pending-writes branch from 492d61b to 100ef2e Compare October 14, 2026 06:50
@github-actions github-actions Bot added needs rebase Conflicts with main, rebase needed and removed needs rebase Conflicts with main, rebase needed labels Oct 14, 2026
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@winstrex
winstrex force-pushed the perf/space-pending-writes branch from 100ef2e to 1ce75da Compare October 14, 2026 17:35
@github-actions github-actions Bot removed the needs rebase Conflicts with main, rebase needed label Oct 14, 2026
@oneandonlydean
oneandonlydean merged commit 5a605d6 into boykopovar:main Oct 14, 2026
8 checks passed
@winstrex
winstrex deleted the perf/space-pending-writes branch October 14, 2026 19:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agc libSceAgc* build

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants