[PATCH] sched/fair: avoid redundant updates for unchanged default slices

From: Chengfeng Lin

Date: Sun Oct 18 2026 - 13:00:19 EST


For a fair task using the default slice, _sched_setscheduler() leaves
attr.sched_runtime at zero, while se.slice holds the actual duration.
Comparing them directly makes an unchanged sched_setparam() request take
the parameter-update path.

Compare default/custom mode before comparing the requested duration, as
suggested by Peter. For default-mode requests, also check that the task's
slice still matches the current default. Keep the update path outside the
fair class and when an implicit uclamp value needs resetting. Explicit
uclamp requests continue to use the existing update path.

This also handles two cases that an equal runtime alone does not cover:
a custom slice equal to the default must switch the task to custom mode,
and an unchanged legacy request may still need to reset an implicit clamp
after an RT-PI boost has ended.

On an i7-12700KF running v7.2, repeated sched_setparam(0, priority=0)
calls from a SCHED_OTHER, nice-0 task took 616.51 ns per call before this
change and 162.39 ns after it, saving 454.12 ns (73.66%). The existing
custom-slice fast path increased from 162.57 to 163.22 ns (0.40%).
These are syscall microbenchmark results, not an application speedup.

Link: https://lkml.iu.edu/2610.1/11150.html
Assisted-by: LLM
Signed-off-by: Chengfeng Lin <lin2530632123@xxxxxxxxx>
---
No code changes since the revision posted in the RFC thread.
It applies to tip/sched/core b99b7f60c5b4; runtime testing was on v7.2.

The original/revision/original runs used matching GCC 15.2 configurations,
CPU 2, full preemption, performance mode and turbo disabled. Each boot had
15 samples of 500,000 calls per case; baseline drift was below 0.18% and
within-run CV below 0.30%. Probes were used separately from timing.

This revision passed the default/custom mode-switching and implicit
uclamp-reset checks described above. Basic syscall checks and the RT/DL PI,
default-change, explicit-uclamp and bounded concurrent-default-change
controls also passed. A running sched_ext scheduler and all possible
races have not been tested.

Codex assisted with the code, tests, analysis and submission text. The
performance figures are from recorded bare-metal runs.

Reproducer and per-batch results:
https://github.com/lcf0399/linux-regression-evidence/tree/a233ed8edb1e91a6a0ea0d326668bb1f52384489/kernel/sched/sched-fair-default-slice-noop

kernel/sched/syscalls.c | 35 +++++++++++++++++++++++++++++++++--
1 file changed, 33 insertions(+), 2 deletions(-)

diff --git a/kernel/sched/syscalls.c b/kernel/sched/syscalls.c
--- a/kernel/sched/syscalls.c
+++ b/kernel/sched/syscalls.c
@@ -488,6 +488,38 @@
return -EPERM;

return 0;
+}
+
+static bool fair_params_changed(struct task_struct *p,
+ const struct sched_attr *attr)
+{
+ if (attr->sched_nice != task_nice(p))
+ return true;
+
+ /* Keep the full update path for PI-boosted and sched_ext tasks. */
+ if (p->sched_class != &fair_sched_class)
+ return true;
+
+ if (!!attr->sched_runtime != !!p->se.custom_slice)
+ return true;
+
+ if (attr->sched_runtime && attr->sched_runtime != p->se.slice)
+ return true;
+
+ if (!attr->sched_runtime &&
+ p->se.slice != READ_ONCE(sysctl_sched_base_slice))
+ return true;
+
+#ifdef CONFIG_UCLAMP_TASK
+ /* Equal fair parameters do not rule out an implicit clamp reset. */
+ if ((!p->uclamp_req[UCLAMP_MIN].user_defined &&
+ p->uclamp_req[UCLAMP_MIN].value != uclamp_none(UCLAMP_MIN)) ||
+ (!p->uclamp_req[UCLAMP_MAX].user_defined &&
+ p->uclamp_req[UCLAMP_MAX].value != uclamp_none(UCLAMP_MAX)))
+ return true;
+#endif
+
+ return false;
}

int __sched_setscheduler(struct task_struct *p,
@@ -589,8 +621,7 @@
*/
if (unlikely(policy == p->policy)) {
if (fair_policy(policy) &&
- (attr->sched_nice != task_nice(p) ||
- (attr->sched_runtime != p->se.slice)))
+ fair_params_changed(p, attr))
goto change;
if (rt_policy(policy) && attr->sched_priority != p->rt_priority)
goto change;