Files
scx_soryu/docs/boost-on-wake.md
2026-09-04 23:53:20 +03:00

4.1 KiB

--boost-on-wake: the typo wins

BMQ's sched_task_ttwu() is meant to boost a waking task in proportion to how long it slept. That is what makes sleeping for 1 us worth nothing and closes the sleep-gaming hole. The line reads

s64 delta = this_rq()->clock_task > p->last_ran;

A comparison, not a subtraction. delta is 0 or 1, the shift takes it to 0, and the function has been dead code in every shipped BMQ build. soryu made it a knob rather than silently picking a side: --boost-on-wake=true is what BMQ meant to do, false is what BMQ actually does.

Result: off is better, and it is not close.

What was run

The scheduler compiling itself, twice per configuration, on the Ivy Bridge target (i5-3210M, 2c/4t, kernel 7.1.6). Each run logged one report every 10 s with --report-sec 10, giving ~34 intervals per run. Rounds 1 and 2 were run in separate working trees.

Every number below is per-interval, not cumulative. That matters: before the histograms were differenced, a run printed one figure blended across every phase the build went through, which described none of them.

Latency

Interactive (boosted class) wake->run, median across intervals:

p99 p99.9 tail >1 ms
on, round 1 524 us 1.30 ms 0.152%
on, round 2 524 us 1.80 ms 0.168%
off, round 1 459 us 0.92 ms 0.075%
off, round 2 459 us 0.92 ms 0.077%

The two off runs agree to the last digit on all three columns.

Pairing the runs interval by interval on the >1 ms tail:

intervals off better on better sign test median ratio
round 1 34 31 3 p = 7.7e-07 0.53
round 2 34 32 2 p = 6.9e-08 0.45
pooled 68 63 5 p = 7.7e-14 0.47

Worst single interval, which is the number a stutter would live in:

worst tail >1 ms worst p99.9 worst max_wait
on, round 1 0.299% 4.2 ms 21 j
on, round 2 1.787% 21.0 ms 38 j
off, round 1 0.656% 7.3 ms 22 j
off, round 2 0.129% 0.92 ms 21 j

Each configuration produced one bad round, and on's was worse: two consecutive intervals at 21 ms p99.9.

Throughput

round 1 round 2
on 5 m 34 s 5 m 45 s
off 5 m 39 s 5 m 32 s

The ordering flips between rounds. With n = 2 per configuration and a ~10 s spread already known from earlier runs, there is no throughput difference to report in either direction. The latency result stands on 68 paired intervals; the timing result stands on nothing, and is not claimed.

Health

insert_fail = 0, starve_serve 22-26 across all four runs, max_wait 21-22 jiffies except the one 38 j interval noted above. No stalls, no ejections.

Why the default flipped

Two independent arguments point the same way. The measurement says off. And off is BMQ as actually shipped -- the behaviour a decade of field use already ran, rather than a resurrection nobody has tested. The knob stays so the question can be reopened on other hardware.

Open question

The mechanism is not understood. The wake boost grants up to 24 steps at once, so a waking task is slammed to the floor rather than climbing at +1 per sleep, and the guess is that this makes a task's level change often enough that cpu_level[] is stale when try_preempt() reads it. That is a guess. It would be tested with a per-task counter of how often the level computed at enqueue differs from the one seen at ops.running.

Note this is not the boost distribution collapsing. Under compile load both configurations sit bimodal at roughly 54% on the -12 floor and 39% on the +12 ceiling, with about 5% spread across the twenty-three values between, and preempt_none stays near 1.2% in both.

That bimodality was expected to disable preemption -- if everything sits at one level, nothing outranks anything and try_preempt() finds nobody worth an IPI. It does not, because the range saturates at both ends rather than one: the crowd on the floor always has a ceiling hog to displace. Saturation at one end would have killed preemption; saturation at both preserves it.