Skip to content

Driver queue workers and the presenter lose their cores to a title's spinning job threads #1788

Closed

Description

@fisherjoey

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 50 on the queue 0 worker alone (attract): 30-33 to 51.8 fps.
  • chrt -f 50 on 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 -10 on the queue 0 worker alone: PS Studios movie 255 to 335 presents per 10 s.

Proposed fix

An opt-in APS5_THREAD_PRIORITY:

  • =high: nice APS5_THREAD_NICE (default -10).
  • =rt: SCHED_FIFO at APS5_THREAD_RT (default 10).
  • On Linux, through rtkit over a dlopen'd libdbus-1, the way SDL does it (MakeThreadHighPriorityWithPID, or MakeThreadRealtimeWithPID after RLIMIT_RTTIME and SCHED_RESET_ON_FORK), with setpriority as 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

  1. MarcZX10 commented on Oct 8, 2026

    @MarcZX10
    Contributor

    I reviewed the prepared branch against current main; git diff --check is clean, and the Linux ThreadPriority.cpp passes a standalone g++ -std=c++20 -Wall -Wextra -Werror -fsyntax-only check. I haven't run the project build or reproduced the game measurements.

    One safety detail to settle before opening a PR: PrepareRealtime() sets both RLIMIT_RTTIME fields (rlim_cur = maxUs / 2, rlim_max = maxUs). Linux lets an unprivileged process lower its hard limit, but cannot raise it again without CAP_SYS_RESOURCE; the limit is shared by the process's threads. So opting into rt permanently lowers the emulator process's hard limit, even if the later rtkit request fails and the code falls back to high. 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. See getrlimit(2).

  2. MarcZX10 commented on Oct 8, 2026

    @MarcZX10
    Contributor

    Update: upstream main is now aa277b3; the prepared priority branch is still based on 6b56175 (1 commit ahead, 22 behind). My earlier base reference is stale. Please rebase on aa277b3 after settling the RLIMIT_RTTIME hard-limit side effect, then rerun the focused checks before opening the PR.

  3. fisherjoey commented on Oct 8, 2026

    @fisherjoey
    ContributorAuthor

    Thanks @MarcZX100, it wasn't deliberate. I checked rtkit itself: verify_process_rttime reads the hard limit from /proc/<pid>/limits and refuses when it is above RTTimeUSecMax (source). Lowering only the soft limit doesn't work. With soft 100000 us and hard unlimited, MakeThreadRealtimeWithPID returns "Access denied"; with hard 200000 it goes through.

    So d50cf53 does the next best thing. It reads rtkit's MaxRealtimePriority and RTTimeUSecMax first 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 applies SCHED_RR by default, not SCHED_FIFO, so the log now reports the actual policy.

    Rebased onto current main (94b1eec, a bit past aa277b3). ctest -R agc_driver -j1 is 202 of 202, both unset and with APS5_THREAD_PRIORITY=rt, and the conventions check passes. The PR is #1873.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions