Skip to content

perf(agc): memoize host import run bounds per lease snapshot - #6951

Open
alfalfa-star wants to merge 1 commit into
boykopovar:mainfrom
alfalfa-star:perf/agc-span-mirror
Open

alfalfa-star wants to merge 1 commit into
boykopovar:mainfrom
alfalfa-star:perf/agc-span-mirror

Conversation

@alfalfa-star

@alfalfa-star alfalfa-star commented Oct 16, 2026 •

Copy link
Copy Markdown
Contributor

What

Every host import lookup walks the lease to find the run of registry ranges around an address (importRun): from the containing range out to both ends of the run, building a vector of the ranges each time. Kena: Bridge of Spirits (PPSA01802) looks runs up thousands of times per frame (HostImportFor, sameRange in the import refresh, importAllocation), and the walk is one of the largest parts of the draw-preparation time.

The run bounds are now memoized per thread. The key is the lease snapshot (its range array and size), the index of the containing range and the import window. Before use, the memo is checked against the first and last range of the stored run, so a new lease or a changed registry misses it. sameRange and the whole-run lookup in importAllocation compare the bounds directly, without building the run. APS5_NO_RUN_MEMO=1 turns the memo off.

Tested

AMD Radeon RX 9070 XT (RADV GFX1201), Mesa 26.2.4, Arch Linux (kernel 7.2.9), Kena: Bridge of Spirits (PPSA01802).

  • Kena forest title scene on main f8b22ed, no input, APS5_TRACE_SECONDS=100:30, three rotated rounds. Comparable runs: main 216 / 254 presents, this PR 236 / 295 (one run of each reached a lighter stretch at 48 ms GPU busy and is left out; summary lines in the review reply). An earlier batch had the memo alone at 239 / 235 / 248 against main 191 / 227 / 208, about +15 %.
  • New agc_driver_graphics case: a range registered next to an imported one, after which the next build's import must cover both.
  • ctest -R '^agc_' six times with -j16: 629 of 630 pass each time; agc_driver_tessellation segfaults on main too.

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 (performance of PPSA01802)
  • GPUs tested: AMD Radeon RX 9070 XT (RADV GFX1201), Mesa 26.2.4, Arch Linux (kernel 7.2.9)
  • AI-assisted: yes

🤖 Generated with Claude Code

@github-actions

github-actions Bot commented Oct 16, 2026 •

Copy link
Copy Markdown

Other open pull requests touch the same code. Merging one will break the other, or both implement the same thing. If this one builds on them, list them in Depends on.

Pull request Conflicting files (lines on main) Same exports Same new files
PR 6457 core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp L1182-1185, L1201-1205, L1273, L1408
PR 6479 core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp L1182-1185, L1201-1205, L1273, L1408
PR 6730 core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp L1182-1185, L1201-1205, L1273, L1408

Some comparisons could not be completed because a pull request conflicts with the base branch. Review these shared files manually before concluding that there is no overlap.

Pull request Shared files not compared
PR 1191 core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp
PR 1324 core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp
PR 2434 core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp
PR 4448 core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp
core/libs/prx/libSceAgcDriver/tests/BdaResources.cpp
PR 4449 core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp
PR 5021 core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp
core/libs/prx/libSceAgcDriver/tests/BdaResources.cpp
PR 5372 core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp
core/libs/prx/libSceAgcDriver/tests/BdaResources.cpp
PR 6146 core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp
PR 6177 core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp
PR 6185 core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp
PR 6186 core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp
PR 6197 core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp
core/libs/prx/libSceAgcDriver/tests/BdaResources.cpp
PR 6292 core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp
PR 6548 core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp
PR 6615 core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp
PR 6628 core/libs/prx/libSceAgcDriver/tests/BdaResources.cpp
PR 6636 core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp
core/libs/prx/libSceAgcDriver/tests/BdaResources.cpp
PR 6786 core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp
PR 6947 core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp
PR 7154 core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp
PR 7201 core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp
core/libs/prx/libSceAgcDriver/tests/BdaResources.cpp
PR 7233 core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp
PR 7292 core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp
core/libs/prx/libSceAgcDriver/tests/BdaResources.cpp
PR 7332 core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp
core/libs/prx/libSceAgcDriver/tests/BdaResources.cpp
PR 7473 core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp
PR 7514 core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp
PR 7618 core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp
PR 7642 core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp

