When the scheduler you expected is not the scheduler you have

A program is not just code — it is code shaped around a particular scheduler. The thread priorities it sets, the spin-loops it tolerates, the places it yields and the places it does not: all of these reflect decisions made against a target operating system's scheduling behaviour. Run that program through a compatibility layer and the syscall boundary passes the thread calls across, but the semantics on the other side may be quietly different.

The scheduling difference in numbers

FROM THIS ENTRY
Windows default thread quantumapproximately 15 ms on a desktop; shorter with foreground boost active
Linux CFStracks virtual runtime for proportional distribution, not fixed quanta
Windows priority classesnamed constants mapping to numerical values with direct preemption influence; POSIX priorities require elevated capabilities to raise on Linux

The gap that looks like nothing

Windows uses a priority-based preemptive scheduler with a default quantum of roughly 15 milliseconds on a desktop build — shorter when foreground-boost is active. Linux's Completely Fair Scheduler operates on a different model entirely: it tracks virtual runtime and aims to distribute CPU time proportionally rather than by fixed quanta. A program that spins briefly on a mutex because experience says the owning thread will be rescheduled within a few milliseconds may spin far longer under CFS, burning a core and starving something else. The compatibility layer translated the call correctly; the problem is in what the program assumed about the answer.

Thread priority is a related trap. Windows exposes a range of named priority classes that map onto numerical values and influence preemption directly. POSIX priorities interact differently with the scheduler, and unprivileged processes on Linux cannot raise their own scheduling priority without elevated capabilities. A game that bumps its render thread to THREAD_PRIORITY_HIGHEST may find that call silently succeeding — the layer accepts it, converts it, passes it on — while the actual effect on CPU time is negligible. No error is returned. The frame-time consequence shows up only in a profiler.

Futex behaviour adds another layer. Windows WaitOnAddress and Linux futexes are conceptually similar but not identical in their spurious-wakeup and ordering guarantees. A translation layer implements the mapping as faithfully as it can, but subtly different wake ordering under contention can shift which thread runs next, turning a tuning decision into a latency source that is genuinely hard to isolate.

None of this is mistranslation in the strict sense. The API calls are handled; the contracts are met. The invisible assumption — that the machine underneath will schedule the way the machine it was written for scheduled — is simply no longer true. That gap does not announce itself. It accumulates in where the milliseconds go.

A bench power supply and meter beside an open machine
FIG. 2Supply and meter beside the machine: absolute numbers first, percentages only once the baseline is stated.
A bare processor die and socket pins, macro
FIG. 3The contract in physical form: pins are the ABI — position, order and width, with no names attached.PHOTO: PIXABAY / PEXELS
FILED UNDER: THE BOUNDARYSHORT ENTRY