Repository navigation
Conversation
On Linux, sleeps under a millisecond spun on sched_yield. OoT's job wait (a 20 us SleepThread) ran 35,000 times a second on the Steam Deck, each worker 0.4-0.6 s a second in the kernel, and the APU's 15 W went to the CPU with the GPU held at ~220 MHz (Kokiri 6.6 fps; 7.9 with the timer). Linux's clock_nanosleep wakes within ~50 us, so short sleeps use it there; Windows keeps the yield loop. HOS_SHORT_SLEEP_TIMER=0 restores the yields on Linux. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017tiHucJGVjM9vhcGkzmPMu
Joey-Magnus
left a comment
There was a problem hiding this comment.
The measurements make a good case: on Linux the yield loop only burns CPU, and on the Deck that power comes out of the GPU's share. I'd like this fix in. But main changed this exact block an hour before the PR was opened, so it can't merge as it is.
blocking: merge conflict in host/hos_thread.c with #2263 (4d488bc). That PR moved the decision into hos_short_sleep_timer(asked, threads) and gave sleep_ns a guest argument. Short sleeps can now go three ways: all yield; the game's own sleeps (SleepThread, through hos_sleep_logged) wait on the timer while the host's yield; or all wait on the timer. git merge-tree against origin/main conflicts in this hunk. Rather than resolving the conflict line by line, I'd fold the Linux rule into that design. For example, on non-Windows, non-web builds, have the unset case return 1 (or 2, see below) instead of going through the processor count, and keep hos_short_sleep_timer the single place the choice is made.
blocking: host/hos_thread.c:874 reads the switch with atoi(getenv(...)) != 0. Since #2042, switches follow env_on/env_value_off, and HOS_SHORT_SLEEP_TIMER=on (or true/yes) would parse as 0 here and turn the timer off, the opposite of what was asked. Main's tests/test_short_sleep_timer.py has a broken variant for exactly this ("read as a number instead of by env_on's rule"). Reusing hos_short_sleep_timer fixes it for free.
should fix: decide on purpose which sleeps the timer applies to. As written, the PR puts every sleep under 1 ms on the Linux timer, including the host's: the frame limiter (host/nn_vi.c:30, :177) and the audio renderer (host/nn_audio.c:1168). #2263 deliberately kept those yielding so that pacing isn't late. With Linux's ~50 µs slack that may well be fine, but it is a separate change from the job-worker fix you measured. Either use mode 1 (only the game's sleeps on the timer), which covers f_800998a0, or keep mode 2 and say in the PR that frame pacing and audio were checked on the Deck.
should fix: tests. tests/test_short_sleep_timer.py forces _WIN32 on the cut code (line 53), so it would not see the Linux default at all. Please add cases for the non-Windows build: unset means the timer, and 0/off means yield. Also run that test against the rebased head. Right now CI has only run the title check; the other workflows are waiting for approval.
minor: the log line in main's version says "where Windows has the high-resolution one". If Linux now logs through the same path, word it for both platforms. The measurement covered the Deck only. A desktop Linux run with many threads (where #2263 found yielding helps TOTK-style 20 µs waits on Windows) would show whether "always the timer on Linux" or "the timer by processor count" is the better rule. The ~50 µs slack makes me expect the former, but it's worth one number.
Joey-Magnus
left a comment
There was a problem hiding this comment.
It conflicts in host/hos_thread.c with #2263's hos_short_sleep_timer, so it needs a rebase onto that. Two other things:
atoi(getenv("HOS_SHORT_SLEEP_TIMER")) != 0makes=onturn the timer off, whichtests/test_short_sleep_timer.pyexists to catch.- As written, the change also moves the frame limiter (
nn_vi.c) and audio (nn_audio.c) sleeps onto the timer, not just the job waits that were measured.
|
This now conflicts with main in |
Resolves host/hos_thread.c against main's processor-count choice (hos_short_sleep_timer): Windows keeps main's logic unchanged; elsewhere short sleeps use the timer by default, and HOS_SHORT_SLEEP_TIMER=0 restores the yields. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017tiHuZvvYyHV6LgKegyNrk
|
Merged main into the branch. |
|
Superseded by #2637 |
On Linux, the game's many short waits spin the CPU in the kernel. On the Steam Deck that takes the power the GPU needs: Kokiri Forest runs at 6.6 fps.
What changed: sleeps under 1 ms yield in a loop until the time, because Windows' timer is too coarse (~0.5 ms) for them. On Linux,
clock_nanosleepwakes within ~50 µs, so the loop only burned CPU. OoT's job wait (f_800998a0, a 20 µs SleepThread between checks) ran about 35,000 times a second, and each worker spent 0.4–0.6 s per second in the kernel with ~20,000 context switches. The Deck's APU shares 15 W, so the CPU took about 11.8 W and the GPU was held at ~220 MHz. Linux now sleeps short waits on the timer, and Windows keeps the yield loop.HOS_SHORT_SLEEP_TIMER=0restores the yields on Linux.Tested: Steam Deck LCD, Kokiri Forest, standing still, 50% resolution:
The rest of the gap is the Deck's power split: with the GPU clock set manually, the same build runs at 14.3 fps.
tests/run_all.py: no new failures againstmain.🤖 Generated with Claude Code