@github-actions github-actions Bot added libc agc libSceAgc* labels Oct 16, 2026

@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.

Default-off span mirror plus two changes that are on by default, and no measurement of any of them.

  • The incremental import refresh (APS5_NO_INCREMENTAL_IMPORT_REFRESH) and the run memo (APS5_NO_RUN_MEMO) change behaviour for every title on every host, and the registry change journal decides which imports get refreshed. A missed journal entry leaves an import pointing at a remapped range. Say what bounds the journal (what happens when it wraps, or when the registry changes during a refresh), and add a test where an allocation is unmapped and remapped between two refreshes and the next draw must see the new pages.
  • The udmabuf path is Linux-only and was tested on RADV only. Windows runs the same refresh and run-memo code; please run the agc_* suite there and say so.
  • APS5_SPAN_NO_FLUSH=1 skips flushing pending GPU writes over the span before it is served. Leave that out of this PR, or show a title that needs it. A switch whose only effect is to serve stale data is not a performance feature.
  • 640 lines across the import code, GuestAllocations and the mirror in one PR; the mirror (off by default) and the refresh changes (on by default) are two topics and should be two PRs, so the default-on part can be measured and merged on its own.
  • "About 24 fps in our local test build" and "a measurement will follow" do not meet the Performance report form. Please add the summary lines of main and this PR, same scene in PPSA01802, once with APS5_SPAN_MIRROR=1 and once without, no APS5_PROFILE_*.

@parl0v

parl0v commented Oct 17, 2026

Copy link
Copy Markdown
Contributor

Data point from a CPU-bound scene on Windows: RTX 3060 12 GB (NVIDIA 617.42), i5-12400F, Windows 10 19045, MinGW GCC Release. Minecraft Dungeons II (PPSA16064 01.100.100), title screen, no input, 200 s rows, APS5_PROFILE_DRAW=1 APS5_PROFILE_GPU=1; main af0fff7 vs main + this PR, interleaved with five other PRs in one batch, two rounds. FPS is the mean of the last 10 [present] windows, GPU busy and idle gaps per frame over the last 8. This scene is CPU-bound on this machine (44-47 ms of idle gaps per frame).

libs FPS GPU busy idle gaps
main 6.61 / 6.76 105 / 104 ms 46 / 44 ms
+ this PR (c7a43cc) 6.58 / 6.62 (-1.3 %) 106 / 104 ms 46 / 46 ms

Within the run-to-run spread (main itself moved 6.61 -> 6.76 between rounds), no regression, 0 fatal.

AI-assisted: yes (Claude Code)

@milescoviello

Copy link
Copy Markdown
Contributor

A profile from another title where this path is the largest committer cost; it doesn't measure this PR. Metal Gear Solid 4 (PPSA18194), Ubuntu 24.04, i7-13700K, RTX 4070 Ti (595.99.02), Release, pad input. main 0dc1562 plus what MGS4 needs here (#6814, #6842, #6237) and #6917. In this scene the queue-0 committer holds the GPU mutex for about 7.9 s of every 10 s (APS5_PROFILE_DRAW, 124k commits per 10 s), so its per-draw CPU time sets the frame rate.

perf record -F 199 --call-graph dwarf for 6 s at 225 s; of the 555 samples with DrawPipeline::run on the stack (inclusive):

Frame Share of committer samples
HostImportFor 20.4%
refreshImports 19.8%
InPlaceDrawInput (from prepareDrawInputs) 19.6%
sameRange(HostImport, Lease) 18.4%
importRun(Lease, ...) 16.0% (7.2% self)
GuestAllocations::Lease::held 5.6% self

