Repository navigation
Driver queue workers and the presenter lose their cores to a title's spinning job threads #1788
Description
Summary
The AGC queue workers and the VideoOut presenter run at the same host priority as the title's threads. Guest priorities are not applied on the host (scePthreadSetprio only stores the value), so nothing orders them relative to the driver. A title whose job system busy-waits on every hardware thread can keep the queue 0 worker off a core for long stretches. The driver then becomes the bottleneck without doing more work.
Evidence
Demon's Souls (PPSA01341 v1.07) on a Ryzen 5 7600X (6 cores, 12 threads), PS Studios movie. 12 app.elf threads take 82-92% of a core each. Their on-CPU samples land on a handful of addresses in the title, which looks like a spin loop. Scheduler tracing over 25 s of the movie shows the queue 0 worker:
- on-CPU 45% of the time;
- preempted for 7.6 s in total (23102 times);
- 3.8 s on the run queue after its 11192 wakeups;
- blocked for 6.6 s.
Priorities applied by hand while the game was running:
chrt -f 50on the queue 0 worker alone (attract): 30-33 to 51.8 fps.chrt -f 50on all 8 queue workers and the presenter (attract): 30.6-31.9 to 57.5-59.5 fps, 482 to 583 presents per 10 s.renice -10on the queue 0 worker alone: PS Studios movie 255 to 335 presents per 10 s.
Proposed fix
An opt-in APS5_THREAD_PRIORITY:
=high: niceAPS5_THREAD_NICE(default -10).=rt:SCHED_FIFOatAPS5_THREAD_RT(default 10).- On Linux, through rtkit over a dlopen'd libdbus-1, the way SDL does it (
MakeThreadHighPriorityWithPID, orMakeThreadRealtimeWithPIDafterRLIMIT_RTTIMEandSCHED_RESET_ON_FORK), withsetpriorityas the fallback. On Windows,SetThreadPriority. - Each queue worker calls it, and so does the presenter, through a new C export from the driver.
- Unset means no change.
With =high (all nine threads at nice -10 via rtkit) the PS Studios movie went from 254-255 to 345 presents per 10 s. The attract movie ran at 49.4-54.1 fps (520-536 presents), against 429-447 presents without it on the same build. That build already included the changes from #1132 and #1133.
Why opt-in: with the priority on, an intermittent boot-time crash in this title (a compute shader reading a garbage descriptor 20-90 s into boot) became much more likely, 9 of 15 runs. That crash is a capture race, #1787: a faster driver records the producer's label sooner and loses the race more often. With the #1787 fix, 15 of 15 runs with the priority on were clean. I'd still keep it off by default: it changes host scheduling for every title, rtkit is not on every system, and rt puts threads under RLIMIT_RTTIME, so a queue worker that runs 100 ms without blocking gets the process a SIGXCPU. A user can turn it on per title.
#626 pins the queue workers to performance cores on hybrid CPUs. It is complementary and does nothing on this CPU, which is not hybrid.
Found on Linux 7.2 (Nobara 44), GCC 16, RTX 5070 Ti (595.91.07), Ryzen 5 7600X, 32 GB, with Demon's Souls PPSA01341 v1.07. Not tested on Windows. Measured on main at c1241ad with #157, #861, #864 and #1051, the path fix from #1047 and the changes from #1132 and #1133. The branch is on current main (6b56175).
The change is ready on a branch: fisherjoey/AnyPS5@main...fisherjoey:AnyPS5:feat/queue-thread-priority
I'll open the PR if the approach looks right.
AI-assisted: yes.
Activity
MarcZX10 commentedon Oct 8, 2026
I reviewed the prepared branch against current
main;git diff --checkis clean, and the LinuxThreadPriority.cpppasses a standaloneg++ -std=c++20 -Wall -Wextra -Werror -fsyntax-onlycheck. I haven't run the project build or reproduced the game measurements.One safety detail to settle before opening a PR:
PrepareRealtime()sets bothRLIMIT_RTTIMEfields (rlim_cur = maxUs / 2,rlim_max = maxUs). Linux lets an unprivileged process lower its hard limit, but cannot raise it again withoutCAP_SYS_RESOURCE; the limit is shared by the process's threads. So opting intortpermanently lowers the emulator process's hard limit, even if the later rtkit request fails and the code falls back tohigh. Is that deliberate? If so, please state the hard-limit side effect in the docs; otherwise consider preserving the existing hard limit and only lowering the soft limit, subject to rtkit's requirements. Seegetrlimit(2).MarcZX10 commentedon Oct 8, 2026
Update: upstream
mainis nowaa277b3; the prepared priority branch is still based on6b56175(1 commit ahead, 22 behind). My earlier base reference is stale. Please rebase onaa277b3after settling theRLIMIT_RTTIMEhard-limit side effect, then rerun the focused checks before opening the PR.fisherjoey commentedon Oct 8, 2026
Thanks @MarcZX100, it wasn't deliberate. I checked rtkit itself:
verify_process_rttimereads the hard limit from/proc/<pid>/limitsand refuses when it is aboveRTTimeUSecMax(source). Lowering only the soft limit doesn't work. With soft 100000 us and hard unlimited,MakeThreadRealtimeWithPIDreturns "Access denied"; with hard 200000 it goes through.So d50cf53 does the next best thing. It reads rtkit's
MaxRealtimePriorityandRTTimeUSecMaxfirst and leaves the limit alone if rtkit is unreachable or the priority is too high. It keeps a hard limit that already satisfies rtkit, and only otherwise lowers it, right before the request. The soft limit goes to at most half the hard one. When the hard limit is lowered, the code logs it. The side effect (it stays lowered if rtkit then refuses) is in TechnicalDebt and BUILD.md. The same check showed rtkit appliesSCHED_RRby default, notSCHED_FIFO, so the log now reports the actual policy.Rebased onto current
main(94b1eec, a bit pastaa277b3).ctest -R agc_driver -j1is 202 of 202, both unset and withAPS5_THREAD_PRIORITY=rt, and the conventions check passes. The PR is #1873.