perf(vk): the replay's reserved core on Linux too #445
No reviewers
Labels
No labels
area/accessibility
area/audio
area/input
area/install
area/menu
area/renderer
area/saves
area/upscaling
good first issue
gpu/amd
gpu/igpu
gpu/intel
gpu/nvidia
help wanted
kind
bug
kind
chore
kind
docs
kind
feature
kind
performance
kind
question
needs measurements
platform/linux
platform/steam-deck
platform/windows
preview
priority
critical
priority
high
priority
low
status
blocked
status
confirmed
status
duplicate
status
invalid
status
needs-info
status
wontfix
testing wanted
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
MagicBeanDev/Longshot!445
Loading…
Reference in a new issue
No description provided.
Delete branch "HumanBean/Longshot:perf/replaypin-linux"
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?
Why
On Linux the Market runs slower than it needs to: the host keeps one CPU core free of the game's threads for the replay, but on Linux nothing ever ran on it, so the replay shared cores with the game's threads while that core sat idle. Windows has used it by default all along.
What changed
thread_entryandthread_selfinhost/posix/win32_posix.cgive a thread that registers the process's affinity as the process started, as Windows gives a new thread. A Linux thread inherits the affinity of the thread that makes it, so the back end's threads made on first use inside a pinned replay would otherwise have stayed on the reserved core.replay_pin_mask()ingpu/vk/vk_replay.cppis no longer Windows only.thread_entry's reset andSetThreadAffinityMaskshare the thread's lock, and the reset is skipped once the thread was pinned.hos_thread_startpins a game thread by its handle right afterCreateThread; the new thread's reset, coming second, would have put it back on the reserved core.tests/test_reserved_core.pyhas upstream's checks of both and one of the lock.RX_VK_REPLAY_PINindocs/development.md.Ported from Hookshot's
git.notrexed.dev/Hookshot/Hookshot@b0373aeab0(https://github.com/NotRexed/Hookshot/pull/2903); the authors are credited in the commit.Flags and settings
RX_VK_REPLAY_PIN=0Measurements
Interim: these numbers are from one binary with the change switched off and on, not yet from main against this branch. The binary is a local merge (318acabe) of this branch and other ports under test, each behind a run-time switch; the other ports were switched off in both columns. A build of this branch alone against main, interleaved, with a Kokiri Forest phase, is running and will replace this table.
Baseline: local merge @ 318acabe,
RX_VK_REPLAY_PIN=0Compared: local merge @ 318acabe,
RX_VK_REPLAY_PINunset (on)Why main wasn't the comparison: the screening run tested several ports on one binary; the branch's own A/B against main follows.
Hardware: RTX 3090, NVIDIA 620.70.06, Ryzen 9 7900X (the game on 12 of its 24 threads: one core complex), 32 GB, CachyOS (Linux 7.3.3), headless sway
Scene: Castle Town Market, day, child Link; 2048x1152, the game's own upscaler, no frame generation, uncapped (
HOS_FPS_MAX=240)Method: each session starts the game on a fresh copy of one save, teleports to the Market gate, stands 10 s, runs 10 s to the fountain square, runs six figure-eights with the camera turning (60 s), then turns the camera at the spot for 10 s. Frame times from
RX_VK_FRAME_LOG, fps and draws from the log's status lines in each window. Sessions interleaved with the other arms of the screening; 6 baseline sessions, 3 with the change. Medians of the sessions.How to reproduce: any save in the Market;
RX_VK_REPLAY_PIN=0against unset, the same route each time.RX_VK_REPLAY_PIN=0(6 sessions)Testing
Tested: in the game on Linux (RTX 3090, NVIDIA 620.70.06, CachyOS), the Market route above, 3 sessions with the pin and 6 without on one binary; every session reached the square and ran the whole route, no crash, no device loss. The game-free checks were not run locally; CI runs them.
Not tested: this branch built alone (in progress). Threads that never register with the host and are made inside a pinned replay (the texture decode pools,
cap_spare_make, FSR 4 builds, driver threads) keep the reserved core's affinity; upstream has the same gap. Whether any thread ends up there is to be checked from a profiled run before this is marked ready. Kokiri Forest and other areas. AMD and Intel GPUs. Windows (no change expected: the code path there is the same as before).Needs other testers:
RX_VK_REPLAY_PIN=0, expecting the same or better fps and no new hitch (request to open)Screenshots
Nothing on screen.
Risk and rollback
A thread could be left on the reserved core and run slowly there, or a game thread's pin could be lost; both would show as a lower frame rate or hitches on Linux, not as a wrong picture.
RX_VK_REPLAY_PIN=0turns the pin off; the affinity reset of registering threads stays (it only gives a thread the process's own mask).Reviewer notes
Look first at the lock and the
pinnedflag inhost/posix/win32_posix.c: that part is not upstream's. Left for a follow-up: the thread is moved to the reserved core and back for every replay (two affinity calls a submission), whichdocs/cpu-profile.mdprices at about 1.9 % of a core on the serial threads; keeping it there across back-to-back submissions would save that on Windows too.Issues
None
Checklist
For everyone
type(scope): description).main.python -I -B tests/run_all.pyrun and passing (any failure that also fails onmainis named above). Not run locally: waiting for CI.third_party/untouched (a new folder with aPROVENANCE.mdinstead).If applicable
Assisted-by:/Co-Authored-By:trailers.docs/tested/for each new measured run (format indocs/tested-configurations.md; no appended row or assembled view). To add with the branch's own runs.tests/that reads changed source updated in this PR, not weakened.🤖 Generated with Claude Code
https://claude.ai/code/session_017vw8e34avQ5cTCWow16MXn
WIP: perf(vk): the replay's reserved core on Linux tooto perf(vk): the replay's reserved core on Linux tooCorrection to this PR's measurements, and the fair numbers.
The table in the description overstated the gain, and the mistake was in my test setup. My timed sessions confine the game to 12 of the PC's 24 logical processors (CPUs 0-5 and 12-17), but the host's reserved core is chosen from all of them, and here it was CPUs 11 and 23, outside that set. With the pin on, the replay therefore ran on a 13th core the baseline did not have (and was starved whenever a build ran on that side, which gave multi-second frames in some sessions). The description's +9 % standing and +6 % running came from that setup.
Measured again with the reserved core inside the session's own processors (
HOS_RESERVE_CORE_MASK=20020: CPUs 5 and 17, so both arms have the same 12 and the game's threads are kept off that core in both):Baseline: local merge @ 318acabe (this PR's change and other ports, each behind a run-time switch, the others off),
RX_VK_REPLAY_PIN=0Compared: the same binary, pin on
Hardware: RTX 3090, NVIDIA 620.70.06, Ryzen 9 7900X (12 of 24 threads), 32 GB, CachyOS (Linux 7.3.3), headless sway
Scene: Castle Town Market, day, child Link; 2048x1152, the game's own upscaler, uncapped
Method: each session loads a save made at the Market gate, stands 10 s, runs 10 s to the fountain square, runs six figure-eights with the camera turning (60 s), then turns the camera for 10 s; frame times from
RX_VK_FRAME_LOG. 3 sessions a side, interleaved (with a third arm). Medians of the sessions, their range in brackets.RX_VK_REPLAY_PIN=0(3 sessions)So the change does help where the replay limits the frame: about +8 % running in the Market and fewer long frames; standing still it is about +4.5 %, not the +9 % the description says. The PC was busier than in the first round (other jobs, some swapping), so the absolute values are a little worse than the description's; the two arms were interleaved.
Still open from the description:
🤖 Generated with Claude Code