So most of it is the import refresh re-walking runs on each draw's input lookup, which your incremental refresh and per-lease run memo target. I haven't run this PR on MGS4 yet. I can if that's useful, but MGS4 needs those three PRs on top.

AI-assisted: yes (Claude Code)

Every import lookup walked the lease to find the run of registry ranges
around an address. The bounds are now memoized per thread, keyed by the
lease snapshot, the containing range and the import window, and checked
against the first and last range before use; sameRange and the whole-run
lookup in importAllocation use the bounds without building the run.
APS5_NO_RUN_MEMO=1 turns the memo off. The test registers a range next
to an imported one and requires the next build's import to cover both.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@alfalfa-star alfalfa-star changed the title perf(agc): mirror large read-only buffer spans and refresh imports incrementally perf(agc): memoize host import run bounds per lease snapshot Oct 17, 2026
@alfalfa-star

Copy link
Copy Markdown
Contributor Author

Thanks. I measured each part on its own, and the PR is now one topic. I force-pushed it down to the part that is on by default and measurably faster: the run memo (ae409e197, on current main). The title and description changed accordingly. What happened to the rest:

Kena (PPSA01802), forest title scene, no input. main f8b22ed + each part, warm cache, APS5_TRACE_SECONDS=100:30, three rotated rounds per batch. Linux 7.2.9, RX 9070 XT, RADV Mesa 26.2.4.

build (one batch) presents in 30 s
main 191 / 227 / 208
run memo + incremental refresh 234 / 243 / 244
the same with APS5_NO_INCREMENTAL_IMPORT_REFRESH=1 239 / 235 / 248
the whole previous PR, mirror off 344 / 323 / 314
the whole previous PR, APS5_SPAN_MIRROR=1 (another batch) the same as mirror off
  • Incremental refresh and journal: they added nothing on top of the memo, so I removed them. That also answers the journal questions by deletion. For the record: the journal was bounded at 8192 entries and fell back to the full walk once a refresh's starting generation was older than the oldest entry. It also flaked spaceReadList twice in six parallel ctest runs, which neither main nor this version does. On the remap test you asked for: an unmap or remap of an imported range can't leave a stale import, journal or not, because the pin waiter retires imports over a range before a mutation changes it. What the refresh does need to see is a run that grows next to an unchanged import. The new test covers that case and failed when the incremental refresh skipped its walk.
  • Span mirror: no measurable gain, so it's out, with APS5_SPAN_NO_FLUSH.
  • Fragmented-backing import: most of the difference between the memo and the whole previous PR (+36 %) came from this part. It imported a window whose direct-memory backing is scattered as one UDMABUF_CREATE_LIST, and it was on by default, which the old description didn't make clear. It's out of this PR too. feat(libSceAgcDriver): import scattered direct memory as a dma-buf run list and probe its coherence #4449 already implements the same idea (a dma-buf run list for scattered direct memory), so I won't open a competing PR. I'll post the Kena numbers there instead.

Windows: this version touches no Linux-only code. I can't run the agc_* suite on Windows here; the memo is plain C++ on the lease, the same on both.

