Skip to content

perf(agc): prefer a pooled buffer of the requested exact size - #8913

Merged
oneandonlydean merged 1 commit into
boykopovar:mainfrom
Celegans12:perf/agc-pool-exact-size-hit
Oct 27, 2026
Merged

oneandonlydean merged 1 commit into
boykopovar:mainfrom
Celegans12:perf/agc-pool-exact-size-hit

Conversation

@Celegans12

@Celegans12 Celegans12 commented Oct 21, 2026 •

Copy link
Copy Markdown
Contributor

What

The pool retains buffers by size class. When a hit's VkBuffer was made for another exact size, Buffer and DeviceBuffer destroy it and bind a new one to the same memory (BindExactBuffer). Since #6763 copies draw inputs on NVIDIA's Windows driver, each draw takes such a buffer per index or vertex range, and Take always hands out the newest slot of the class, whatever its size. In a Yōtei profile on our tree the draw commit thread spent 1.3 s per 120 s in BindExactBuffer and another 0.36 s in the destroys.

  • Take now scans the newest 64 slots of the class for one whose buffer has the requested size, and takes the newest slot as before when none does.
  • Memory class, usage and properties are the same for every slot in the list, so only the choice among equivalent slots changes; least-recently-used eviction keeps its order.

Overlaps: #8748 changes the free-list key a few lines above in Take (textual only).

Tested

  • Windows 11 Pro, MinGW-w64 GCC 15.2.0, -DBUILD_TESTING=ON, RTX 3080 Laptop, based on main 7ab4b2e: ctest 1824/1828. The 4 failures (log_macros, log_macros_timestamps, guest_kqueue, and agc_driver_image_array_window_past_layer_limit, which doesn't compile with MinGW here) fail on main too.
  • New ExactSizeReuse case in agc_buffer_pool: with a 3000-byte and then a 4000-byte buffer released into one class, a 3000-byte request gets the first one back without a rebuild and a 4000-byte request the second. With main's BufferPool it fails: "a pooled buffer of the requested exact size was passed over for a newer one that had to be rebuilt".
  • Linux (WSL, GCC 13.3, lavapipe), CI's job on the merge with main: 1762/1782; the same 20 fail on main (WSL has no userfaultfd write watching).
  • Speed, not isolated for this PR: the only number I have comes from our own Ghost of Yōtei (PPSA26344) tree, not main, with perf(agc): skip the queued-draw read check when a storage flush stores nothing #8911, perf(agc): give small device-local buffers a pool tier of their own #8912 and perf(agc): prefer a pooled buffer of the requested exact size #8913 switched on and off together in one in-run open-world A/B (9+9 phases): 6.17 vs 5.16 presents per 10 s (paired -0.95 +- 0.20 SE) for the combined set. This PR's own counter in that run: 436k vs 253k exact-size hits, 425k vs 493k rebinds to another size. The main-vs-change summary lines, traces and trace_digest.py output for the perf label are still owed.
  • main vs this change on the same machine (RTX 3080 Laptop, driver 610.62, Windows 11), Ghost of Yōtei (PPSA26344 v1.008) title menu from the same save with no input, warm caches, interleaved runs in one session, perf-report protocol (only APS5_TRACE_FRAMES=150:60, no APS5_PROFILE_*): main a5a2c94 against main a5a2c94 + this PR. Frame p50 131.9 ms (3 runs) against 132.7 ms (2 runs), GPU busy 77.7 against 81.5 ms per present. That's inside the run-to-run spread (main's three runs: 128.2-135.1 ms), as expected for a scene that doesn't exercise this path; our open-world numbers, from our own tree, are in the comments. Traces and tools/trace_digest.py output per run: pl-main-1, pl-main-2, pl-main-3, pl-8913-1, pl-8913-2.
