Repository navigation
Conversation
…orities - In WakeConditionVariable and WakeAllConditionVariable, check cv->waiters before invoking SYS_futex to avoid redundant kernel round-trips when no threads are waiting. - In WakeByAddressSingle, wake 1 waiter (val = 1) instead of waking INT_MAX waiters. - In SetThreadPriority and thread_entry, atomically store and apply thread priority/nice level upon thread creation so lowered priorities take effect even if SetThreadPriority is called before the thread finishes startup.
Joey-Magnus
left a comment
There was a problem hiding this comment.
The thread-priority part fixes a real gap: on main, a SetThreadPriority that runs before the new thread has its tid is stored and never applied. Three points:
host/posix/win32_posix.c:140:WakeByAddressSinglecan't wake just one waiter.WaitOnAddresswaits on the aligned 32-bit word that holds the address (see the comment above line 127), so waiters on different bytes of one word share a futex. Waking one can wake the wrong waiter, which re-checks and sleeps again, and the waiter it was meant for never wakes.INT_MAXwas deliberate. Nothing in host/ callsWaitOnAddresstoday, so there's no thundering herd to remove either. Please revert that line.apply_thread_priority(line 223): the setter stores the priority and then loads the tid;thread_entrystores the tid and then loads the priority. With release/acquire/relaxed ordering, both loads can see the old values (store-load reordering), so the priority can still be dropped in exactly the race this targets. Both stores and loads need__ATOMIC_SEQ_CST.- The
waiterscount looks correct, but it adds two atomic read-modify-writes to every wait to save a syscall on an uncontended signal. Do you have a measurement?
…ssSingle to INT_MAX
ghost
left a comment
There was a problem hiding this comment.
Thanks for the follow-up. Reviewed at 810e755. Two of @Joey-Magnus's points are fixed: WakeByAddressSingle is back to INT_MAX (the diff no longer touches it), and the priority store/load pairs are both __ATOMIC_SEQ_CST now. With that, the setter and thread_entry can't both miss each other: at least one of them sees the other's store, and if both do, setpriority just runs twice, which is harmless. The priority fix looks right to me.
The waiters count looks correct as well. The waiter loads seq and then increments waiters, and the waker increments seq and then loads waiters, all seq_cst. So if the waker reads 0, the waiter's FUTEX_WAIT sees the new seq and returns at once. No lost wakeup.
Still open:
- Joey's third question is unanswered: is there a measurement showing the skipped syscall is worth two atomic RMWs on every wait? The hot cases already avoid it:
nn_os_sync.ccounts mutex sleepers (mwait) and only wakes when someone is asleep. So it's not clear which CV is signalled often enough with no waiters for this to show up. If there's no number, I'd drop the CV half and keep this PR to the priority fix. - The description is out of date. It still lists the
WakeByAddressSinglechange and "prevents thundering herds" under Problem/Solution/Impact, but this head doesn't change that. Please update it. - CI is still pending at this head.
No file overlap with your other open PRs (#2462, #2467, #2469, #2482 are in gpu/vk, #2491 in host/mkw_glue.c).
| const int p = __atomic_load_n(&t->priority, __ATOMIC_SEQ_CST); | ||
| const int nice = p <= THREAD_PRIORITY_IDLE ? 19 : p <= THREAD_PRIORITY_LOWEST ? 10 : p == THREAD_PRIORITY_BELOW_NORMAL ? 5 : 0; | ||
| const pid_t tid = __atomic_load_n(&t->tid, __ATOMIC_SEQ_CST); | ||
| if (nice && (tid || t == self_thread)) setpriority(PRIO_PROCESS, tid ? (id_t)tid : 0, nice); |
There was a problem hiding this comment.
Nit: tid == 0 && t == self_thread can't happen. thread_entry stores the tid before it calls this, and thread_self() sets the tid before it sets self_thread. So this reduces to if (nice && tid) setpriority(PRIO_PROCESS, (id_t)tid, nice);.
There was a problem hiding this comment.
Good catch, simplified to if (nice && tid) setpriority(PRIO_PROCESS, (id_t)tid, nice); in commit 969bc80.
|
Thanks @hckrman101 and @Joey-Magnus for the detailed reviews and feedback! 1. Nit cleanup in
|
…the state files The ledger records the sync at upstream's 3e32949 (pull request 3), upstream's own CPU profile (NotRexed#2424) and the open pull requests found since: NotRexed#2470 (sound and the physics step above 60), NotRexed#2325 (the frame's chain of waits), NotRexed#2433 (fast float regions), NotRexed#2487 (wake-ups on Linux). correctness.json takes the water ripples as fixed upstream (NotRexed#2409), the Deku Tree platform's cause as reported in NotRexed#2470, and a new entry for the sound loss. Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
…the state files The ledger records the sync at upstream's 3e32949 (pull request 3), upstream's own CPU profile (NotRexed#2424) and the open pull requests found since: NotRexed#2470 (sound and the physics step above 60), NotRexed#2325 (the frame's chain of waits), NotRexed#2433 (fast float regions), NotRexed#2487 (wake-ups on Linux). correctness.json takes the water ripples as fixed upstream (NotRexed#2409), the Deku Tree platform's cause as reported in NotRexed#2470, and a new entry for the sound loss. Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
… base8, NotRexed#2487 already in resume9.sh died with the previous session; its gsched8 game was left running and was stopped. base8's renderer is built without its own profile or ThinLTO (work/ has no bunnyhood_vk.profdata), which session 7 measured at 1.5-2.5 ms less replay. NotRexed#2487's substance is in through 56facad. The dirty-page scan and the async replay's readback waits looked at again: neither is a lever here. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Joey-Magnus
left a comment
There was a problem hiding this comment.
All three of my points are addressed at 969bc80: WakeByAddressSingle is back to waking every waiter, the priority stores and loads in apply_thread_priority (host/posix/win32_posix.c:223) and thread_entry are seq_cst so the setter and the new thread can't both miss each other, and you answered the measurement question.
I merged it into current main and ran tests/run_all.py: all pass except test_lift_cache.py, which fails on main here too. The CI reds were main's at the time and are fixed there, so a re-run should clear them.
The waiters ordering is right: the waiter reads seq before counting itself and the waker bumps seq before reading the count, so a zero read means the waiter's FUTEX_WAIT sees the new seq and returns. Every CONDITION_VARIABLE is a host-side struct, so growing it to 8 bytes is safe. The ~26x is a microbenchmark of one uncontended wake, not a frame time from the game; please say so in the description rather than under Impact. Otherwise I'd merge it as is.
…the state files The ledger records the sync at upstream's 3e32949 (pull request 3), upstream's own CPU profile (NotRexed#2424) and the open pull requests found since: NotRexed#2470 (sound and the physics step above 60), NotRexed#2325 (the frame's chain of waits), NotRexed#2433 (fast float regions), NotRexed#2487 (wake-ups on Linux). correctness.json takes the water ripples as fixed upstream (NotRexed#2409), the Deku Tree platform's cause as reported in NotRexed#2470, and a new entry for the sound loss. Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
… base8, NotRexed#2487 already in resume9.sh died with the previous session; its gsched8 game was left running and was stopped. base8's renderer is built without its own profile or ThinLTO (work/ has no bunnyhood_vk.profdata), which session 7 measured at 1.5-2.5 ms less replay. NotRexed#2487's substance is in through 56facad. The dirty-page scan and the async replay's readback waits looked at again: neither is a lever here. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Problem
SetThreadPriorityimmediately afterCreateThreadwere dropped if the caller executed before the spawned thread assigned its OS TID inthread_entry. Because stores and loads betweenSetThreadPriorityandthread_entrylacked sequential consistency, store-load reordering could cause lowered priorities on background threads to be missed.WakeConditionVariableandWakeAllConditionVariableunconditionally invokedSYS_futexeven when no threads were blocked waiting on the condition variable, incurring kernel context switch overhead on frequent uncontended signal paths (e.g. event signals, message queue puts/takes, reader-writer lock releases).Solution
t->priorityandt->tidwith__ATOMIC_SEQ_CSTand apply thread priority from boththread_entryandSetThreadPriority, ensuring lowered priorities take effect reliably regardless of execution order between parent and child threads.waitersatomically onCONDITION_VARIABLEwith sequential consistency soWakeConditionVariableandWakeAllConditionVariableskip callingSYS_futexwhenwaiters == 0.Impact
tests/run_all.py(359 passed, 0 failed).