Measurement of this version (rotated with another build; one run of each reached a lighter stretch with 48 ms GPU busy instead of about 80 and isn't comparable):

build presents in 30 s p50 frame ms GPU busy ms per present
main 216 / 325* / 254 130.2 / 87.8 / 117.3 78.2 / 48.4 / 82.2
this PR 321* / 236 / 295 84.5 / 126.4 / 99.9 48.7 / 79.6 / 80.4
Summary lines
{"commit":"f8b22edfb7f5","dirty":false,"env":{"APS5_TRACE_DIR":"<path>","APS5_TRACE_SECONDS":"100:30"},"gpu":"AMD Radeon RX 9070 XT (RADV GFX1201)","driver":"radv Mesa 26.2.4-arch1.1","os":"Linux 7.2.9-arch1-1","resolution":"3840x2160","window":{"spec":"seconds 100:30","first_present":771,"ms":30041.458},"presents":216,"frame_ms":{"p50":130.2,"p90":161.713,"p99":275.775,"max":455.902},"gpu_busy_ms_per_present":78.23,"trace":"aps5-trace-f8b22edfb7f5-370363.json.gz"}
{"commit":"f8b22edfb7f5","dirty":true,"env":{"APS5_TRACE_DIR":"<path>","APS5_TRACE_SECONDS":"100:30"},"gpu":"AMD Radeon RX 9070 XT (RADV GFX1201)","driver":"radv Mesa 26.2.4-arch1.1","os":"Linux 7.2.9-arch1-1","resolution":"3840x2160","window":{"spec":"seconds 100:30","first_present":1187,"ms":30077.514},"presents":321,"frame_ms":{"p50":84.548,"p90":115.084,"p99":123.446,"max":210.047},"gpu_busy_ms_per_present":48.711,"trace":"aps5-trace-f8b22edfb7f5-377486.json.gz"}
{"commit":"f8b22edfb7f5","dirty":false,"env":{"APS5_TRACE_DIR":"<path>","APS5_TRACE_SECONDS":"100:30"},"gpu":"AMD Radeon RX 9070 XT (RADV GFX1201)","driver":"radv Mesa 26.2.4-arch1.1","os":"Linux 7.2.9-arch1-1","resolution":"3840x2160","window":{"spec":"seconds 100:30","first_present":1182,"ms":30102.816},"presents":325,"frame_ms":{"p50":87.85,"p90":113.641,"p99":120.253,"max":122.562},"gpu_busy_ms_per_present":48.443,"trace":"aps5-trace-f8b22edfb7f5-388013.json.gz"}
{"commit":"f8b22edfb7f5","dirty":true,"env":{"APS5_TRACE_DIR":"<path>","APS5_TRACE_SECONDS":"100:30"},"gpu":"AMD Radeon RX 9070 XT (RADV GFX1201)","driver":"radv Mesa 26.2.4-arch1.1","os":"Linux 7.2.9-arch1-1","resolution":"3840x2160","window":{"spec":"seconds 100:30","first_present":934,"ms":30053.807},"presents":236,"frame_ms":{"p50":126.393,"p90":129.001,"p99":153.913,"max":155.756},"gpu_busy_ms_per_present":79.603,"trace":"aps5-trace-f8b22edfb7f5-384471.json.gz"}
{"commit":"f8b22edfb7f5","dirty":false,"env":{"APS5_TRACE_DIR":"<path>","APS5_TRACE_SECONDS":"100:30"},"gpu":"AMD Radeon RX 9070 XT (RADV GFX1201)","driver":"radv Mesa 26.2.4-arch1.1","os":"Linux 7.2.9-arch1-1","resolution":"3840x2160","window":{"spec":"seconds 100:30","first_present":1032,"ms":30016.046},"presents":254,"frame_ms":{"p50":117.267,"p90":121.144,"p99":147.852,"max":152.264},"gpu_busy_ms_per_present":82.231,"trace":"aps5-trace-f8b22edfb7f5-395030.json.gz"}
{"commit":"f8b22edfb7f5","dirty":true,"env":{"APS5_TRACE_DIR":"<path>","APS5_TRACE_SECONDS":"100:30"},"gpu":"AMD Radeon RX 9070 XT (RADV GFX1201)","driver":"radv Mesa 26.2.4-arch1.1","os":"Linux 7.2.9-arch1-1","resolution":"3840x2160","window":{"spec":"seconds 100:30","first_present":1115,"ms":30058.106},"presents":295,"frame_ms":{"p50":99.901,"p90":108.835,"p99":124.458,"max":136.9},"gpu_busy_ms_per_present":80.439,"trace":"aps5-trace-f8b22edfb7f5-391491.json.gz"}

The traces are kept locally; I can attach any of them. ctest -R '^agc_' six times with -j16: 629 of 630 pass each time (agc_driver_tessellation segfaults on main too).

AI-assisted: yes (Claude Code)

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* libc perf numbers missing perf pull request without a before and after summary line 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.

4 participants