aps5-summary.jsonl, pl-main-3 and pl-8913-2 (the run closest to each side's mean; `title` written as `ō`, since the summary writer double-encodes it and GitHub mangles that escape)
{"commit":"a5a2c946a3a9","dirty":false,"env":{"APS5_NO_CLEAR_HAND_OVER":"1","APS5_NO_DEFERRED_SETTLES":"1","APS5_NO_PAD_INPUT":"1","APS5_NO_PIPELINE_JOURNAL":"1","APS5_NO_RESIDENT_LINEAR":"1","APS5_PAD_INPUT_FILE":"<path>","APS5_RESOURCE_CACHE_ENTRIES":"1024","APS5_TRACE_FRAMES":"150:60"},"gpu":"NVIDIA GeForce RTX 3080 Laptop GPU","driver":"NVIDIA 610.62","os":"Windows 10.0.26200","cpu":"11th Gen Intel(R) Core(TM) i9-11900H @ 2.50GHz","cpu_threads":16,"memory_mib":32469,"title_id":"PPSA26344","title":"Ghost of Yōtei","content_version":"01.008.000","resolution":"3840x2160","window":{"spec":"frames 150:60","first_present":150,"ms":11388.409},"presents":60,"frame_ms":{"p50":132.237,"p90":378.788,"p99":2488.458,"max":2488.458},"gpu_busy_ms_per_present":76.739,"worker_busy_pct":{"queue worker 0x0":61.336,"queue worker 0x20":12.709,"queue worker 0x52":10.897,"queue worker 0x53":12.001,"queue worker 0x30":43.438,"queue worker 0x38":50.980,"queue worker 0x48":3.931},"top_phases_ms_per_frame":{"Driver.Packet":363.867,"Shader.PrepareArtifact":329.176,"Driver.Packet.dispatch":163.759,"Driver.Packet.memory_wait":130.658,"Driver.Dispatch":116.659,"Driver.Packet.prepare_memory":46.855,"GpuMutex.wait.dispatch":32.057,"Shader.Register":29.105},"trace_dropped":false,"trace":"aps5-trace-a5a2c946a3a9-16452.json.gz"}
{"commit":"ef29bab589cf","dirty":false,"env":{"APS5_NO_CLEAR_HAND_OVER":"1","APS5_NO_DEFERRED_SETTLES":"1","APS5_NO_PAD_INPUT":"1","APS5_NO_PIPELINE_JOURNAL":"1","APS5_NO_RESIDENT_LINEAR":"1","APS5_PAD_INPUT_FILE":"<path>","APS5_RESOURCE_CACHE_ENTRIES":"1024","APS5_TRACE_FRAMES":"150:60"},"gpu":"NVIDIA GeForce RTX 3080 Laptop GPU","driver":"NVIDIA 610.62","os":"Windows 10.0.26200","cpu":"11th Gen Intel(R) Core(TM) i9-11900H @ 2.50GHz","cpu_threads":16,"memory_mib":32469,"title_id":"PPSA26344","title":"Ghost of Yōtei","content_version":"01.008.000","resolution":"3840x2160","window":{"spec":"frames 150:60","first_present":150,"ms":11646.443},"presents":60,"frame_ms":{"p50":132.185,"p90":393.353,"p99":2677.602,"max":2677.602},"gpu_busy_ms_per_present":79.738,"worker_busy_pct":{"queue worker 0x0":60.693,"queue worker 0x20":12.172,"queue worker 0x52":9.480,"queue worker 0x53":11.304,"queue worker 0x30":42.585,"queue worker 0x48":4.456,"queue worker 0x38":50.368},"top_phases_ms_per_frame":{"Driver.Packet":365.333,"Shader.PrepareArtifact":355.582,"Driver.Packet.dispatch":160.389,"Driver.Packet.memory_wait":127.861,"Driver.Dispatch":112.743,"Driver.Packet.prepare_memory":48.419,"GpuMutex.wait.flush":30.404,"GpuMutex.wait.dispatch":30.058},"trace_dropped":false,"trace":"aps5-trace-ef29bab589cf-21524.json.gz"}
- Not tested: AMD, Intel, console behaviour.

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
  • GPUs tested: NVIDIA GeForce RTX 3080 Laptop GPU, driver 610.62, Windows 11 Pro; lavapipe (Mesa 25.2.8, WSL Ubuntu 24.04)
  • AI-assisted: yes

A pooled buffer is retained by size class, and a hit whose VkBuffer was
created for another exact size destroys it and binds a new one to the
same memory (BindExactBuffer). Copying draw inputs on NVIDIA's Windows
driver takes one such buffer per index or vertex range per draw, so most
hits in that path pay a vkDestroyBuffer, vkCreateBuffer and bind.

Take now scans the newest 64 slots of the class for one whose buffer has
the requested size and takes the newest slot as before when none has.
The memory class, usage and properties are the same, so only the choice
among equivalent slots changes.

AI-assisted: yes
@github-actions github-actions Bot added perf Performance change perf numbers missing perf pull request without a before and after summary line labels Oct 21, 2026
@github-actions

Copy link
Copy Markdown

This pull request is labeled perf, so it needs the summary line of a capture on main and one with the change, from the same title, scene and machine, as the Performance report form asks. Please paste both aps5-summary.jsonl lines, attach the two traces and add the output of tools/trace_digest.py.

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

The Take change preserves the existing newest-slot fallback and removes only the selected slot; the regression exercises both exact sizes and checks the buffers are reused. I ran python3 tools/check_conventions.py --base upstream/main --head HEAD and git diff --check upstream/main...HEAD; both pass. I could not run agc_buffer_pool from this Linux checkout because there is no matching configured test binary here. One reporting gap: the performance result combines this PR with #8911 and #8912, while CONTRIBUTING.md and the perf-label bot ask for matched main/change trace summaries and both trace artifacts. Please attach the required artifacts or keep the quantified claim clearly scoped to the combined set.

@github-actions

github-actions Bot commented Oct 21, 2026 •

Copy link
Copy Markdown

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 7642 core/libs/prx/libSceAgcDriver/Graphics/include/BufferPool.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/BufferPool.cpp
PR 9508 core/libs/prx/libSceAgcDriver/Graphics/include/BufferPool.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/BufferPool.cpp
PR 10194 core/libs/prx/libSceAgcDriver/Graphics/include/BufferPool.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/BufferPool.cpp
core/libs/prx/libSceAgcDriver/tests/BufferPool.cpp

@Celegans12

Copy link
Copy Markdown
Contributor Author

@MarcZX10 thanks for running the conventions check and git diff --check, and for reading the fallback path in Take.

You're right that the speed result mixed this PR with #8911 and #8912. It came from our own Yōtei tree, not main, with all three switched together. I've changed the description to say so plainly. The exact-size hits vs rebinds counts are the only part specific to this change. The main/change summary lines, both traces and trace_digest.py output are still owed. I'll attach them when I have a clean capture, but I can't promise a date.

@github-actions github-actions Bot added the agc libSceAgc* label Oct 21, 2026
@EdouardSence

Copy link
Copy Markdown
Contributor

AMD / Linux (RX 9070 XT, RADV) A/B of #8911 + #8912 + #8913 together on ASTRO BOT: neutral there (no change, no regression). Numbers and trace digest: #8911 (comment)


Tested and written by Claude (AI assistant) on a contributor's machine.
AI-assisted: yes

@Celegans12

Copy link
Copy Markdown
Contributor Author

Here's this PR measured alone, as asked on #8912. Plain main can't take Ghost of Yōtei to the open world (the loading screen stops presenting after about 160 s and the game later aborts on a blend into a 10_11_11 unorm target main doesn't support), so this is our tree (our local merge with main of 2026-10-21) with only APS5_NO_EXACT_SIZE_REUSE flipped every 60 s in one run.

The result is mixed. Presents lean its way: 7.23 per 10 s with the exact-size scan vs 6.78 without, paired -0.34 +- 0.28 with it off, about 1.2 SE. But committer time per commit was a little lower with the scan off (258 vs 234 us, paired -19 +- 15 us), so the scan itself isn't free. It does what it says on the buffers: per phase about 490k pooled hits of the exact size with it on against 365k off, and rebinds to another size drop from about 733k to 476k.

I also tried a boot-scene pair on plain main, but those captures turned out to be pipeline-cold on this machine, so I'm not quoting them. Given milescoviello's point about #8888's size classes, I'd rerun this after #8888 lands rather than tune the scan now.

Details

Run pb-8913, our local merge with main 557682c, warm caches (NVIDIA pipeline cache readable, dictionary checked before the run), web-teshio-b save, 1,300 s cap, APS5_AB=60:APS5_NO_EXACT_SIZE_REUSE from present 760, RTX 3080 Laptop (610.62), Windows 11. B = the switch set, i.e. this PR's change off.

phases (presents: begin / settled / end; clean reports present/gputime/draw/syncwait):
  A  0     760 /    770 /    823  2/0/4/3     9.04 presents/10 s
  B  1     823 /    825 /    856  4/0/4/4     5.39 presents/10 s
  A  2     856 /    858 /    888  4/0/4/4     5.21 presents/10 s
  B  3     888 /    890 /    925  5/0/5/4     6.10 presents/10 s
  A  4     925 /    927 /    968  4/0/5/5     7.10 presents/10 s
  B  5     968 /    970 /   1011  5/0/5/5     7.05 presents/10 s
  A  6    1011 /   1013 /   1054  4/0/5/5     7.01 presents/10 s
  B  7    1054 /   1056 /   1097  4/0/5/5     7.12 presents/10 s
  A  8    1097 /   1099 /   1141  5/0/5/5     7.33 presents/10 s
  B  9    1141 /   1143 /   1185  4/0/5/5     7.27 presents/10 s
  A 10    1185 /   1187 /   1232  5/0/5/5     7.68 presents/10 s
  B 11    1232 /   1234 /   1279  5/0/5/5     7.74 presents/10 s
  A 12    1279 /   1281 /      -  3/0/4/3     unfinished

per phase mean                          A          B        B-A    B/A-1   noise: B vs neighbouring A, mean diff +- SE (SD, n)
presents/10s                         7.23       6.78      -0.45    -6.2%   -0.34 +- 0.28 (SD 0.69, n 6)
frame ms                          1421.03    1497.39     +76.36    +5.4%   +59.97 +- 57.23 (SD 140.18, n 6)
GPU busy ms/frame                  648.81     599.41     -49.40    -7.6%   -24.27 +- 28.99 (SD 71.02, n 6)
GPU idle gaps ms/frame            1185.94     904.79    -281.16   -23.7%   -155.62 +- 160.25 (SD 392.53, n 6)
committer us/commit                258.31     234.21     -24.10    -9.3%   -19.01 +- 14.77 (SD 36.18, n 6)
committer % busy                    59.06      62.67      +3.61    +6.1%   +1.61 +- 2.22 (SD 5.44, n 6)
room wait us/draw                   73.94      69.16      -4.78    -6.5%   -3.23 +- 3.27 (SD 8.02, n 6)
drain ms/10s                      1750.47    1821.55     +71.08    +4.1%   -27.16 +- 77.15 (SD 188.98, n 6)
syncwait GPU ms/10s                797.20     830.23     +33.03    +4.1%   +6.74 +- 26.30 (SD 64.42, n 6)
A total (6 phases): alloc 3.0 us/upload, lookup 13.7 us/upload; per phase: small misses 0, small-device hits/misses 113202/265, device misses 228, exact-size hits 489859, rebound 476350, held-read checks skipped 1034291, held 0
B total (6 phases): alloc 3.4 us/upload, lookup 13.5 us/upload; per phase: small misses 0, small-device hits/misses 125081/14, device misses 13, exact-size hits 364594, rebound 732772, held-read checks skipped 1230567, held 0

@oneandonlydean oneandonlydean self-assigned this Oct 22, 2026
@Dextroid17

Copy link
Copy Markdown
Contributor

Linux data point for the test, since the review says agc_buffer_pool could not be run from a Linux checkout: llvmpipe (LLVM 23.1.1, 256 bits) via lavapipe, Mesa 26.2.4, GALLIUM_OVERRIDE_CPU_CAPS=avx, i9-11900K, CachyOS, GCC 16.2.1, CMake 4.4.3, Release, ANYPS5_REQUIRE_VULKAN=1, ANYPS5_NO_SHADER_CACHE=1; ad25c9ee1 merged with main f57ceaad6 (your base is 1408 commits behind it; local merge ab8262262, clean). The target is agc_buffer_pool_tests; it is EXCLUDE_FROM_ALL only without -DBUILD_TESTING=ON, which both puts it in all and registers the ctest entry agc_buffer_pool.

Head. agc_buffer_pool passes 3 of 3 runs.

Negative control. core/libs/prx/libSceAgcDriver/tests/BufferPool.cpp from ad25c9ee1 on plain f57ceaad6, rebuilt: fails 3 of 3 with two FAIL lines, a pooled buffer of the requested exact size was passed over for a newer one that had to be rebuilt (the one your body names) and the other exact size was not kept for its own request; the neighbouring 4 MiB + 1 smallest-class check in LargerClass, and the new case's size and leak checks, pass on both arms.

Scoping: ExactSizeReuse runs against the test's MockDevice, so this is the Take scan's bookkeeping, not a GPU result. Stacked on #8912 (a62e91004, clean in that order): 3 of 3 with both new cases present. I have no measurement to add on the #8888 interaction @milescoviello names on #8912.

AI-assisted: yes

Celegans12 added a commit to Celegans12/AnyPS5-Ghost-of-Yotei that referenced this pull request Oct 24, 2026
@Celegans12

Copy link
Copy Markdown
Contributor Author

I've finished the second round of world captures I mentioned, this time with 60-present windows (the first round's 16-present windows were too noisy to use). All ten are warm runs of PPSA26344 v1.008 on our own tree (085bd8f, which carries all six of these changes; not main), back to back in one session on the same laptop (RTX 3080 Laptop, driver 610.62, Windows 11), from the same open-world save with the same pad script. Each capture is APS5_TRACE_FRAMES=880:60, about 90 s in the world once the scene settles. The A runs are the tree as it is; each B run adds one off switch. One caveat on the protocol: our run script also sets APS5_PROFILE_DRAW=1 on every run, A and B alike. Run order: pe2-a1, pe2-b9287, pe2-b9289, pe2-a2, pe2-b8911, pe2-b8912k, pe2-a3k, pe2-b8913k, pe2-b10382k, pe2-a4k. For this PR the B run adds APS5_NO_EXACT_SIZE_REUSE=1. That switch is in our tree only; this PR has no switch of its own, so the A/B here is our tree's version of the change.

With the change switched off, the frame p50 was 1,469 ms against 1,605 and 1,634 ms in the A runs on either side (mean 1,620), and GPU busy was 808 against 906 and 877 ms per present (mean 892). This one goes the wrong way: with the change switched off, frames were about 9 % shorter and GPU time per present about 9 % lower, and the two A runs around it agree within 2 %. An earlier, shorter round leaned the same way. So on our tree right now this change looks like it costs time rather than saving it. I'm running an in-run A/B of the switch (both settings alternating inside one run, so session drift drops out) to check, and I'll post that result here either way.

These are all on one tree, so they don't replace a main capture for the perf label. I'm making main-against-main+this-PR captures on the title menu next and will add those summary lines to the description.

Files are on our media branch (the comment box can't take 35 MB traces):

aps5-summary.jsonl window lines (B, then the two A runs)
{"commit":"085bd8f20325","dirty":false,"env":{"APS5_ASYNC_LANE":"51","APS5_BINDLESS_IMAGES":"1","APS5_BINDLESS_LAZY":"1","APS5_CACHE_SPIRV_BACKING":"1","APS5_COLD_SPIRV":"1","APS5_COLD_SPIRV_FILE":"1","APS5_DEFER_REFRESH_FLUSH":"1","APS5_DUMP_FRAMES":"300","APS5_DUMP_FRAMES_EVERY":"10","APS5_INLINE_SHADER_PREPARE":"1","APS5_MEM_STATS":"10","APS5_NO_ADDRESS_DRAW_REUSE":"1","APS5_NO_DRAW_AHEAD":"1","APS5_NO_EXACT_SIZE_REUSE":"1","APS5_NO_PAD_INPUT":"1","APS5_NO_PIPELINE_JOURNAL":"1","APS5_NO_RESULT_MEMO":"1","APS5_PAD_SCRIPT":"100:cross,106:cross,112:cross,118:cross,124:cross,130:cross,136:cross,142:cross,148:cross,154:cross,160:cross,166:cross,172:cross,178:cross,184:cross,190:cross,196:cross,202:cross,208:cross,214:cross,220:cross,226:cross,232:cross,238:cross,244:cross,250:cross,256:cross,262:cross,268:cross,274:cross,280:cross,286:cross,292:cross,298:cross,304:cross,310:cross,316:cross,322:cross,328:cross,334:cross,340:cross,346:cross,352:cross,358:cross,364:cross,370:cross,376:cross,382:cross,388:cross,394:cross,400:cross,406:cross,412:cross,418:cross,424:cross,430:cross,436:cross,442:cross,448:cross,454:cross,460:cross,466:cross,472:cross,478:cross,484:cross,490:cross,496:cross,502:cross,508:cross,514:cross,520:cross,526:cross,532:cross,538:cross,544:cross,550:cross,556:cross,562:cross,568:cross,574:cross,580:cross","APS5_PAD_SCRIPT_REPEAT":"","APS5_PIPELINE_INDIRECT":"1","APS5_PROFILE_DRAW":"1","APS5_PROFILE_GPU":"0","APS5_RESOURCE_CACHE_ENTRIES":"1024","APS5_SPANNED_WAVE":"1","APS5_STAGING_DIFF":"1","APS5_TEXTURE_OVERFLOW_MIB":"1024","APS5_TRACE_DIR":"<path>","APS5_TRACE_EVENTS":"4194304","APS5_TRACE_FRAMES":"880:60","APS5_TRACE_SUMMARY_EVERY":"10","APS5_UNNORM_WRAP_GUARD":"1"},"gpu":"NVIDIA GeForce RTX 3080 Laptop GPU","driver":"NVIDIA 610.62","os":"Windows 10.0.26200","cpu":"11th Gen Intel(R) Core(TM) i9-11900H @ 2.50GHz","cpu_threads":16,"memory_mib":32469,"title_id":"PPSA26344","title":"Ghost of Y\u00c5\u008dtei","content_version":"01.008.000","resolution":"3840x2160","window":{"spec":"frames 880:60","first_present":880,"ms":92357.774},"presents":60,"frame_ms":{"p50":1468.640,"p90":1859.986,"p99":2057.624,"max":2057.624},"gpu_busy_ms_per_present":807.758,"worker_busy_pct":{"queue worker 0x0":90.922,"queue worker 0x52":57.130,"queue worker 0x20":71.370,"queue worker 0x53":56.728,"queue worker 0x48":3.271,"queue worker 0x30":83.112,"queue worker 0x38":84.811,"queue worker 0x51":7.724,"queue worker 0x50":39.201},"top_phases_ms_per_frame":{"Driver.Packet":7605.934,"Driver.Packet.memory_wait":5236.826,"Driver.Packet.dispatch":1125.466,"DrawPipeline.commit":908.810,"Driver.Dispatch":882.294,"Graphics.Draw":802.634,"Driver.Packet.draw":768.073,"Driver.Draw":763.574},"trace_dropped":false,"trace":"aps5-trace-085bd8f20325-10720.json.gz"}
{"commit":"085bd8f20325","dirty":false,"env":{"APS5_ASYNC_LANE":"51","APS5_BINDLESS_IMAGES":"1","APS5_BINDLESS_LAZY":"1","APS5_CACHE_SPIRV_BACKING":"1","APS5_COLD_SPIRV":"1","APS5_COLD_SPIRV_FILE":"1","APS5_DEFER_REFRESH_FLUSH":"1","APS5_DUMP_FRAMES":"300","APS5_DUMP_FRAMES_EVERY":"10","APS5_INLINE_SHADER_PREPARE":"1","APS5_MEM_STATS":"10","APS5_NO_ADDRESS_DRAW_REUSE":"1","APS5_NO_DRAW_AHEAD":"1","APS5_NO_PAD_INPUT":"1","APS5_NO_PIPELINE_JOURNAL":"1","APS5_NO_RESULT_MEMO":"1","APS5_PAD_SCRIPT":"100:cross,106:cross,112:cross,118:cross,124:cross,130:cross,136:cross,142:cross,148:cross,154:cross,160:cross,166:cross,172:cross,178:cross,184:cross,190:cross,196:cross,202:cross,208:cross,214:cross,220:cross,226:cross,232:cross,238:cross,244:cross,250:cross,256:cross,262:cross,268:cross,274:cross,280:cross,286:cross,292:cross,298:cross,304:cross,310:cross,316:cross,322:cross,328:cross,334:cross,340:cross,346:cross,352:cross,358:cross,364:cross,370:cross,376:cross,382:cross,388:cross,394:cross,400:cross,406:cross,412:cross,418:cross,424:cross,430:cross,436:cross,442:cross,448:cross,454:cross,460:cross,466:cross,472:cross,478:cross,484:cross,490:cross,496:cross,502:cross,508:cross,514:cross,520:cross,526:cross,532:cross,538:cross,544:cross,550:cross,556:cross,562:cross,568:cross,574:cross,580:cross","APS5_PAD_SCRIPT_REPEAT":"","APS5_PIPELINE_INDIRECT":"1","APS5_PROFILE_DRAW":"1","APS5_PROFILE_GPU":"0","APS5_RESOURCE_CACHE_ENTRIES":"1024","APS5_SPANNED_WAVE":"1","APS5_STAGING_DIFF":"1","APS5_TEXTURE_OVERFLOW_MIB":"1024","APS5_TRACE_DIR":"<path>","APS5_TRACE_EVENTS":"4194304","APS5_TRACE_FRAMES":"880:60","APS5_TRACE_SUMMARY_EVERY":"10","APS5_UNNORM_WRAP_GUARD":"1"},"gpu":"NVIDIA GeForce RTX 3080 Laptop GPU","driver":"NVIDIA 610.62","os":"Windows 10.0.26200","cpu":"11th Gen Intel(R) Core(TM) i9-11900H @ 2.50GHz","cpu_threads":16,"memory_mib":32469,"title_id":"PPSA26344","title":"Ghost of Y\u00c5\u008dtei","content_version":"01.008.000","resolution":"3840x2160","window":{"spec":"frames 880:60","first_present":880,"ms":97466.171},"presents":60,"frame_ms":{"p50":1605.492,"p90":1758.921,"p99":1913.625,"max":1913.625},"gpu_busy_ms_per_present":906.032,"worker_busy_pct":{"queue worker 0x0":91.049,"queue worker 0x53":58.251,"queue worker 0x20":72.397,"queue worker 0x52":58.648,"queue worker 0x38":85.704,"queue worker 0x48":2.584,"queue worker 0x30":83.804,"queue worker 0x51":7.236,"queue worker 0x50":37.859},"top_phases_ms_per_frame":{"Driver.Packet":8079.757,"Driver.Packet.memory_wait":5587.069,"Driver.Packet.dispatch":1119.145,"DrawPipeline.commit":979.337,"Driver.Dispatch":875.332,"Graphics.Draw":856.851,"Driver.Packet.draw":833.317,"Driver.Draw":828.708},"trace_dropped":false,"trace":"aps5-trace-085bd8f20325-3756.json.gz"}
{"commit":"085bd8f20325","dirty":false,"env":{"APS5_ASYNC_LANE":"51","APS5_BINDLESS_IMAGES":"1","APS5_BINDLESS_LAZY":"1","APS5_CACHE_SPIRV_BACKING":"1","APS5_COLD_SPIRV":"1","APS5_COLD_SPIRV_FILE":"1","APS5_DEFER_REFRESH_FLUSH":"1","APS5_DUMP_FRAMES":"300","APS5_DUMP_FRAMES_EVERY":"10","APS5_INLINE_SHADER_PREPARE":"1","APS5_MEM_STATS":"10","APS5_NO_ADDRESS_DRAW_REUSE":"1","APS5_NO_DRAW_AHEAD":"1","APS5_NO_PAD_INPUT":"1","APS5_NO_PIPELINE_JOURNAL":"1","APS5_NO_RESULT_MEMO":"1","APS5_PAD_SCRIPT":"100:cross,106:cross,112:cross,118:cross,124:cross,130:cross,136:cross,142:cross,148:cross,154:cross,160:cross,166:cross,172:cross,178:cross,184:cross,190:cross,196:cross,202:cross,208:cross,214:cross,220:cross,226:cross,232:cross,238:cross,244:cross,250:cross,256:cross,262:cross,268:cross,274:cross,280:cross,286:cross,292:cross,298:cross,304:cross,310:cross,316:cross,322:cross,328:cross,334:cross,340:cross,346:cross,352:cross,358:cross,364:cross,370:cross,376:cross,382:cross,388:cross,394:cross,400:cross,406:cross,412:cross,418:cross,424:cross,430:cross,436:cross,442:cross,448:cross,454:cross,460:cross,466:cross,472:cross,478:cross,484:cross,490:cross,496:cross,502:cross,508:cross,514:cross,520:cross,526:cross,532:cross,538:cross,544:cross,550:cross,556:cross,562:cross,568:cross,574:cross,580:cross","APS5_PAD_SCRIPT_REPEAT":"","APS5_PIPELINE_INDIRECT":"1","APS5_PROFILE_DRAW":"1","APS5_PROFILE_GPU":"0","APS5_RESOURCE_CACHE_ENTRIES":"1024","APS5_SPANNED_WAVE":"1","APS5_STAGING_DIFF":"1","APS5_TEXTURE_OVERFLOW_MIB":"1024","APS5_TRACE_DIR":"<path>","APS5_TRACE_EVENTS":"4194304","APS5_TRACE_FRAMES":"880:60","APS5_TRACE_SUMMARY_EVERY":"10","APS5_UNNORM_WRAP_GUARD":"1"},"gpu":"NVIDIA GeForce RTX 3080 Laptop GPU","driver":"NVIDIA 610.62","os":"Windows 10.0.26200","cpu":"11th Gen Intel(R) Core(TM) i9-11900H @ 2.50GHz","cpu_threads":16,"memory_mib":32469,"title_id":"PPSA26344","title":"Ghost of Y\u00c5\u008dtei","content_version":"01.008.000","resolution":"3840x2160","window":{"spec":"frames 880:60","first_present":880,"ms":101508.899},"presents":60,"frame_ms":{"p50":1634.192,"p90":1985.444,"p99":2121.146,"max":2121.146},"gpu_busy_ms_per_present":877.331,"worker_busy_pct":{"queue worker 0x0":91.377,"queue worker 0x52":58.903,"queue worker 0x53":58.483,"queue worker 0x20":71.895,"queue worker 0x38":85.946,"queue worker 0x30":84.403,"queue worker 0x48":3.061,"queue worker 0x51":7.275,"queue worker 0x50":37.284},"top_phases_ms_per_frame":{"Driver.Packet":8433.109,"Driver.Packet.memory_wait":5832.700,"Driver.Packet.dispatch":1158.377,"DrawPipeline.commit":1060.990,"Graphics.Draw":924.617,"Driver.Dispatch":908.500,"Driver.Packet.draw":906.946,"Driver.Draw":902.247},"trace_dropped":false,"trace":"aps5-trace-085bd8f20325-22212.json.gz"}

@Celegans12

Copy link
Copy Markdown
Contributor Author

Follow-up to my last comment: the in-run A/B doesn't back up the -9 %, so please disregard that reading. I alternated 60 s phases with and without APS5_NO_EXACT_SIZE_REUSE=1 inside one open-world run (9 pairs, same save and scene, on our tree with #8887 added; T1 bit-identical). With the change switched off, presents per 10 s were 6.75 against 6.88 with it on (-0.09 ± 0.15 against the neighbouring phases), and frame time was 1.6 % longer, both inside the noise. So this change is neutral to slightly positive on Yōtei, not a cost. The gap in the separate runs was session drift, which this method removes, and it's also why I'll lean on in-run A/Bs rather than separate runs for small effects from now on.

Per-phase means (A = as is, B = APS5_NO_EXACT_SIZE_REUSE=1)
per phase mean                          A          B        B-A    B/A-1   noise: B vs neighbouring A, mean diff +- SE (SD, n)
presents/10s                         6.88       6.75      -0.13    -1.9%   -0.09 +- 0.15 (SD 0.45, n 9)
frame ms                          1479.13    1503.20     +24.08    +1.6%   +17.69 +- 32.62 (SD 97.86, n 9)
GPU busy ms/frame                  838.06     802.97     -35.08    -4.2%   -14.88 +- 21.52 (SD 64.56, n 9)
committer us/commit                263.61     240.84     -22.77    -8.6%   -14.75 +- 10.43 (SD 31.28, n 9)
room wait us/draw                  116.23      99.97     -16.26   -14.0%   -11.39 +- 7.55 (SD 22.64, n 9)

@github-actions github-actions Bot removed the perf numbers missing perf pull request without a before and after summary line label Oct 24, 2026
@imellag

imellag commented Oct 25, 2026

Copy link
Copy Markdown
Contributor

Lana numbers for this PR, on macOS, where the draw committer hits the rebuild path on most draws.

Setup: M4 Pro, macOS 27, Rosetta 2, MoltenVK 1.4.2. main 61fc572 with the open macOS PRs it needs to boot (#10423, #10889, #10898, #11047, #11048, #11049), plus #11130 (mimalloc, APS5_MIMALLOC=1 on every run). Planet of Lana (PPSA19450): village gameplay from New Game with the same scripted pad input, 120 s per run, fps from 85 s, runs alternated with the base in one session.

fps mean Driver.Packet per frame
base 33.04, 33.82, 33.73, 33.04, 33.56, 33.69, 33.46, 33.63 33.50 21-22 ms
base + #8913 33.91, 33.95, 33.86 33.91 (+1.2%) 20 ms

All three #8913 runs are above seven of the eight base runs.

Where it hits: in a 15 s Time Profiler capture of the base, the committer thread spends 626 samples in Buffer::Buffer and 471 in vkDestroyBuffer called from it, out of 10,921. All of them come from ShaderResources::PrepareDrawBindings, at the std::make_shared<Buffer>(context, override->size, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT) of the moved-word path. That is the BindExactBuffer rebuild this PR avoids when an exact-size slot is retained.

For the same call site I also tried copying the moved words into the recorder's draw upload ring (Recorder::DrawUpload) instead of making a buffer: 34.04 fps (34.01, 34.07, 34.05), Driver.Packet 19 ms. I will open that separately. It only changes that one caller, so #8913 still applies to the other buffers that go through Take with a new size.

Not measured: this PR's own exact-size hit counter, and trace_digest.py lines.

@Celegans12

Copy link
Copy Markdown
Contributor Author

@imellag thanks, this is the first measurement that shows where the exact-size hit actually lands. On Yōtei I could only say the change was neutral to slightly positive, and I never pinned down which Take callers benefit. Your Time Profiler capture does: the moved-word buffer in PrepareDrawBindings, which MoltenVK makes and destroys on most draws, so +1.2 % with all three runs above seven of the eight base runs is a clean result.

Your #11142 looks like the better fix for that particular caller, since it skips making the buffer at all instead of reusing one. I agree the two don't get in each other's way: once #11142 lands, this PR still covers the other buffers that come through Take with a new size. If you get around to it, the exact-size hit counter in a #11142 + #8913 run would show how much of the hit rate is left after that caller moves to the ring. No pressure, though.

@oneandonlydean
oneandonlydean merged commit a0cf4c4 into boykopovar:main Oct 27, 2026
17 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agc libSceAgc* perf Performance change

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants