perf(vk): with batched submits (RADV), send a batch at once while the GPU is idle (#1993) #1999
No reviewers
Labels
No labels
accessibility
autorelease: pending
autorelease: tagged
bug
dependencies
documentation
duplicate
enhancement
github_actions
good first issue
help wanted
invalid
question
testing wanted
wontfix
No milestone
No assignees
1 participant
Due date
No due date set.
Dependencies
No dependencies set
Reference
MagicBeanDev/Hookshot!1999
Loading…
Reference in a new issue
No description provided.
Delete branch "perf/batch-idle-flush"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Implements #1993's proposal by @mrdev023, as a default where batching is on.
With batched submits (on by default with RADV, so Linux AMD including the Steam Deck;
RX_VK_BATCH=1elsewhere), a held batch waited for the next wait, present or the 32-buffer cap. That happened even when the GPU had finished everything it was sent. So the GPU sat idle until the game waited for it, and with serial replay that's mid-frame, attake_capture's late-page wait.Now, when a submit joins the batch and the GPU has completed every batch sent so far (
batch_tl>=batch_flushed), the batch goes out at once. While the GPU is busy, submits are still grouped.The reporter's numbers (RX 9070 XT, RADV, Kokiri Forest, FSR 4, 5120x1440):
Flushes went from about 5.7 a frame to about 10 of 3.7 command buffers each, and the late-page wait from about 1.4 to 0.64 ms.
RX_VK_BATCH_IDLE=0keeps the old behaviour.Not yet checked here: I have no RADV machine. I'll A/B it on Windows with
RX_VK_BATCH=1(NVIDIA) for fps and flashes before merging. Drivers other than RADV are unchanged, since batching stays off there by default.🤖 Generated with Claude Code
Tested this branch as is (
35a8aaaonmainabd5e5f) on RX 9070 XT, RADV, Ryzen 9 9950X3D, NixOS. Kokiri Forest, FSR 4 at 5120x1440, GPU power levelauto, same build both ways:RX_VK_BATCH_IDLE=0Same gain as in #1993, no flashes.
The change looks right to me. One thing should be checked before merge: the hardware this is aimed at.
Correctness. Sending a held batch early can't reorder anything.
queue_flush_lockedstill runs underdev.present_mu, on the one queue, and signalstimeline,rec_timelineandbatch_tlwith the same values it would have used later.batch_flushedis only read or written under that lock (recording_wait,queue_flush_locked, nowbatch_gpu_idle), so the replay, present and recorder threads gain no new race. Waits that need the batch keep their own flushes (wait/wait_value/vk_timeline_wait-> FR_VALUE/FR_WAIT,wait_recorded-> FR_WAIT,recording_wait-> FR_SLOT, present -> FR_PRESENT), so nothing new depends on this flush. It only makes them find the batch already sent more often. The first submit works:batch_tlandbatch_flushedboth start at 0, so it goes out at once. A lost device just makesvkGetSemaphoreCounterValuefail and the batch stays held, as before. The second commit only moves the check intobatch_gpu_idle(), so mrdev023's run on35a8aaastill applies to this head.Gating.
batch_gpu_idle()is only called inside thebatch_on()branch, so drivers without batching (everything but RADV unlessRX_VK_BATCH=1) don't change.Perf. The numbers are there, and mrdev023 reran this branch (41-57 vs 29-38 fps, 0 flashes in 3044 frames). The catch is that both runs are one machine (9070 XT + 9950X3D at 5120x1440 with FSR 4), and that machine is GPU-bound. #1993 also notes that
RX_VK_BATCH=0was faster than the default there (44-55 vs 41-48). So on that box, much of the win comes from batching less. Batching was turned on for RADV for the opposite case: issue 413's 8060S, where each submit paid for the userptr imports again (~70 -> ~6 ms of queue submit a frame). On a setup where the GPU keeps up with the recorder,done >= batch_flushedholds at almost every submit, so most submits go out alone and we're close toRX_VK_BATCH=0again. The Steam Deck is Tier 1 and RADV, so this goes default-on there. The NVIDIARX_VK_BATCH=1A/B you plan won't show that cost, because NVIDIA doesn't have the per-submit userptr cost. Could someone run it on a Deck or an RADV APU, or on a lighter RADV setup (native res, no FSR) where the GPU isn't the bottleneck? Check that the stats line's flushes a frame and the queue-submit time stay well below whatRX_VK_BATCH=0gives. If it does regress, the fix could be to flush on idle only once the batch is a few buffers big or has been held for a while, likeRX_VK_BATCH_POLL_USdoes.#1905. No duplicate and no textual conflict. #1905 changes
wait_recorded's flush, which main already does unconditionally as FR_WAIT since the load-hang fix, into a conditional FR_READBACK. This PR touchessubmit_recordingand not the FR enum or the stats line. The two complement each other: this one sends a batch early while the GPU is idle, and the readback flush still covers a batch held while the GPU was busy. #1905 is in conflict with main as it stands and also carries unrelated TAAU shader files, so it needs a rebase anyway.Nit: the description says the late-page wait went from ~1.4 to 0.64 ms. #1993 says ~8.4 -> ~3.8 ms a frame and mrdev023's rerun ~7.9 -> ~4.6 ms, so the description's number is probably per wait or from another build.
CI: the only failure is
tests/test_log_batch.pyon windows-2022 (GetTickCount64() - t0 < 200), a timing check in logging that has nothing to do with this change. Ubuntu checks, the llvm-mingw Vulkan build and the AppImage pass.Nit: these flushes are counted as FR_OTHER, so in the stats line they're mixed with the other untagged flushes (a parallel segment's own submit,
queue_flush(),device_wait_idle_locked). An FR_IDLE reason, its own column in the stats line (one more stub in the two test fixtures), would show directly how many of the ~14 flushes a frame this causes. That's also the number to watch on a Deck or APU run.A/B on Windows (RTX 4060 Ti, batching forced on with
RX_VK_BATCH=1), smooth.ps1's route (Kokiri Forest, Hyrule Field, Kakariko), 2 rounds alternating:RX_VK_BATCH_IDLE=0, before this PR)With the batch held, the GPU sits idle and loses ~12 fps here, matching what #1993 measured on RADV. The idle flush brings batching back to the unbatched frame rate, and Windows' default (batching off) is unchanged.
🤖 Generated with Claude Code
Re-review at
da8a8a2. Since our review the only new commit is the merge of main. The PR's own diff (vk_replay.cppand the two test stubs) is byte-for-byte what I reviewed at23c47ad, so the correctness part of that review still holds.Thanks for the Windows A/B. It's useful: holding the batch costs ~12 fps on the 4060 Ti too, and the idle flush gets batching back to the unbatched rate with no crashes. Windows' default is unchanged.
It also confirms why the RADV APU run is still open. When the GPU keeps up, the idle flush makes batched submits behave about like
RX_VK_BATCH=0. That's fine on NVIDIA and on mrdev023's GPU-bound 9070 XT, where unbatched was already as fast or faster. But batching is on for RADV because of issue 413's 8060S, where unbatched was the slow path (each submit paid for the userptr imports again, ~70 ms of queue submit a frame). The Deck is Tier 1 and gets this by default, and neither A/B covers that case. So my request stands: one run on a Deck or an RADV APU (8060S/780M) withRX_VK_BATCH_IDLE=0, this PR, andRX_VK_BATCH=0, comparing fps, flushes a frame and queue-submit ms from the stats line. If the PR comes out close toRX_VK_BATCH=0there, a minimum batch size or a minimum hold time before the idle flush would keep most of the win without paying that cost.Still open from before, both nits: the idle flushes are counted as FR_OTHER (inline thread above; an FR_IDLE column would make the APU run easy to read), and the description's late-page wait (~1.4 -> 0.64 ms) doesn't match #1993's or mrdev023's per-frame numbers (~7.9 -> ~4.6 ms).
CI: the failures (
test_make_game_ngx.pyon Ubuntu and Windows 1/2,test_pipeline_state_portable.pyon Windows 2/2, actionlint's SC2012 inlinux-appimage.yml) fail the same way on main's run at21fa1365, so they're not from this PR. The Vulkan build, AppImage and builder pass. Mergeable, no conflicts.Those came from my first write-up in #1993, and they were my misreading. The log line
a capture taken (ms): ... GPU wait for late pages X (100 of 600 takes)averages over all 600 takes. There are 6 takes a frame and the wait falls in one of them, so the per-frame wait is 6 × X: ~8.4 -> ~3.8 ms in #1993, ~7.9 -> ~4.6 ms in my rerun of this branch. I've corrected both. The relative gain is unchanged.I don't think it is, after profiling it today (
mainabd5e5f+ this PR, Kokiri Forest):HOS_SAMPLE_ALL+scripts/crit.py: the main thread runs ~18% of the time. It waits ~25% intake_capture's GPU wait and ~35% inevent_waitfor the Presentation thread, which does the replay. The replay costs ~14-20 ms of CPU a frame (a frame's replay time: draw 6-10, descriptors 2-4, commands 2.5-4).RX_VK_PASS_TIMING=1: the GPU's work is ~175 passes (~39 us each) and ~143 dispatches (~21 us each), with ~409 full barriers a frame. That looks latency-bound (many small serialized pieces), not throughput-bound, which is why the resolution doesn't change it.So on this box the frame is the CPU replay plus the mid-frame wait, and the GPU mostly waits for work. That also explains why
RX_VK_BATCH=0was faster than the held batch: the GPU sat idle while the batch was held.@hckrman101
github-sync referenced this pull request2026-11-12 06:22:54 +00:00