* [PATCH 00/18 v3] Improving latency of short slice tasks
@ 2026-11-06 15:19 Vincent Guittot
2026-11-06 15:19 ` [PATCH 01/20 v3] sched/eevdf: Decay positive lag of sleeping entities Vincent Guittot
` (19 more replies)
0 siblings, 20 replies; 24+ messages in thread
From: Vincent Guittot @ 2026-11-06 15:19 UTC (permalink / raw)
To: mingo, peterz, juri.lelli, dietmar.eggemann, rostedt, bsegall,
mgorman, vschneid, kprateek.nayak, linux-kernel, lukasz.luba,
rafael, linux-pm, tj, void, arighi, changwoo, sched-ext
Cc: qyousef, christian.loehle, pierre.gondois, sshegde, kayracizmeci,
tim.c.chen, Vincent Guittot
This is another round of scheduling latency improvements which fix
some remaining corner cases and start to fix somes cases when multi
short slice tasks are running simultaneously on the system.
This patchset gathers 5 different parts:
- Patch 1-4 implement decay of positive lag
- Patch 5-6 take into account slice when selecting CPU
- Patch 7-12 add push callback for fair
- Patch 13-15 use push for migrating short slice task that failed
to run at enqueue
- Patch 16-19 modify feec to take into account slice when selecting CPU
- Patch 20 handles case where several tasks select the same idle CPU
I run my usual set of scheduling latency tests on dragonboard rb5
- cyclictest with a 3777us period and a 8ms slice alone
- cyclictest with a 3777us period and a 8ms slice. 2xNR_CPUS rt-app
tasks that run (8177us) and sleep (17777us) with a 16ms slice.
- cyclictest with a 3777us period and a 8ms slice. Hackbench with
1 group using thread and pipe and a 16ms slice.
NB: periods and run duration have been chosen to minimize alignment
with tick or other periodic activities.
Each test is 130 seconds long
scheduling latency (us) for cyclictest
tip/sched/core| this patchset
slice 8ms | 8ms
99th Percentile 91 | 92 (- 1 %)
99.9th Percentile 128 | 161 (-26 %)
Maximum 273 | 223 (+18 %)
scheduling latency (us) for cyclictest and rt-app
tip/sched/core| this patchset
slice 8ms / 16ms | 8ms / 16 ms
99th Percentile 65 | 68 (- 5 %)
99.9th Percentile 986 | 913 (+ 7 %)
Maximum 6229 | 5715 (+ 8 %)
scheduling latency (us) for cyclictest and hackbench
tip/sched/core| this patchset
slice 8ms / 16ms | 8ms / 16 ms
99th Percentile 74 | 90 (-22 %)
99.9th Percentile 643 | 997 (-55 %)
Maximum 13501 | 4454 (+67 %)
The 99.9th percentile are a bit worse mainly because of the decay/reset of
positive lag. Eternal positive lag was beneficial for cyclictest which
doesn't do anything while running and slowly decrease its large positive
lag. Now its positive lag elapses while sleeping and cyclictest is
enqueued with a null lag most of the time. On the other side this reduces
cases where long sleeping task with large positive lag can preempt or
prevent short task to run first.
For testing cases w/ multi short slice tasks, I run a new rt-app test that
wakes up simultaneously 4 short slice tasks while 16 normal tasks are
also enqueued. All tasks wants to run 1ms every 7777ms. The 4 short tasks
have an uclamp min of 512 to target the high and mid cores on my system
(1 task per core)
(SIS_UTIL is not disabled unlike for v2)
tip/sched/core
Task-O Task-1 Task-2 Task-3
Average | 1051 52 1051 1093
Median (P50) | 1091 53 1091 1092
90th Percentile | 2133 53 2132 2133
99th Percentile | 2400 53 2398 2394
99.9th Percentile | 2519 120 2518 2408
Maximum | 2523 1327 3097 2415
+ The patchset
Task-O Task-1 Task-2 Task-3
Average | 61 61 60 61
Median (P50) | 60 60 60 60
90th Percentile | 62 62 62 62
99th Percentile | 62 62 62 62
99.9th Percentile | 1839 2012 63 2175
Maximum | 2175 2066 2176 2689
On tip/sched/core, the 4 tasks tends to wake up on the same CPU whereas
this patchset pushes the tasks which aren't picked on another CPU.
Beside the results above I noticed significants performance improvements
for hackbench with pipe which were not expected.
"sched/eevdf: Decay positive lag of sleeping entities" is the patch that
provides most of the performance improvements
The test were run with the default 2.8ms slice to check for some
performance regressions
hackbench tip/sched/core this patchset
1 group process pipe 0,862(+/-3.9%) 0,839(+/-1.3%) (+ 3%)
4 group process pipe 0,725(+/-1.5%) 0,593(+/-1.3%) (+18%)
8 group process pipe 0,640(+/-4.2%) 0,546(+/-2.2%) (+15%)
16 group process pipe 0,620(+/-3.9%) 0,528(+/-0.8%) (+15%)
1 group thread pipe 0,917(+/-2.8%) 0,907(+/-2.2%) (+ 1%)
4 group thread pipe 0,838(+/-4.5%) 0,697(+/-3.6%) (+17%)
8 group thread pipe 0,774(+/-1.5%) 0,583(+/-2.5%) (+25%)
16 group thread pipe 0,628(+/-2.9%) 0,548(+/-2.1%) (+13%)
Those tests have been run with perf scheduler (Using schedutil and EAS
provides similar results)
Changes since v2:
- Remove min_slice comparison during wake_affine which didn't provide
benefit at the end
- Added reset lag when migrating
- Fixing stacking on idle cpu case
- Tried to cover all the comments I received in v2 and more while running
more test and reviewing the changes
Changes since v1:
- Change decay_entity_lag to take into account cfs load but not
migrated task yet.
- Reset lag if the CPU entered idle while task was sleeping.
- Move lockless min_slice in struct rq and use usigned long
which is enough for slice which stays in range [100us:100ms]
- Merge min_slice rq selection in select_idle_capacity() and
select_idle_cpu()
- Add push callback mecanism for cfs
- Push short slice tasks that are not selected at wakeup
- Update feec() to take into accoun slice when selecting a CPU
Vincent Guittot (20):
sched/eevdf: Decay positive lag of sleeping entities
sched/eevdf: Reset lag at wakeup when prev CPU has been idle
sched/eevdf: Reset lag at wakeup when migrating and prev has been idle
sched/eevdf: Reset lag when waking up on idle cpu
sched/eevdf: Add per cpu cached min_slice
sched/eevdf: Add min slice check when selecting CPU
sched/fair: Prepare select_task_rq_fair() to be called for new cases
sched/fair: Add push task mechanism for fair
sched/fair: Optimize push task mechanism for fair
sched/core: Add rq flag to tick parameters
sched/fair: Add force push task mechanism for fair
sched/fair: Support not wakeup case in select_idle_sibling
sched/eevdf: Try to push short slice task on a better CPU
sched/eevdf: Push short slice task that are not picked
sched/fair: Enable push task for preempt short
energy model: Add a get previous state function
sched/fair: Rework feec() to use cost instead of spare capacity
energy model: Remove unused em_cpu_energy()
sched/fair: Take into account slice in EAS
sched/fair: Fix tasks stacking on same idle CPU
include/linux/energy_model.h | 109 +--
include/linux/sched.h | 1 +
kernel/sched/core.c | 8 +-
kernel/sched/deadline.c | 2 +-
kernel/sched/ext/ext.c | 2 +-
kernel/sched/fair.c | 1244 ++++++++++++++++++++++++----------
kernel/sched/idle.c | 2 +-
kernel/sched/rt.c | 2 +-
kernel/sched/sched.h | 8 +-
kernel/sched/stop_task.c | 2 +-
kernel/sched/topology.c | 3 +
11 files changed, 938 insertions(+), 445 deletions(-)
--
2.53.0
^ permalink raw reply [flat|nested] 24+ messages in thread
* [PATCH 01/20 v3] sched/eevdf: Decay positive lag of sleeping entities
2026-11-06 15:19 [PATCH 00/18 v3] Improving latency of short slice tasks Vincent Guittot
@ 2026-11-06 15:19 ` Vincent Guittot
2026-11-06 15:19 ` [PATCH 02/20 v3] sched/eevdf: Reset lag at wakeup when prev CPU has been idle Vincent Guittot
` (18 subsequent siblings)
19 siblings, 0 replies; 24+ messages in thread
From: Vincent Guittot @ 2026-11-06 15:19 UTC (permalink / raw)
To: mingo, peterz, juri.lelli, dietmar.eggemann, rostedt, bsegall,
mgorman, vschneid, kprateek.nayak, linux-kernel, lukasz.luba,
rafael, linux-pm, tj, void, arighi, changwoo, sched-ext
Cc: qyousef, christian.loehle, pierre.gondois, sshegde, kayracizmeci,
tim.c.chen, Vincent Guittot
Similarly to delayed dequeue, which enables an entity to decay its negative
lag while sleeping, a task should not keep a positive lag forever.
The sleep duration and the weight are used to decay the positive lag at
wakeup.
Apply the decay when enqueueing on prev rq for which we hold the lock to
get rq_clock_task().
Signed-off-by: Vincent Guittot <vincent.guittot@linaro.org>
---
kernel/sched/fair.c | 124 ++++++++++++++++++++++++++++++++------------
1 file changed, 90 insertions(+), 34 deletions(-)
diff --git a/kernel/sched/fair.c b/kernel/sched/fair.c
index 0feec172fd37..5cd171f830d8 100644
--- a/kernel/sched/fair.c
+++ b/kernel/sched/fair.c
@@ -897,6 +897,91 @@ bool update_entity_lag(struct cfs_rq *cfs_rq, struct sched_entity *se)
return avruntime - vlag != se->vruntime;
}
+/*
+ * Signed add and clamp on underflow.
+ *
+ * Explicitly do a load-store to ensure the intermediate value never hits
+ * memory. This allows lockless observations without ever seeing the negative
+ * values.
+ */
+#define add_positive(_ptr, _val) do { \
+ typeof(_ptr) ptr = (_ptr); \
+ __signed_scalar_typeof(*ptr) val = (_val); \
+ typeof(*ptr) res, var = READ_ONCE(*ptr); \
+ \
+ res = var + val; \
+ \
+ if (val < 0 && res > var) \
+ res = 0; \
+ \
+ WRITE_ONCE(*ptr, res); \
+} while (0)
+
+/*
+ * Remove and clamp on negative, from a local variable.
+ *
+ * A variant of sub_positive(), which does not use explicit load-store
+ * and is thus optimized for local variable updates.
+ */
+#define lsub_positive(_ptr, _val) do { \
+ typeof(_ptr) ptr = (_ptr); \
+ *ptr -= min_t(typeof(*ptr), *ptr, _val); \
+} while (0)
+
+static inline unsigned long cfs_rq_load_avg(struct cfs_rq *cfs_rq);
+static unsigned long task_h_load(struct task_struct *p);
+
+static __always_inline
+void decay_entity_lag(struct cfs_rq *cfs_rq, struct task_struct *p, int flags)
+{
+ struct sched_entity *se = &p->se;
+ s64 delta_exec, vlag = se->vlag;
+ unsigned long cfs_load;
+ struct rq *rq;
+
+ WARN_ON_ONCE(se->on_rq);
+
+ /* Negative lag is managed by delayed dequeue path */
+ if (vlag <= 0)
+ return;
+
+ rq = rq_of(cfs_rq);
+
+ /* No fast access to prev rq task clock */
+ if (flags & ENQUEUE_MIGRATED)
+ return;
+
+ /* Compute sleep time */
+ delta_exec = rq_clock_task(rq) - se->exec_start;
+ if (unlikely(delta_exec <= 0))
+ return;
+
+ /* For anything above ~4 seconds, save computation and clear the lag */
+ if (unlikely(delta_exec >> 32)) {
+ se->vlag = 0;
+ return;
+ }
+
+ /*
+ * Use cfs' load and p's weight to estimate what would have been the
+ * share for p. task's load contribution to cfs' load is replaced by
+ * its weight.
+ */
+ cfs_load = cfs_rq_load_avg(cfs_rq);
+ lsub_positive(&cfs_load, task_h_load(p));
+ if (cfs_load) {
+ unsigned long weight = scale_load_down(se->h_load.weight);
+
+ delta_exec *= weight;
+ delta_exec = div64_long(delta_exec, cfs_load + weight);
+ }
+
+ vlag -= calc_delta_fair(delta_exec, se);
+
+ /* vlag can't become neg while sleeping */
+ se->vlag = max(0, vlag);
+}
+
/*
* Entity is eligible once it received less service than it ought to have,
* eg. lag >= 0.
@@ -4693,38 +4778,6 @@ account_entity_dequeue(struct cfs_rq *cfs_rq, struct sched_entity *se)
cfs_rq->nr_queued--;
}
-/*
- * Signed add and clamp on underflow.
- *
- * Explicitly do a load-store to ensure the intermediate value never hits
- * memory. This allows lockless observations without ever seeing the negative
- * values.
- */
-#define add_positive(_ptr, _val) do { \
- typeof(_ptr) ptr = (_ptr); \
- __signed_scalar_typeof(*ptr) val = (_val); \
- typeof(*ptr) res, var = READ_ONCE(*ptr); \
- \
- res = var + val; \
- \
- if (val < 0 && res > var) \
- res = 0; \
- \
- WRITE_ONCE(*ptr, res); \
-} while (0)
-
-/*
- * Remove and clamp on negative, from a local variable.
- *
- * A variant of sub_positive(), which does not use explicit load-store
- * and is thus optimized for local variable updates.
- */
-#define lsub_positive(_ptr, _val) do { \
- typeof(_ptr) ptr = (_ptr); \
- *ptr -= min_t(typeof(*ptr), *ptr, _val); \
-} while (0)
-
-
/*
* Because of rounding, se->util_sum might ends up being +1 more than
* cfs->util_sum. Although this is not a problem by itself, detaching
@@ -8152,7 +8205,7 @@ static void
enqueue_task_fair(struct rq *rq, struct task_struct *p, int flags)
{
int rq_h_nr_queued = rq->cfs.h_nr_queued;
- int task_new = !(flags & ENQUEUE_WAKEUP);
+ int task_wake = flags & ENQUEUE_WAKEUP;
struct sched_entity *se = &p->se;
struct cfs_rq *cfs_rq = &rq->cfs;
unsigned long weight;
@@ -8185,6 +8238,9 @@ enqueue_task_fair(struct rq *rq, struct task_struct *p, int flags)
if (p->in_iowait)
cpufreq_update_util(rq, SCHED_CPUFREQ_IOWAIT);
+ if (task_wake)
+ decay_entity_lag(cfs_rq, p, flags);
+
curr = (cfs_rq->curr == se);
/*
* This can actually happen with CFS_BANDWIDTH, a task can be throttled
@@ -8225,7 +8281,7 @@ enqueue_task_fair(struct rq *rq, struct task_struct *p, int flags)
* into account, but that is not straightforward to implement,
* and the following generally works well enough in practice.
*/
- if (!task_new)
+ if (task_wake)
check_update_overutilized_status(rq);
assert_list_leaf_cfs_rq(rq);
--
2.53.0
^ permalink raw reply [flat|nested] 24+ messages in thread
* [PATCH 02/20 v3] sched/eevdf: Reset lag at wakeup when prev CPU has been idle
2026-11-06 15:19 [PATCH 00/18 v3] Improving latency of short slice tasks Vincent Guittot
2026-11-06 15:19 ` [PATCH 01/20 v3] sched/eevdf: Decay positive lag of sleeping entities Vincent Guittot
@ 2026-11-06 15:19 ` Vincent Guittot
2026-11-06 16:22 ` Kayra Cizmeci
2026-11-06 15:19 ` [PATCH 03/20 v3] sched/eevdf: Reset lag at wakeup when migrating and prev " Vincent Guittot
` (17 subsequent siblings)
19 siblings, 1 reply; 24+ messages in thread
From: Vincent Guittot @ 2026-11-06 15:19 UTC (permalink / raw)
To: mingo, peterz, juri.lelli, dietmar.eggemann, rostedt, bsegall,
mgorman, vschneid, kprateek.nayak, linux-kernel, lukasz.luba,
rafael, linux-pm, tj, void, arighi, changwoo, sched-ext
Cc: qyousef, christian.loehle, pierre.gondois, sshegde, kayracizmeci,
tim.c.chen, Vincent Guittot
When prev CPU entered idle while a task was sleeping, we assume that the
latter received enough running time and the lag can be cleared similarly to
what happens for a negative lag.
Track CPU's idle phase and save it when a task goes to sleep. Then check if
the local CPU entered idle while the task was sleeping when enqueuing the
waking task.
This only applies if the task didn't migrate at wakeup.
Suggested-by: Peter Zijlstra (Intel) <peterz@infradead.org>
Signed-off-by: Vincent Guittot <vincent.guittot@linaro.org>
---
include/linux/sched.h | 1 +
kernel/sched/fair.c | 14 ++++++++++++--
kernel/sched/sched.h | 1 +
3 files changed, 14 insertions(+), 2 deletions(-)
diff --git a/include/linux/sched.h b/include/linux/sched.h
index 0cb03f9acfe3..3d31e9a698c3 100644
--- a/include/linux/sched.h
+++ b/include/linux/sched.h
@@ -593,6 +593,7 @@ struct sched_entity {
u64 vruntime;
/* Approximated virtual lag: */
s64 vlag;
+ u32 vlag_seq;
/* 'Protected' deadline, to give out minimum quantums: */
u64 vprot;
u64 slice;
diff --git a/kernel/sched/fair.c b/kernel/sched/fair.c
index 5cd171f830d8..79f53811274e 100644
--- a/kernel/sched/fair.c
+++ b/kernel/sched/fair.c
@@ -893,6 +893,7 @@ bool update_entity_lag(struct cfs_rq *cfs_rq, struct sched_entity *se)
vlag = min(vlag, 0);
}
se->vlag = vlag;
+ se->vlag_seq = cfs_rq->idle_seq;
return avruntime - vlag != se->vruntime;
}
@@ -945,12 +946,18 @@ void decay_entity_lag(struct cfs_rq *cfs_rq, struct task_struct *p, int flags)
if (vlag <= 0)
return;
- rq = rq_of(cfs_rq);
-
/* No fast access to prev rq task clock */
if (flags & ENQUEUE_MIGRATED)
return;
+ /* CPU has been idle in between so the lag has been removed */
+ if (se->vlag_seq != cfs_rq->idle_seq) {
+ se->vlag = 0;
+ return;
+ }
+
+ rq = rq_of(cfs_rq);
+
/* Compute sleep time */
delta_exec = rq_clock_task(rq) - se->exec_start;
if (unlikely(delta_exec <= 0))
@@ -8376,6 +8383,9 @@ static bool __dequeue_task(struct rq *rq, struct task_struct *p, int flags)
dequeue_hierarchy(p, flags);
+ if (!(flags & DEQUEUE_SAVE) && !cfs_rq->h_nr_queued)
+ cfs_rq->idle_seq++;
+
if (sched_feat(PLACE_REL_DEADLINE) && !task_sleep) {
se->deadline -= se->vruntime;
se->rel_deadline = 1;
diff --git a/kernel/sched/sched.h b/kernel/sched/sched.h
index 7d2ec527b8a2..24f9b7bd8f0f 100644
--- a/kernel/sched/sched.h
+++ b/kernel/sched/sched.h
@@ -689,6 +689,7 @@ struct cfs_rq {
u64 sum_weight;
u64 zero_vruntime;
unsigned int sum_shift;
+ u32 idle_seq;
#ifdef CONFIG_SCHED_CORE
unsigned int forceidle_seq;
--
2.53.0
^ permalink raw reply [flat|nested] 24+ messages in thread
* [PATCH 03/20 v3] sched/eevdf: Reset lag at wakeup when migrating and prev has been idle
2026-11-06 15:19 [PATCH 00/18 v3] Improving latency of short slice tasks Vincent Guittot
2026-11-06 15:19 ` [PATCH 01/20 v3] sched/eevdf: Decay positive lag of sleeping entities Vincent Guittot
2026-11-06 15:19 ` [PATCH 02/20 v3] sched/eevdf: Reset lag at wakeup when prev CPU has been idle Vincent Guittot
@ 2026-11-06 15:19 ` Vincent Guittot
2026-11-06 18:05 ` Kayra Cizmeci
2026-11-06 15:19 ` [PATCH 04/20 v3] sched/eevdf: Reset lag when waking up on idle cpu Vincent Guittot
` (16 subsequent siblings)
19 siblings, 1 reply; 24+ messages in thread
From: Vincent Guittot @ 2026-11-06 15:19 UTC (permalink / raw)
To: mingo, peterz, juri.lelli, dietmar.eggemann, rostedt, bsegall,
mgorman, vschneid, kprateek.nayak, linux-kernel, lukasz.luba,
rafael, linux-pm, tj, void, arighi, changwoo, sched-ext
Cc: qyousef, christian.loehle, pierre.gondois, sshegde, kayracizmeci,
tim.c.chen, Vincent Guittot
When prev CPU entered idle while a task was sleeping, we assume that the
latter received enough running time and the lag can be cleared similarly to
what happens for a negative lag.
While migrating the waking task to a new cpu, Check if prev CPU entered
idle in the meantime.
Signed-off-by: Vincent Guittot <vincent.guittot@linaro.org>
---
kernel/sched/fair.c | 27 +++++++++++++++++++++++++++
1 file changed, 27 insertions(+)
diff --git a/kernel/sched/fair.c b/kernel/sched/fair.c
index 79f53811274e..945de0215abb 100644
--- a/kernel/sched/fair.c
+++ b/kernel/sched/fair.c
@@ -932,6 +932,27 @@ bool update_entity_lag(struct cfs_rq *cfs_rq, struct sched_entity *se)
static inline unsigned long cfs_rq_load_avg(struct cfs_rq *cfs_rq);
static unsigned long task_h_load(struct task_struct *p);
+static __always_inline
+void entity_clear_lag(struct task_struct *p)
+{
+ struct sched_entity *se;
+ struct cfs_rq *cfs_rq;
+
+ if (READ_ONCE(p->__state) != TASK_WAKING)
+ return;
+
+ se = &p->se;
+ /* Negative lag implies delayed dequeue */
+ if (se->vlag <= 0)
+ return;
+
+ cfs_rq = &task_rq(p)->cfs;
+
+ /* CPU has been idle in between so the lag has been removed */
+ if (se->vlag_seq != READ_ONCE(cfs_rq->idle_seq))
+ se->vlag = 0;
+}
+
static __always_inline
void decay_entity_lag(struct cfs_rq *cfs_rq, struct task_struct *p, int flags)
{
@@ -10049,6 +10070,12 @@ static void migrate_task_rq_fair(struct task_struct *p, int new_cpu)
struct sched_entity *se = &p->se;
if (!task_on_rq_migrating(p)) {
+ /*
+ * Clear the positive lag of a waking task before migrating to
+ * the new cpu.
+ */
+ entity_clear_lag(p);
+
remove_entity_load_avg(se);
/*
--
2.53.0
^ permalink raw reply [flat|nested] 24+ messages in thread
* [PATCH 04/20 v3] sched/eevdf: Reset lag when waking up on idle cpu
2026-11-06 15:19 [PATCH 00/18 v3] Improving latency of short slice tasks Vincent Guittot
` (2 preceding siblings ...)
2026-11-06 15:19 ` [PATCH 03/20 v3] sched/eevdf: Reset lag at wakeup when migrating and prev " Vincent Guittot
@ 2026-11-06 15:19 ` Vincent Guittot
2026-11-06 18:39 ` Kayra Cizmeci
2026-11-06 15:19 ` [PATCH 05/20 v3] sched/eevdf: Add per cpu cached min_slice Vincent Guittot
` (15 subsequent siblings)
19 siblings, 1 reply; 24+ messages in thread
From: Vincent Guittot @ 2026-11-06 15:19 UTC (permalink / raw)
To: mingo, peterz, juri.lelli, dietmar.eggemann, rostedt, bsegall,
mgorman, vschneid, kprateek.nayak, linux-kernel, lukasz.luba,
rafael, linux-pm, tj, void, arighi, changwoo, sched-ext
Cc: qyousef, christian.loehle, pierre.gondois, sshegde, kayracizmeci,
tim.c.chen, Vincent Guittot
When several tasks wake up simultaneously on an idle CPU but their prev
CPUs were not idle, their final vlag will depend on the enqueue ordering
as the first one will lose its lag but not the next ones.
Reset the lag during the enqueue if no fair task has already been picked
and set as the running task.
As a typical example:
- CPU0 is idle
- TA with vlag 0ms and TB with vlag 5ms wake up on CPU0 simultaneously.
Depending which task grabs the lock 1st, the behavior will be different:
- If TA is enqueued 1st, TB will be enqueued with a positive lag and will
be picked 1st.
- If TB is enqueued 1st, it will lose its positive vlag and both TA and
TB will have 0 vlag when eevdf picks next task.
Signed-off-by: Vincent Guittot <vincent.guittot@linaro.org>
---
kernel/sched/fair.c | 10 ++++++++--
1 file changed, 8 insertions(+), 2 deletions(-)
diff --git a/kernel/sched/fair.c b/kernel/sched/fair.c
index 945de0215abb..6ce4162a7703 100644
--- a/kernel/sched/fair.c
+++ b/kernel/sched/fair.c
@@ -967,6 +967,14 @@ void decay_entity_lag(struct cfs_rq *cfs_rq, struct task_struct *p, int flags)
if (vlag <= 0)
return;
+ rq = rq_of(cfs_rq);
+
+ /* You can't claim any lag when waking on idle CPU */
+ if (rq->curr == rq->idle) {
+ se->vlag = 0;
+ return;
+ }
+
/* No fast access to prev rq task clock */
if (flags & ENQUEUE_MIGRATED)
return;
@@ -977,8 +985,6 @@ void decay_entity_lag(struct cfs_rq *cfs_rq, struct task_struct *p, int flags)
return;
}
- rq = rq_of(cfs_rq);
-
/* Compute sleep time */
delta_exec = rq_clock_task(rq) - se->exec_start;
if (unlikely(delta_exec <= 0))
--
2.53.0
^ permalink raw reply [flat|nested] 24+ messages in thread
* [PATCH 05/20 v3] sched/eevdf: Add per cpu cached min_slice
2026-11-06 15:19 [PATCH 00/18 v3] Improving latency of short slice tasks Vincent Guittot
` (3 preceding siblings ...)
2026-11-06 15:19 ` [PATCH 04/20 v3] sched/eevdf: Reset lag when waking up on idle cpu Vincent Guittot
@ 2026-11-06 15:19 ` Vincent Guittot
2026-11-06 15:19 ` [PATCH 06/20 v3] sched/eevdf: Add min slice check when selecting CPU Vincent Guittot
` (14 subsequent siblings)
19 siblings, 0 replies; 24+ messages in thread
From: Vincent Guittot @ 2026-11-06 15:19 UTC (permalink / raw)
To: mingo, peterz, juri.lelli, dietmar.eggemann, rostedt, bsegall,
mgorman, vschneid, kprateek.nayak, linux-kernel, lukasz.luba,
rafael, linux-pm, tj, void, arighi, changwoo, sched-ext
Cc: qyousef, christian.loehle, pierre.gondois, sshegde, kayracizmeci,
tim.c.chen, Vincent Guittot
In order to use min_slice during CPU selection, update a cached value when
needed after en/dequeing a new task. This cached value includes current
task.
Signed-off-by: Vincent Guittot <vincent.guittot@linaro.org>
---
kernel/sched/core.c | 1 +
kernel/sched/fair.c | 28 ++++++++++++++++++++++++++++
kernel/sched/sched.h | 1 +
3 files changed, 30 insertions(+)
diff --git a/kernel/sched/core.c b/kernel/sched/core.c
index d7c3b43158de..031efe7d49fb 100644
--- a/kernel/sched/core.c
+++ b/kernel/sched/core.c
@@ -9012,6 +9012,7 @@ void __init sched_init(void)
rq->sd = NULL;
rq->rd = NULL;
rq->cpu_capacity = SCHED_CAPACITY_SCALE;
+ rq->min_slice = ULONG_MAX;
rq->balance_callback = &balance_push_callback;
rq->active_balance = 0;
rq->next_balance = jiffies;
diff --git a/kernel/sched/fair.c b/kernel/sched/fair.c
index 6ce4162a7703..cfc99b94d9ce 100644
--- a/kernel/sched/fair.c
+++ b/kernel/sched/fair.c
@@ -1130,6 +1130,30 @@ RB_DECLARE_CALLBACKS(static, min_vruntime_cb,
RB_AUG(slice, min_slice, min),
RB_AUG(slice, max_slice, max));
+/*
+ * Entity's slice is in the range [100us:100ms].
+ */
+static unsigned long get_rq_min_slice(struct rq *rq)
+{
+ return READ_ONCE(rq->min_slice);
+}
+
+static void __update_rq_min_slice(struct rq *rq)
+{
+ unsigned long min = cfs_rq_min_slice(&rq->cfs);
+
+ /*
+ * When there is no task enqueued, we check idleness instead of slice.
+ * Reset min_slice so the rq will be selected because its idlenes and
+ * not because of slice comparison.
+ */
+ if (min == ULONG_MAX)
+ min = 0;
+
+ if (min != get_rq_min_slice(rq))
+ WRITE_ONCE(rq->min_slice, min);
+}
+
/*
* Enqueue an entity into the rb-tree:
*/
@@ -8295,6 +8319,8 @@ enqueue_task_fair(struct rq *rq, struct task_struct *p, int flags)
__enqueue_entity(cfs_rq, se);
}
+ __update_rq_min_slice(rq);
+
if (!rq_h_nr_queued && rq->cfs.h_nr_queued)
dl_server_start(&rq->fair_server);
@@ -8420,6 +8446,8 @@ static bool __dequeue_task(struct rq *rq, struct task_struct *p, int flags)
if (se != cfs_rq->curr)
__dequeue_entity(cfs_rq, se);
+ __update_rq_min_slice(rq);
+
sub_nr_running(rq, 1);
/* balance early to pull high priority tasks */
diff --git a/kernel/sched/sched.h b/kernel/sched/sched.h
index 24f9b7bd8f0f..7a95df4f14d2 100644
--- a/kernel/sched/sched.h
+++ b/kernel/sched/sched.h
@@ -1173,6 +1173,7 @@ struct rq {
#endif
unsigned int ttwu_pending;
unsigned long cpu_capacity;
+ unsigned long min_slice;
#ifdef CONFIG_SCHED_PROXY_EXEC
struct task_struct __rcu *donor; /* Scheduling context */
struct task_struct __rcu *curr; /* Execution context */
--
2.53.0
^ permalink raw reply [flat|nested] 24+ messages in thread
* [PATCH 06/20 v3] sched/eevdf: Add min slice check when selecting CPU
2026-11-06 15:19 [PATCH 00/18 v3] Improving latency of short slice tasks Vincent Guittot
` (4 preceding siblings ...)
2026-11-06 15:19 ` [PATCH 05/20 v3] sched/eevdf: Add per cpu cached min_slice Vincent Guittot
@ 2026-11-06 15:19 ` Vincent Guittot
2026-11-06 15:19 ` [PATCH 07/20 v3] sched/fair: Prepare select_task_rq_fair() to be called for new cases Vincent Guittot
` (13 subsequent siblings)
19 siblings, 0 replies; 24+ messages in thread
From: Vincent Guittot @ 2026-11-06 15:19 UTC (permalink / raw)
To: mingo, peterz, juri.lelli, dietmar.eggemann, rostedt, bsegall,
mgorman, vschneid, kprateek.nayak, linux-kernel, lukasz.luba,
rafael, linux-pm, tj, void, arighi, changwoo, sched-ext
Cc: qyousef, christian.loehle, pierre.gondois, sshegde, kayracizmeci,
tim.c.chen, Vincent Guittot
Add a new level for selecting CPU when select_task_rq_fair() fails to find
an idle CPU. This last level compares the min slice to select a CPU where
the task could run 1st.
This helps a waking task to select a CPU where a longer slice runs
instead of one where a task with the same or shorter slice already runs.
Signed-off-by: Vincent Guittot <vincent.guittot@linaro.org>
---
kernel/sched/core.c | 2 +-
kernel/sched/fair.c | 157 ++++++++++++++++++++++++++++----------------
2 files changed, 102 insertions(+), 57 deletions(-)
diff --git a/kernel/sched/core.c b/kernel/sched/core.c
index 031efe7d49fb..d71da9069218 100644
--- a/kernel/sched/core.c
+++ b/kernel/sched/core.c
@@ -9012,7 +9012,7 @@ void __init sched_init(void)
rq->sd = NULL;
rq->rd = NULL;
rq->cpu_capacity = SCHED_CAPACITY_SCALE;
- rq->min_slice = ULONG_MAX;
+ rq->min_slice = 0;
rq->balance_callback = &balance_push_callback;
rq->active_balance = 0;
rq->next_balance = jiffies;
diff --git a/kernel/sched/fair.c b/kernel/sched/fair.c
index cfc99b94d9ce..039dce244aaa 100644
--- a/kernel/sched/fair.c
+++ b/kernel/sched/fair.c
@@ -8842,11 +8842,25 @@ static inline int sched_balance_find_dst_cpu(struct sched_domain *sd, struct tas
return new_cpu;
}
-static inline int __select_idle_cpu(int cpu, struct task_struct *p)
+static inline bool choose_sched_slice_rq(struct rq *rq, struct task_struct *p)
{
- if (choose_idle_cpu(cpu, p) && sched_cpu_cookie_match(cpu_rq(cpu), p))
+ if (p->se.slice < get_rq_min_slice(rq))
+ return true;
+
+ return false;
+}
+
+static inline int __select_idle_cpu(int cpu, struct task_struct *p, int *slice_cpu)
+{
+ if (!sched_cpu_cookie_match(cpu_rq(cpu), p))
+ return -1;
+
+ if (choose_idle_cpu(cpu, p))
return cpu;
+ if (*slice_cpu == -1 && choose_sched_slice_rq(cpu_rq(cpu), p))
+ *slice_cpu = cpu;
+
return -1;
}
@@ -8937,7 +8951,8 @@ void __update_idle_core(struct rq *rq)
* sd_balance_shared->has_idle_cores and enabled through update_idle_core()
* above.
*/
-static int select_idle_core(struct task_struct *p, int core, struct cpumask *cpus, int *idle_cpu)
+static int select_idle_core(struct task_struct *p, int core, struct cpumask *cpus,
+ int *idle_cpu, int *slice_cpu)
{
bool idle = true;
int cpu;
@@ -8946,11 +8961,18 @@ static int select_idle_core(struct task_struct *p, int core, struct cpumask *cpu
if (!available_idle_cpu(cpu)) {
idle = false;
if (*idle_cpu == -1) {
- if (choose_sched_idle_rq(cpu_rq(cpu), p) &&
- cpumask_test_cpu(cpu, cpus)) {
+ if (!cpumask_test_cpu(cpu, cpus))
+ continue;
+
+ if (choose_sched_idle_rq(cpu_rq(cpu), p)) {
*idle_cpu = cpu;
break;
}
+
+ if (*slice_cpu == -1 &&
+ choose_sched_slice_rq(cpu_rq(cpu), p))
+ *slice_cpu = cpu;
+
continue;
}
break;
@@ -8994,10 +9016,11 @@ static int select_idle_smt(struct task_struct *p, struct sched_domain *sd, int t
* comparing the average scan cost (tracked in sd->avg_scan_cost) against the
* average idle time for this rq (as found in rq->avg_idle).
*/
-static int select_idle_cpu(struct task_struct *p, struct sched_domain *sd, bool has_idle_core, int target)
+static int select_idle_cpu(struct task_struct *p, struct sched_domain *sd,
+ bool has_idle_core, int *best)
{
struct cpumask *cpus = this_cpu_cpumask_var_ptr(select_rq_mask);
- int i, cpu, idle_cpu = -1, nr = INT_MAX;
+ int i, cpu, idle_cpu = -1, slice_cpu = -1, target = *best, nr = INT_MAX;
if (sched_feat(SIS_UTIL) && sd->shared) {
/*
@@ -9027,13 +9050,13 @@ static int select_idle_cpu(struct task_struct *p, struct sched_domain *sd, bool
continue;
if (has_idle_core) {
- i = select_idle_core(p, cpu, cpus, &idle_cpu);
+ i = select_idle_core(p, cpu, cpus, &idle_cpu, &slice_cpu);
if ((unsigned int)i < nr_cpumask_bits)
return i;
} else {
if (--nr <= 0)
- return -1;
- idle_cpu = __select_idle_cpu(cpu, p);
+ goto abort_idle_cpu;
+ idle_cpu = __select_idle_cpu(cpu, p, &slice_cpu);
if ((unsigned int)idle_cpu < nr_cpumask_bits)
return idle_cpu;
}
@@ -9044,14 +9067,14 @@ static int select_idle_cpu(struct task_struct *p, struct sched_domain *sd, bool
for_each_cpu_wrap(cpu, cpus, target + 1) {
if (has_idle_core) {
- i = select_idle_core(p, cpu, cpus, &idle_cpu);
+ i = select_idle_core(p, cpu, cpus, &idle_cpu, &slice_cpu);
if ((unsigned int)i < nr_cpumask_bits)
return i;
} else {
if (--nr <= 0)
- return -1;
- idle_cpu = __select_idle_cpu(cpu, p);
+ goto abort_idle_cpu;
+ idle_cpu = __select_idle_cpu(cpu, p, &slice_cpu);
if ((unsigned int)idle_cpu < nr_cpumask_bits)
break;
}
@@ -9060,6 +9083,10 @@ static int select_idle_cpu(struct task_struct *p, struct sched_domain *sd, bool
if (has_idle_core)
set_idle_cores(target, false);
+abort_idle_cpu:
+ if (idle_cpu == -1 && !choose_sched_slice_rq(cpu_rq(target), p) && slice_cpu != -1)
+ *best = slice_cpu;
+
return idle_cpu;
}
@@ -9074,16 +9101,21 @@ static int select_idle_cpu(struct task_struct *p, struct sched_domain *sd, bool
*
* Rank Val Tier Meaning
* ------------------------------ --- ------ ---------------------------
- * ASYM_IDLE_UCLAMP_MISFIT -4 core Idle core; capacity fits
+ * ASYM_BUSY_FITS -6 core Busy core but capacity fits
+ * and task can preempt current.
+ * ASYM_IDLE_UCLAMP_MISFIT -5 core Idle core; capacity fits
* util but uclamp_min misses.
- * ASYM_IDLE_COMPLETE_MISFIT -3 core Idle core; capacity does
+ * ASYM_IDLE_COMPLETE_MISFIT -4 core Idle core; capacity does
* not fit. Still beats every
* thread-tier rank: a busy
* sibling cuts effective
* capacity more than a
* misfit hurts a quiet core.
- * ASYM_IDLE_THREAD_FITS -2 thread Busy SMT sibling; capacity
+ * ASYM_IDLE_THREAD_FITS -3 thread Busy SMT sibling; capacity
* fits util + uclamp.
+ * ASYM_BUSY_THREAD_FITS -2 thread Busy CPU and SMT sibling but
+ * capacity fits and task can
+ * preempt current.
* ASYM_IDLE_THREAD_UCLAMP_MISFIT -1 thread Busy SMT sibling; capacity
* fits but uclamp_min misses
* (native util_fits_cpu()
@@ -9091,24 +9123,26 @@ static int select_idle_cpu(struct task_struct *p, struct sched_domain *sd, bool
* ASYM_IDLE_THREAD_MISFIT 0 thread Busy SMT sibling; capacity
* does not fit.
*
- * ASYM_IDLE_CORE_BIAS (-3) is an offset, not a state. On an idle core,
+ * ASYM_IDLE_CORE_BIAS (-4) is an offset, not a state. On an idle core,
* fits += ASYM_IDLE_CORE_BIAS rebases thread-tier ranks into the core tier:
*
- * ASYM_IDLE_THREAD_UCLAMP_MISFIT (-1) + BIAS -> ASYM_IDLE_UCLAMP_MISFIT (-4)
- * ASYM_IDLE_THREAD_MISFIT (0) + BIAS -> ASYM_IDLE_COMPLETE_MISFIT (-3)
+ * ASYM_IDLE_THREAD_UCLAMP_MISFIT (-1) + BIAS -> ASYM_IDLE_UCLAMP_MISFIT (-5)
+ * ASYM_IDLE_THREAD_MISFIT (0) + BIAS -> ASYM_IDLE_COMPLETE_MISFIT (-4)
*
- * ASYM_IDLE_THREAD_FITS (-2) is never rebased because a fully-fitting idle-core
+ * ASYM_IDLE_THREAD_FITS (-3) is never rebased because a fully-fitting idle-core
* candidate early-returns from select_idle_capacity().
*/
enum asym_fits_state {
- ASYM_IDLE_UCLAMP_MISFIT = -4,
+ ASYM_BUSY_FITS = -6,
+ ASYM_IDLE_UCLAMP_MISFIT,
ASYM_IDLE_COMPLETE_MISFIT,
ASYM_IDLE_THREAD_FITS,
+ ASYM_BUSY_THREAD_FITS,
ASYM_IDLE_THREAD_UCLAMP_MISFIT,
ASYM_IDLE_THREAD_MISFIT,
/* util_fits_cpu() bias for idle core */
- ASYM_IDLE_CORE_BIAS = -3,
+ ASYM_IDLE_CORE_BIAS = -4,
};
/*
@@ -9127,6 +9161,7 @@ select_idle_capacity(struct task_struct *p, struct sched_domain *sd, int target)
bool has_idle_core = sched_smt_active() && test_idle_cores(target);
unsigned long task_util, util_min, util_max, best_cap = 0;
int fits, best_fits = ASYM_IDLE_THREAD_MISFIT;
+ unsigned long task_slice;
int cpu, best_cpu = -1;
struct cpumask *cpus;
int nr = INT_MAX;
@@ -9137,6 +9172,7 @@ select_idle_capacity(struct task_struct *p, struct sched_domain *sd, int target)
task_util = task_util_est(p);
util_min = uclamp_eff_value(p, UCLAMP_MIN);
util_max = uclamp_eff_value(p, UCLAMP_MAX);
+ task_slice = p->se.slice;
if (sched_feat(SIS_UTIL) && sd->shared) {
/*
@@ -9160,42 +9196,49 @@ select_idle_capacity(struct task_struct *p, struct sched_domain *sd, int target)
if (!has_idle_core && --nr <= 0)
return best_cpu;
- if (!choose_idle_cpu(cpu, p))
- continue;
-
fits = util_fits_cpu(task_util, util_min, util_max, cpu);
- /*
- * Perfect fit: capacity satisfies util + uclamp and the CPU
- * sits on a fully-idle SMT core, this is a !SMT system, or
- * there is no idle core to find.
- * Short-circuit the rank-based selection and return
- * immediately.
- */
- if (fits > 0 && preferred_core)
- return cpu;
- /*
- * Only the min performance hint (i.e. uclamp_min) doesn't fit.
- * Look for the CPU with best capacity.
- */
- else if (fits < 0)
+ if (choose_idle_cpu(cpu, p)) {
+ /*
+ * Perfect fit: capacity satisfies util + uclamp and the CPU
+ * sits on a fully-idle SMT core, this is a !SMT system, or
+ * there is no idle core to find.
+ * Short-circuit the rank-based selection and return
+ * immediately.
+ */
+ if (fits > 0 && preferred_core)
+ return cpu;
+ /*
+ * Only the min performance hint (i.e. uclamp_min) doesn't fit.
+ * Look for the CPU with best capacity.
+ */
+ else if (fits < 0)
+ cpu_cap = get_actual_cpu_capacity(cpu);
+ /*
+ * fits > 0 implies we are not on a preferred core, but the util
+ * fits CPU capacity. Set fits to ASYM_IDLE_THREAD_FITS
+ * so the effective range becomes
+ * [ASYM_IDLE_THREAD_FITS, ASYM_IDLE_THREAD_MISFIT], where:
+ * ASYM_IDLE_THREAD_MISFIT - does not fit
+ * ASYM_IDLE_THREAD_UCLAMP_MISFIT - fits with the exception of UCLAMP_MIN
+ * ASYM_BUSY_THREAD_FITS - fits with the exception of idleness
+ * but can preempt current
+ * ASYM_IDLE_THREAD_FITS - fits with the exception of preferred_core
+ */
+ else if (fits > 0)
+ fits = ASYM_IDLE_THREAD_FITS;
+
+ } else if (fits > 0 && task_slice < get_rq_min_slice(cpu_rq(cpu))) {
+ fits = ASYM_BUSY_THREAD_FITS;
cpu_cap = get_actual_cpu_capacity(cpu);
- /*
- * fits > 0 implies we are not on a preferred core, but the util
- * fits CPU capacity. Set fits to ASYM_IDLE_THREAD_FITS
- * so the effective range becomes
- * [ASYM_IDLE_THREAD_FITS, ASYM_IDLE_THREAD_MISFIT], where:
- * ASYM_IDLE_THREAD_MISFIT - does not fit
- * ASYM_IDLE_THREAD_UCLAMP_MISFIT - fits with the exception of UCLAMP_MIN
- * ASYM_IDLE_THREAD_FITS - fits with the exception of preferred_core
- */
- else if (fits > 0)
- fits = ASYM_IDLE_THREAD_FITS;
+ } else {
+ continue;
+ }
/*
* If we are on a preferred core, translate the range of fits
- * of [ASYM_IDLE_THREAD_UCLAMP_MISFIT, ASYM_IDLE_THREAD_MISFIT] to
- * [ASYM_IDLE_UCLAMP_MISFIT, ASYM_IDLE_COMPLETE_MISFIT].
+ * of [ASYM_BUSY_THREAD_FITS, ASYM_IDLE_THREAD_MISFIT] to
+ * [ASYM_BUSY_FITS, ASYM_IDLE_COMPLETE_MISFIT].
* This ensures that an idle core is always given priority over
* (partially) busy core.
*
@@ -9219,15 +9262,17 @@ select_idle_capacity(struct task_struct *p, struct sched_domain *sd, int target)
/*
* A value in the [ASYM_IDLE_UCLAMP_MISFIT, ASYM_IDLE_COMPLETE_MISFIT]
- * range means the chosen CPU is in a fully idle SMT core. Values above
- * ASYM_IDLE_COMPLETE_MISFIT mean we never ranked such a CPU best.
+ * range mean the chosen CPU is in a fully idle SMT core. Values
+ * ASYM_BUSY_FITS or above ASYM_IDLE_COMPLETE_MISFIT mean we never
+ * ranked such a CPU best.
*
* The asym-capacity wakeup path returns from select_idle_sibling()
* after this function and never runs select_idle_cpu(), so the usual
* select_idle_cpu() tail that clears idle cores must live here when the
* idle-core preference did not win.
*/
- if (has_idle_core && best_fits > ASYM_IDLE_COMPLETE_MISFIT)
+ if (has_idle_core && (best_fits > ASYM_IDLE_COMPLETE_MISFIT ||
+ best_fits == ASYM_BUSY_FITS))
set_idle_cores(target, false);
return best_cpu;
@@ -9381,7 +9426,7 @@ static int select_idle_sibling(struct task_struct *p, int prev, int target)
}
}
- i = select_idle_cpu(p, sd, has_idle_core, target);
+ i = select_idle_cpu(p, sd, has_idle_core, &target);
if ((unsigned int)i < nr_cpumask_bits) {
target = i;
goto select_smt_priority;
--
2.53.0
^ permalink raw reply [flat|nested] 24+ messages in thread
* [PATCH 07/20 v3] sched/fair: Prepare select_task_rq_fair() to be called for new cases
2026-11-06 15:19 [PATCH 00/18 v3] Improving latency of short slice tasks Vincent Guittot
` (5 preceding siblings ...)
2026-11-06 15:19 ` [PATCH 06/20 v3] sched/eevdf: Add min slice check when selecting CPU Vincent Guittot
@ 2026-11-06 15:19 ` Vincent Guittot
2026-11-06 15:19 ` [PATCH 08/20 v3] sched/fair: Add push task mechanism for fair Vincent Guittot
` (12 subsequent siblings)
19 siblings, 0 replies; 24+ messages in thread
From: Vincent Guittot @ 2026-11-06 15:19 UTC (permalink / raw)
To: mingo, peterz, juri.lelli, dietmar.eggemann, rostedt, bsegall,
mgorman, vschneid, kprateek.nayak, linux-kernel, lukasz.luba,
rafael, linux-pm, tj, void, arighi, changwoo, sched-ext
Cc: qyousef, christian.loehle, pierre.gondois, sshegde, kayracizmeci,
tim.c.chen, Vincent Guittot
Update select_task_rq_fair() to be called outside of the 3 current cases:
- wake up
- exec
- fork
We wants to select a rq in some new cases like pushing a runnable task on a
better CPU than the local one. In such case, it's not a wakeup , nor an
exec nor a fork. We ensure to not distrub these cases but still go through
EAS and fast-path.
Signed-off-by: Vincent Guittot <vincent.guittot@linaro.org>
---
kernel/sched/fair.c | 75 +++++++++++++++++++++++++++++----------------
1 file changed, 48 insertions(+), 27 deletions(-)
diff --git a/kernel/sched/fair.c b/kernel/sched/fair.c
index 039dce244aaa..8f99c8bf1a80 100644
--- a/kernel/sched/fair.c
+++ b/kernel/sched/fair.c
@@ -6170,6 +6170,18 @@ static void sync_entity_load_avg(struct sched_entity *se)
__update_load_avg_blocked_se(last_update_time, se);
}
+/*
+ * Synchronize task load avg of dequeued entity without locking
+ * the previous rq.
+ */
+static void sync_task_load_avg(struct task_struct *p)
+{
+ if (task_on_rq_queued(p))
+ return;
+
+ sync_entity_load_avg(&p->se);
+}
+
/*
* Task first catches up with cfs_rq, and then subtract
* itself from the cfs_rq (task must be off the queue now).
@@ -8802,7 +8814,7 @@ static inline int sched_balance_find_dst_cpu(struct sched_domain *sd, struct tas
* prev_cpu's last_update_time.
*/
if (!(sd_flag & SD_BALANCE_FORK))
- sync_entity_load_avg(&p->se);
+ sync_task_load_avg(p);
while (sd) {
struct sched_group *group;
@@ -9318,7 +9330,7 @@ static int select_idle_sibling(struct task_struct *p, int prev, int target)
* that the task fits with CPU's capacity.
*/
if (sched_asym_cpucap_active()) {
- sync_entity_load_avg(&p->se);
+ sync_task_load_avg(p);
task_util = task_util_est(p);
util_min = uclamp_eff_value(p, UCLAMP_MIN);
util_max = uclamp_eff_value(p, UCLAMP_MAX);
@@ -9912,7 +9924,7 @@ static int find_energy_efficient_cpu(struct task_struct *p, int prev_cpu)
target = prev_cpu;
- sync_entity_load_avg(&p->se);
+ sync_task_load_avg(p);
if (!task_util_est(p) && p_util_min == 0)
return target;
@@ -10060,46 +10072,55 @@ static int find_energy_efficient_cpu(struct task_struct *p, int prev_cpu)
}
/*
- * select_task_rq_fair: Select target runqueue for the waking task in domains
- * that have the relevant SD flag set. In practice, this is SD_BALANCE_WAKE,
- * SD_BALANCE_FORK, or SD_BALANCE_EXEC.
+ * select_task_rq_fair: Select a target runqueue for the task.
+ * There are 2 ways to select the target runqueue:
+ * - The fast path which only looks for an idle CPU in the LLC or the smallest
+ * asymmetric domain (i.e. the lowest domain with all compute capacities).
+ * - The slow path which looks for the idlest CPU in the highest domain with
+ * the relevant SD flag set.
*
- * Balances load by selecting the idlest CPU in the idlest group, or under
- * certain conditions an idle sibling CPU if the domain has SD_WAKE_AFFINE set.
+ * In practice, WF_EXEC and WF_FORK uses the slow path whereas WF_TTWU and no
+ * flag (Push) uses the fast path.
*
- * Returns the target CPU number.
*/
static int
-select_task_rq_fair(struct task_struct *p, int prev_cpu, int wake_flags)
+select_task_rq_fair(struct task_struct *p, int prev_cpu, int select_flags)
{
- int sync = (wake_flags & WF_SYNC) && !(current->flags & PF_EXITING);
+ int sync = (select_flags & WF_SYNC) && !(current->flags & PF_EXITING);
+ int want_sibling = !(select_flags & (WF_EXEC | WF_FORK));
+ int new_cpu, cpu = smp_processor_id();
struct sched_domain *tmp, *sd = NULL;
- int cpu = smp_processor_id();
- int new_cpu = prev_cpu;
- int want_affine = 0;
/* SD_flags and WF_flags share the first nibble */
- int sd_flag = wake_flags & 0xF;
+ int sd_flag = select_flags & 0xF;
+ int want_affine = 0;
/*
- * required for stable ->cpus_allowed
+ * Required for stable ->cpus_allowed
*/
lockdep_assert_held(&p->pi_lock);
- if (wake_flags & WF_TTWU) {
+
+ if (select_flags & WF_TTWU) {
record_wakee(p);
- if ((wake_flags & WF_CURRENT_CPU) &&
+ if ((select_flags & WF_CURRENT_CPU) &&
cpumask_test_cpu(cpu, p->cpus_ptr))
return cpu;
+ }
- if (!is_rd_overutilized(this_rq()->rd)) {
- new_cpu = find_energy_efficient_cpu(p, prev_cpu);
- if (new_cpu >= 0)
- return new_cpu;
- new_cpu = prev_cpu;
- }
+ /*
+ * We don't want EAS to be called for exec or fork but it should be
+ * called for any other case such as wake up or push callback.
+ */
+ if (!is_rd_overutilized(this_rq()->rd) && want_sibling) {
+ new_cpu = find_energy_efficient_cpu(p, prev_cpu);
+ if (new_cpu >= 0)
+ return new_cpu;
+ }
+ if (select_flags & WF_TTWU)
want_affine = !wake_wide(p) && cpumask_test_cpu(cpu, p->cpus_ptr);
- }
+
+ new_cpu = prev_cpu;
for_each_domain(cpu, tmp) {
/*
@@ -10133,8 +10154,8 @@ select_task_rq_fair(struct task_struct *p, int prev_cpu, int wake_flags)
}
/* Fast path */
- if (wake_flags & WF_TTWU)
- return select_idle_sibling(p, prev_cpu, new_cpu);
+ if (want_sibling)
+ new_cpu = select_idle_sibling(p, prev_cpu, new_cpu);
return new_cpu;
}
--
2.53.0
^ permalink raw reply [flat|nested] 24+ messages in thread
* [PATCH 08/20 v3] sched/fair: Add push task mechanism for fair
2026-11-06 15:19 [PATCH 00/18 v3] Improving latency of short slice tasks Vincent Guittot
` (6 preceding siblings ...)
2026-11-06 15:19 ` [PATCH 07/20 v3] sched/fair: Prepare select_task_rq_fair() to be called for new cases Vincent Guittot
@ 2026-11-06 15:19 ` Vincent Guittot
2026-11-06 15:19 ` [PATCH 09/20 v3] sched/fair: Optimize " Vincent Guittot
` (11 subsequent siblings)
19 siblings, 0 replies; 24+ messages in thread
From: Vincent Guittot @ 2026-11-06 15:19 UTC (permalink / raw)
To: mingo, peterz, juri.lelli, dietmar.eggemann, rostedt, bsegall,
mgorman, vschneid, kprateek.nayak, linux-kernel, lukasz.luba,
rafael, linux-pm, tj, void, arighi, changwoo, sched-ext
Cc: qyousef, christian.loehle, pierre.gondois, sshegde, kayracizmeci,
tim.c.chen, Vincent Guittot
EAS is based on wakeup events to efficiently place tasks on the system, but
there are cases where a task doesn't have wakeup events anymore or at a far
too low pace. For such situation, we can take advantage of the task being
put back in the enqueued list to check if it should be pushed on another
CPU.
Add a push task mechanism that enables fair scheduler to push runnable
tasks. EAS will be one user but other feature like filling idle CPUs or
short slice tasks migration can also take advantage of it.
Signed-off-by: Vincent Guittot <vincent.guittot@linaro.org>
---
kernel/sched/core.c | 4 +-
kernel/sched/fair.c | 177 ++++++++++++++++++++++++++++++++++++++++++-
kernel/sched/sched.h | 7 ++
3 files changed, 185 insertions(+), 3 deletions(-)
diff --git a/kernel/sched/core.c b/kernel/sched/core.c
index d71da9069218..1096ea9cbc4c 100644
--- a/kernel/sched/core.c
+++ b/kernel/sched/core.c
@@ -2589,8 +2589,8 @@ static inline bool is_cpu_allowed(struct task_struct *p, int cpu)
*
* Returns (locked) new rq. Old rq's lock is released.
*/
-static struct rq *move_queued_task(struct rq *rq, struct rq_flags *rf,
- struct task_struct *p, int new_cpu)
+struct rq *move_queued_task(struct rq *rq, struct rq_flags *rf,
+ struct task_struct *p, int new_cpu)
__must_hold(__rq_lockp(rq))
{
lockdep_assert_rq_held(rq);
diff --git a/kernel/sched/fair.c b/kernel/sched/fair.c
index 8f99c8bf1a80..e37f8b55a293 100644
--- a/kernel/sched/fair.c
+++ b/kernel/sched/fair.c
@@ -8405,6 +8405,8 @@ static void dequeue_hierarchy(struct task_struct *p, int flags)
}
}
+static void fair_remove_pushable_task(struct rq *rq, struct task_struct *p);
+
/*
* The part of dequeue_task_fair() that is needed to dequeue delayed tasks.
*
@@ -8421,6 +8423,7 @@ static bool __dequeue_task(struct rq *rq, struct task_struct *p, int flags)
bool task_delayed = flags & DEQUEUE_DELAYED;
clear_buddies(cfs_rq, se);
+ fair_remove_pushable_task(rq, p);
update_curr_eevdf(cfs_rq);
update_entity_lag(cfs_rq, se);
@@ -10071,6 +10074,163 @@ static int find_energy_efficient_cpu(struct task_struct *p, int prev_cpu)
return target;
}
+DEFINE_STATIC_KEY_FALSE(sched_push_task);
+
+static inline bool sched_push_task_enabled(void)
+{
+ return static_branch_unlikely(&sched_push_task);
+}
+
+static bool __check_pushable_fair_task(struct rq *rq, struct task_struct *p)
+{
+ if (!task_on_rq_queued(p))
+ return false;
+
+ if (p->se.sched_delayed)
+ return false;
+
+ if (p->nr_cpus_allowed <= 1)
+ return false;
+
+ return true;
+}
+
+static bool fair_check_pushable_task(struct rq *rq, struct task_struct *p, struct task_struct *next)
+{
+ if (!__check_pushable_fair_task(rq, p))
+ return false;
+
+ if (next == p)
+ return false;
+
+ return false;
+}
+
+static inline int has_pushable_tasks(struct rq *rq)
+{
+ return !plist_head_empty(&rq->cfs.pushable_tasks);
+}
+
+static struct task_struct *pick_next_pushable_fair_task(struct rq *rq)
+{
+ struct task_struct *p;
+
+ if (!has_pushable_tasks(rq))
+ return NULL;
+
+ p = plist_first_entry(&rq->cfs.pushable_tasks,
+ struct task_struct, pushable_tasks);
+
+ WARN_ON_ONCE(rq->cpu != task_cpu(p));
+ WARN_ON_ONCE(task_current(rq, p));
+ WARN_ON_ONCE(p->nr_cpus_allowed <= 1);
+ WARN_ON_ONCE(!task_on_rq_queued(p));
+
+ /*
+ * Remove task from the pushable list as we try only once after that
+ * the task has been put back in enqueued list.
+ */
+ plist_del(&p->pushable_tasks, &rq->cfs.pushable_tasks);
+
+ return p;
+}
+
+static int
+select_task_rq_fair(struct task_struct *p, int prev_cpu, int wake_flags);
+
+/*
+ * See if the non running fair tasks on this rq can be sent on other CPUs
+ * that fits better with their profile.
+ */
+static bool fair_push_task(struct rq *rq)
+ __releases(__rq_lockp(rq))
+ __acquires(__rq_lockp(rq))
+{
+ struct task_struct *next_task;
+ int prev_cpu, new_cpu;
+ struct rq_flags rf;
+ struct rq *cur_rq;
+
+ next_task = pick_next_pushable_fair_task(rq);
+ if (!next_task)
+ return false;
+
+ if (is_migration_disabled(next_task))
+ return true;
+
+ /* We might release rq lock */
+ get_task_struct(next_task);
+
+ prev_cpu = rq->cpu;
+
+ /*
+ * We need to release rq lock and take both task and rq w/o
+ * triggering a deadlock.
+ */
+ raw_spin_rq_unlock(rq);
+
+ cur_rq = task_rq_lock(next_task, &rf);
+
+ /* Task already migrated */
+ if (cur_rq->cpu != prev_cpu)
+ goto skip_push;
+
+ new_cpu = select_task_rq_fair(next_task, prev_cpu, 0);
+
+ /* Task doesn't need to migrate */
+ if (new_cpu == prev_cpu)
+ goto skip_push;
+
+ update_rq_clock(cur_rq);
+ cur_rq = move_queued_task(cur_rq, &rf, next_task, new_cpu);
+
+skip_push:
+ task_rq_unlock(cur_rq, next_task, &rf);
+
+ /* Restore rq state */
+ raw_spin_rq_lock(rq);
+ put_task_struct(next_task);
+
+ return true;
+}
+
+static void fair_push_tasks(struct rq *rq)
+{
+ /* fair_push_task() will return true if it moved a fair task */
+ while (fair_push_task(rq))
+ ;
+}
+
+static DEFINE_PER_CPU(struct balance_callback, fair_push_head);
+
+static inline void fair_queue_push_tasks(struct rq *rq)
+{
+ if (!sched_push_task_enabled() || !has_pushable_tasks(rq))
+ return;
+
+ queue_balance_callback(rq, &per_cpu(fair_push_head, rq->cpu), fair_push_tasks);
+}
+
+static void fair_remove_pushable_task(struct rq *rq, struct task_struct *p)
+{
+ if (sched_push_task_enabled())
+ plist_del(&p->pushable_tasks, &rq->cfs.pushable_tasks);
+}
+
+static void __fair_add_pushable_task(struct rq *rq, struct task_struct *p)
+{
+ plist_del(&p->pushable_tasks, &rq->cfs.pushable_tasks);
+ plist_node_init(&p->pushable_tasks, p->prio);
+ plist_add(&p->pushable_tasks, &rq->cfs.pushable_tasks);
+}
+
+static void fair_add_pushable_prev(struct rq *rq, struct task_struct *prev,
+ struct task_struct *next)
+{
+ if (sched_push_task_enabled() && fair_check_pushable_task(rq, prev, next))
+ __fair_add_pushable_task(rq, prev);
+}
+
/*
* select_task_rq_fair: Select a target runqueue for the task.
* There are 2 ways to select the target runqueue:
@@ -10552,6 +10712,18 @@ static void put_prev_task_fair(struct rq *rq, struct task_struct *prev, struct t
cfs_rq->curr = NULL;
if (se->on_rq)
__enqueue_entity(cfs_rq, se);
+
+ if (next) {
+ /*
+ * The previous task might be eligible for being pushed on another cpu
+ * if it is still active.
+ */
+ if (next != prev)
+ fair_add_pushable_prev(rq, prev, next);
+
+ if (next->sched_class != &fair_sched_class)
+ fair_queue_push_tasks(rq);
+ }
}
/*
@@ -15667,6 +15839,7 @@ static void attach_task_cfs_rq(struct task_struct *p)
static void switching_from_fair(struct rq *rq, struct task_struct *p)
{
+ fair_remove_pushable_task(rq, p);
if (p->se.sched_delayed)
dequeue_task(rq, p, DEQUEUE_SLEEP | DEQUEUE_DELAYED | DEQUEUE_NOCLOCK);
}
@@ -15710,6 +15883,7 @@ static void set_next_task_fair(struct rq *rq, struct task_struct *p, enum snt_e
goto repick;
clear_buddies(cfs_rq, se);
+ fair_remove_pushable_task(rq, p);
if (on_rq)
__dequeue_entity(cfs_rq, se);
@@ -15754,7 +15928,7 @@ static void set_next_task_fair(struct rq *rq, struct task_struct *p, enum snt_e
update_misfit_status(p, rq);
sched_fair_update_stop_tick(rq, p);
-
+ fair_queue_push_tasks(rq);
repick:
/*
* A same-task repick skips put_prev_task_fair(), but
@@ -15770,6 +15944,7 @@ void init_cfs_rq(struct cfs_rq *cfs_rq)
{
cfs_rq->tasks_timeline = RB_ROOT_CACHED;
cfs_rq->zero_vruntime = (u64)(-(1LL << 20));
+ plist_head_init(&cfs_rq->pushable_tasks);
raw_spin_lock_init(&cfs_rq->removed.lock);
}
diff --git a/kernel/sched/sched.h b/kernel/sched/sched.h
index 7a95df4f14d2..74e68d458509 100644
--- a/kernel/sched/sched.h
+++ b/kernel/sched/sched.h
@@ -721,6 +721,8 @@ struct cfs_rq {
unsigned long runnable_avg;
} removed;
+ struct plist_head pushable_tasks;
+
#ifdef CONFIG_FAIR_GROUP_SCHED
u64 last_update_tg_load_avg;
unsigned long tg_load_avg_contrib;
@@ -3887,6 +3889,8 @@ static inline bool sched_energy_enabled(void) { return false; }
#endif /* !(CONFIG_ENERGY_MODEL && CONFIG_CPU_FREQ_GOV_SCHEDUTIL) */
+DECLARE_STATIC_KEY_FALSE(sched_push_task);
+
#ifdef CONFIG_MEMBARRIER
/*
@@ -4219,6 +4223,9 @@ void move_queued_task_locked(struct rq *src_rq, struct rq *dst_rq, struct task_s
wakeup_preempt(dst_rq, task, 0);
}
+struct rq *move_queued_task(struct rq *rq, struct rq_flags *rf,
+ struct task_struct *p, int new_cpu);
+
static inline
bool task_is_pushable(struct rq *rq, struct task_struct *p, int cpu)
{
--
2.53.0
^ permalink raw reply [flat|nested] 24+ messages in thread
* [PATCH 09/20 v3] sched/fair: Optimize push task mechanism for fair
2026-11-06 15:19 [PATCH 00/18 v3] Improving latency of short slice tasks Vincent Guittot
` (7 preceding siblings ...)
2026-11-06 15:19 ` [PATCH 08/20 v3] sched/fair: Add push task mechanism for fair Vincent Guittot
@ 2026-11-06 15:19 ` Vincent Guittot
2026-11-06 15:19 ` [PATCH 10/20 v3] sched/core: Add rq flag to tick parameters Vincent Guittot
` (10 subsequent siblings)
19 siblings, 0 replies; 24+ messages in thread
From: Vincent Guittot @ 2026-11-06 15:19 UTC (permalink / raw)
To: mingo, peterz, juri.lelli, dietmar.eggemann, rostedt, bsegall,
mgorman, vschneid, kprateek.nayak, linux-kernel, lukasz.luba,
rafael, linux-pm, tj, void, arighi, changwoo, sched-ext
Cc: qyousef, christian.loehle, pierre.gondois, sshegde, kayracizmeci,
tim.c.chen, Vincent Guittot
Instead of always unlocking local rq in order to lock both task and rq
in a safe order, just try to lock the task. If the task is already locked
by something else its state is probably changing and the conditions used
to add it in the pushable list are probably not true anymore so skipping
the push sequence seems like a good choice.
Signed-off-by: Vincent Guittot <vincent.guittot@linaro.org>
---
kernel/sched/core.c | 4 ++--
kernel/sched/fair.c | 44 +++++++++++++++++++++++---------------------
kernel/sched/sched.h | 3 ---
3 files changed, 25 insertions(+), 26 deletions(-)
diff --git a/kernel/sched/core.c b/kernel/sched/core.c
index 1096ea9cbc4c..d71da9069218 100644
--- a/kernel/sched/core.c
+++ b/kernel/sched/core.c
@@ -2589,8 +2589,8 @@ static inline bool is_cpu_allowed(struct task_struct *p, int cpu)
*
* Returns (locked) new rq. Old rq's lock is released.
*/
-struct rq *move_queued_task(struct rq *rq, struct rq_flags *rf,
- struct task_struct *p, int new_cpu)
+static struct rq *move_queued_task(struct rq *rq, struct rq_flags *rf,
+ struct task_struct *p, int new_cpu)
__must_hold(__rq_lockp(rq))
{
lockdep_assert_rq_held(rq);
diff --git a/kernel/sched/fair.c b/kernel/sched/fair.c
index e37f8b55a293..e2999d3f88e4 100644
--- a/kernel/sched/fair.c
+++ b/kernel/sched/fair.c
@@ -10148,8 +10148,7 @@ static bool fair_push_task(struct rq *rq)
{
struct task_struct *next_task;
int prev_cpu, new_cpu;
- struct rq_flags rf;
- struct rq *cur_rq;
+ struct rq *new_rq;
next_task = pick_next_pushable_fair_task(rq);
if (!next_task)
@@ -10158,38 +10157,41 @@ static bool fair_push_task(struct rq *rq)
if (is_migration_disabled(next_task))
return true;
- /* We might release rq lock */
- get_task_struct(next_task);
-
prev_cpu = rq->cpu;
/*
- * We need to release rq lock and take both task and rq w/o
- * triggering a deadlock.
+ * The safe lock ordering for task and rq is task 1st then rq but we
+ * already get the rq so just try to get task too. If task is already
+ * locked, it is waiting for the rq's lock and it is about to change
+ * task state so skipping the push sequence in order to speed up the
+ * release of the lock is the best choice.
*/
- raw_spin_rq_unlock(rq);
-
- cur_rq = task_rq_lock(next_task, &rf);
-
- /* Task already migrated */
- if (cur_rq->cpu != prev_cpu)
- goto skip_push;
+ if (!raw_spin_trylock(&next_task->pi_lock))
+ return true;
new_cpu = select_task_rq_fair(next_task, prev_cpu, 0);
/* Task doesn't need to migrate */
if (new_cpu == prev_cpu)
- goto skip_push;
+ goto no_push;
+
+ new_rq = cpu_rq(new_cpu);
- update_rq_clock(cur_rq);
- cur_rq = move_queued_task(cur_rq, &rf, next_task, new_cpu);
+ deactivate_task(rq, next_task, 0);
+ set_task_cpu(next_task, new_cpu);
+ raw_spin_rq_unlock(rq);
-skip_push:
- task_rq_unlock(cur_rq, next_task, &rf);
+ raw_spin_rq_lock(new_rq);
+ WARN_ON_ONCE(task_cpu(next_task) != new_cpu);
+ activate_task(new_rq, next_task, 0);
+ wakeup_preempt(new_rq, next_task, 0);
+ raw_spin_rq_unlock(new_rq);
- /* Restore rq state */
+ /* Restore rq lock state */
raw_spin_rq_lock(rq);
- put_task_struct(next_task);
+
+no_push:
+ raw_spin_unlock(&next_task->pi_lock);
return true;
}
diff --git a/kernel/sched/sched.h b/kernel/sched/sched.h
index 74e68d458509..b88857dd3941 100644
--- a/kernel/sched/sched.h
+++ b/kernel/sched/sched.h
@@ -4223,9 +4223,6 @@ void move_queued_task_locked(struct rq *src_rq, struct rq *dst_rq, struct task_s
wakeup_preempt(dst_rq, task, 0);
}
-struct rq *move_queued_task(struct rq *rq, struct rq_flags *rf,
- struct task_struct *p, int new_cpu);
-
static inline
bool task_is_pushable(struct rq *rq, struct task_struct *p, int cpu)
{
--
2.53.0
^ permalink raw reply [flat|nested] 24+ messages in thread
* [PATCH 10/20 v3] sched/core: Add rq flag to tick parameters
2026-11-06 15:19 [PATCH 00/18 v3] Improving latency of short slice tasks Vincent Guittot
` (8 preceding siblings ...)
2026-11-06 15:19 ` [PATCH 09/20 v3] sched/fair: Optimize " Vincent Guittot
@ 2026-11-06 15:19 ` Vincent Guittot
2026-11-06 15:19 ` [PATCH 11/20 v3] sched/fair: Add force push task mechanism for fair Vincent Guittot
` (9 subsequent siblings)
19 siblings, 0 replies; 24+ messages in thread
From: Vincent Guittot @ 2026-11-06 15:19 UTC (permalink / raw)
To: mingo, peterz, juri.lelli, dietmar.eggemann, rostedt, bsegall,
mgorman, vschneid, kprateek.nayak, linux-kernel, lukasz.luba,
rafael, linux-pm, tj, void, arighi, changwoo, sched-ext
Cc: qyousef, christian.loehle, pierre.gondois, sshegde, kayracizmeci,
tim.c.chen, Vincent Guittot
In order to force migration in tick, we need to unlock the rq
Signed-off-by: Vincent Guittot <vincent.guittot@linaro.org>
---
kernel/sched/core.c | 6 +++---
kernel/sched/deadline.c | 2 +-
kernel/sched/ext/ext.c | 2 +-
kernel/sched/fair.c | 2 +-
kernel/sched/idle.c | 2 +-
kernel/sched/rt.c | 2 +-
kernel/sched/sched.h | 2 +-
kernel/sched/stop_task.c | 2 +-
8 files changed, 10 insertions(+), 10 deletions(-)
diff --git a/kernel/sched/core.c b/kernel/sched/core.c
index d71da9069218..a1a3aa45ecf6 100644
--- a/kernel/sched/core.c
+++ b/kernel/sched/core.c
@@ -923,7 +923,7 @@ static enum hrtimer_restart hrtick(struct hrtimer *timer)
rq_lock(rq, &rf);
update_rq_clock(rq);
- rq->donor->sched_class->task_tick(rq, rq->donor, 1);
+ rq->donor->sched_class->task_tick(rq, rq->donor, &rf, 1);
rq_unlock(rq, &rf);
return HRTIMER_NORESTART;
@@ -5857,7 +5857,7 @@ void sched_tick(void)
if (dynamic_preempt_lazy() && tif_test_bit(TIF_NEED_RESCHED_LAZY))
resched_curr(rq);
- donor->sched_class->task_tick(rq, donor, 0);
+ donor->sched_class->task_tick(rq, donor, &rf, 0);
if (sched_feat(LATENCY_WARN))
resched_latency = cpu_resched_latency(rq);
calc_global_load_tick(rq);
@@ -5953,7 +5953,7 @@ static void sched_tick_remote(struct work_struct *work)
u64 delta = rq_clock_task(rq) - curr->se.exec_start;
WARN_ON_ONCE(delta > (u64)NSEC_PER_SEC * 30);
}
- curr->sched_class->task_tick(rq, curr, 0);
+ curr->sched_class->task_tick(rq, curr, NULL, 0);
calc_load_nohz_remote(rq);
}
diff --git a/kernel/sched/deadline.c b/kernel/sched/deadline.c
index c0ebdcde5fe5..1f6bc63b3110 100644
--- a/kernel/sched/deadline.c
+++ b/kernel/sched/deadline.c
@@ -2879,7 +2879,7 @@ static void put_prev_task_dl(struct rq *rq, struct task_struct *p, struct task_s
* and everything must be accessed through the @rq and @curr passed in
* parameters.
*/
-static void task_tick_dl(struct rq *rq, struct task_struct *p, int queued)
+static void task_tick_dl(struct rq *rq, struct task_struct *p, struct rq_flags *rf, int queued)
{
update_curr_dl(rq);
diff --git a/kernel/sched/ext/ext.c b/kernel/sched/ext/ext.c
index 0403c4634281..8728e394d4fb 100644
--- a/kernel/sched/ext/ext.c
+++ b/kernel/sched/ext/ext.c
@@ -3822,7 +3822,7 @@ void scx_tick(struct rq *rq)
update_other_load_avgs(rq);
}
-static void task_tick_scx(struct rq *rq, struct task_struct *curr, int queued)
+static void task_tick_scx(struct rq *rq, struct task_struct *curr, struct rq_flags *rf, int queued)
{
struct scx_sched *sch = scx_task_sched(curr);
diff --git a/kernel/sched/fair.c b/kernel/sched/fair.c
index e2999d3f88e4..3bcb663ebcde 100644
--- a/kernel/sched/fair.c
+++ b/kernel/sched/fair.c
@@ -15686,7 +15686,7 @@ static inline void task_tick_core(struct rq *rq, struct task_struct *curr) {}
* and everything must be accessed through the @rq and @curr passed in
* parameters.
*/
-static void task_tick_fair(struct rq *rq, struct task_struct *curr, int queued)
+static void task_tick_fair(struct rq *rq, struct task_struct *curr, struct rq_flags *rf, int queued)
{
struct sched_entity *se = &curr->se;
diff --git a/kernel/sched/idle.c b/kernel/sched/idle.c
index 76f3c84ca684..1ba0303aacb4 100644
--- a/kernel/sched/idle.c
+++ b/kernel/sched/idle.c
@@ -538,7 +538,7 @@ dequeue_task_idle(struct rq *rq, struct task_struct *p, int flags)
* and everything must be accessed through the @rq and @curr passed in
* parameters.
*/
-static void task_tick_idle(struct rq *rq, struct task_struct *curr, int queued)
+static void task_tick_idle(struct rq *rq, struct task_struct *curr, struct rq_flags *rf, int queued)
{
update_curr_idle(rq);
}
diff --git a/kernel/sched/rt.c b/kernel/sched/rt.c
index 1535046a23ff..35b3831a72cf 100644
--- a/kernel/sched/rt.c
+++ b/kernel/sched/rt.c
@@ -2541,7 +2541,7 @@ static inline void watchdog(struct rq *rq, struct task_struct *p) { }
* and everything must be accessed through the @rq and @curr passed in
* parameters.
*/
-static void task_tick_rt(struct rq *rq, struct task_struct *p, int queued)
+static void task_tick_rt(struct rq *rq, struct task_struct *p, struct rq_flags *rf, int queued)
{
struct sched_rt_entity *rt_se = &p->rt;
diff --git a/kernel/sched/sched.h b/kernel/sched/sched.h
index b88857dd3941..95a18c0d21c6 100644
--- a/kernel/sched/sched.h
+++ b/kernel/sched/sched.h
@@ -2751,7 +2751,7 @@ struct sched_class {
* sched_tick: rq->lock
* sched_tick_remote: rq->lock
*/
- void (*task_tick)(struct rq *rq, struct task_struct *p, int queued);
+ void (*task_tick)(struct rq *rq, struct task_struct *p, struct rq_flags *rf, int queued);
/*
* sched_cgroup_fork: p->pi_lock
*/
diff --git a/kernel/sched/stop_task.c b/kernel/sched/stop_task.c
index 1e0109ec36b3..3d99b23afd89 100644
--- a/kernel/sched/stop_task.c
+++ b/kernel/sched/stop_task.c
@@ -74,7 +74,7 @@ static void put_prev_task_stop(struct rq *rq, struct task_struct *prev, struct t
* and everything must be accessed through the @rq and @curr passed in
* parameters.
*/
-static void task_tick_stop(struct rq *rq, struct task_struct *curr, int queued)
+static void task_tick_stop(struct rq *rq, struct task_struct *curr, struct rq_flags *rf, int queued)
{
}
--
2.53.0
^ permalink raw reply [flat|nested] 24+ messages in thread
* [PATCH 11/20 v3] sched/fair: Add force push task mechanism for fair
2026-11-06 15:19 [PATCH 00/18 v3] Improving latency of short slice tasks Vincent Guittot
` (9 preceding siblings ...)
2026-11-06 15:19 ` [PATCH 10/20 v3] sched/core: Add rq flag to tick parameters Vincent Guittot
@ 2026-11-06 15:19 ` Vincent Guittot
2026-11-06 15:19 ` [PATCH 12/20 v3] sched/fair: Support not wakeup case in select_idle_sibling Vincent Guittot
` (8 subsequent siblings)
19 siblings, 0 replies; 24+ messages in thread
From: Vincent Guittot @ 2026-11-06 15:19 UTC (permalink / raw)
To: mingo, peterz, juri.lelli, dietmar.eggemann, rostedt, bsegall,
mgorman, vschneid, kprateek.nayak, linux-kernel, lukasz.luba,
rafael, linux-pm, tj, void, arighi, changwoo, sched-ext
Cc: qyousef, christian.loehle, pierre.gondois, sshegde, kayracizmeci,
tim.c.chen, Vincent Guittot
When the task is alone on the CPU, it's never put back in the enqueued
list; In this special case, we use the tick to run the check used to push
runnable task on a better CPU.
Signed-off-by: Vincent Guittot <vincent.guittot@linaro.org>
---
kernel/sched/fair.c | 75 +++++++++++++++++++++++++++++++++++++++++++--
1 file changed, 73 insertions(+), 2 deletions(-)
diff --git a/kernel/sched/fair.c b/kernel/sched/fair.c
index 3bcb663ebcde..f2a0d2c2bb63 100644
--- a/kernel/sched/fair.c
+++ b/kernel/sched/fair.c
@@ -10233,6 +10233,75 @@ static void fair_add_pushable_prev(struct rq *rq, struct task_struct *prev,
__fair_add_pushable_task(rq, prev);
}
+static int active_load_balance_cpu_stop(void *data);
+
+/*
+ * See if the alone task running on the CPU should migrate on a better than
+ * the local one.
+ */
+static inline bool tick_push_task(struct task_struct *p, struct rq *rq, struct rq_flags *rf)
+ __releases(__rq_lockp(rq))
+ __acquires(__rq_lockp(rq))
+{
+ int new_cpu, cpu = cpu_of(rq);
+
+ if (!sched_push_task_enabled())
+ return false;
+
+ if (!rf)
+ return false;
+
+ if (WARN_ON(!p))
+ return false;
+
+ if (WARN_ON(!task_current_donor(rq, p)))
+ return false;
+
+ if (is_migration_disabled(p))
+ return false;
+
+ /* If there are several task, wait for being put back */
+ if (rq->nr_running > 1)
+ return false;
+
+ if (!fair_check_pushable_task(rq, p, NULL))
+ return false;
+
+ if (!raw_spin_trylock(&p->pi_lock))
+ return false;
+
+ new_cpu = select_task_rq_fair(p, cpu, 0);
+
+ raw_spin_unlock(&p->pi_lock);
+
+ if (new_cpu == cpu)
+ return false;
+
+ /*
+ * ->active_balance synchronizes accesses to
+ * ->active_balance_work. Once set, it's cleared
+ * only after active load balance is finished.
+ */
+ if (!rq->active_balance) {
+ rq->active_balance = 1;
+ rq->push_cpu = new_cpu;
+ } else {
+ return false;
+ }
+
+ preempt_disable();
+ rq_unpin_lock(rq, rf);
+ raw_spin_rq_unlock(rq);
+ stop_one_cpu_nowait(cpu,
+ active_load_balance_cpu_stop, rq,
+ &rq->active_balance_work);
+ preempt_enable();
+ raw_spin_rq_lock(rq);
+ rq_repin_lock(rq, rf);
+
+ return true;
+}
+
/*
* select_task_rq_fair: Select a target runqueue for the task.
* There are 2 ways to select the target runqueue:
@@ -15713,8 +15782,10 @@ static void task_tick_fair(struct rq *rq, struct task_struct *curr, struct rq_fl
task_tick_cache(rq, curr);
- update_misfit_status(curr, rq);
- check_update_overutilized_status(task_rq(curr));
+ if (!tick_push_task(curr, rq, rf)) {
+ update_misfit_status(curr, rq);
+ check_update_overutilized_status(task_rq(curr));
+ }
task_tick_core(rq, curr);
}
--
2.53.0
^ permalink raw reply [flat|nested] 24+ messages in thread
* [PATCH 12/20 v3] sched/fair: Support not wakeup case in select_idle_sibling
2026-11-06 15:19 [PATCH 00/18 v3] Improving latency of short slice tasks Vincent Guittot
` (10 preceding siblings ...)
2026-11-06 15:19 ` [PATCH 11/20 v3] sched/fair: Add force push task mechanism for fair Vincent Guittot
@ 2026-11-06 15:19 ` Vincent Guittot
2026-11-06 15:19 ` [PATCH 13/20] sched/eevdf: Try to push short slice task on a better CPU Vincent Guittot
` (7 subsequent siblings)
19 siblings, 0 replies; 24+ messages in thread
From: Vincent Guittot @ 2026-11-06 15:19 UTC (permalink / raw)
To: mingo, peterz, juri.lelli, dietmar.eggemann, rostedt, bsegall,
mgorman, vschneid, kprateek.nayak, linux-kernel, lukasz.luba,
rafael, linux-pm, tj, void, arighi, changwoo, sched-ext
Cc: qyousef, christian.loehle, pierre.gondois, sshegde, kayracizmeci,
tim.c.chen, Vincent Guittot
With push callback mecanism, select_task_rq_fair() can be called for a
task that is already enqueued. Task into account this case when choosing
an idle CPU.
Signed-off-by: Vincent Guittot <vincent.guittot@linaro.org>
---
kernel/sched/fair.c | 31 +++++++++++++++++++++++--------
1 file changed, 23 insertions(+), 8 deletions(-)
diff --git a/kernel/sched/fair.c b/kernel/sched/fair.c
index f2a0d2c2bb63..980eda9c935a 100644
--- a/kernel/sched/fair.c
+++ b/kernel/sched/fair.c
@@ -8187,10 +8187,27 @@ static int choose_sched_idle_rq(struct rq *rq, struct task_struct *p)
return sched_idle_rq(rq) && !task_has_idle_policy(p);
}
+static int idle_rq_without(struct rq *rq, struct task_struct *p);
+static unsigned int task_running_on_cpu(int cpu, struct task_struct *p);
+
+static int choose_idle_rq_without(struct rq *rq, struct task_struct *p)
+{
+ if (rq->nr_running != 1)
+ return 0;
+
+ if (!task_running_on_cpu(cpu_of(rq), p))
+ return 0;
+
+ return idle_rq_without(rq, p);
+}
+
static int choose_idle_cpu(int cpu, struct task_struct *p)
{
+ struct rq *rq = cpu_rq(cpu);
+
return available_idle_cpu(cpu) ||
- choose_sched_idle_rq(cpu_rq(cpu), p);
+ choose_sched_idle_rq(rq, p) ||
+ choose_idle_rq_without(rq, p);
}
static void
@@ -12968,23 +12985,21 @@ static unsigned int task_running_on_cpu(int cpu, struct task_struct *p)
}
/**
- * idle_cpu_without - would a given CPU be idle without p ?
+ * idle_rq_without - would a given CPU be idle without p ?
* @cpu: the processor on which idleness is tested.
* @p: task which should be ignored.
*
* Return: 1 if the CPU would be idle. 0 otherwise.
*/
-static int idle_cpu_without(int cpu, struct task_struct *p)
+static int idle_rq_without(struct rq *rq, struct task_struct *p)
{
- struct rq *rq = cpu_rq(cpu);
-
if (rq->curr != rq->idle && rq->curr != p)
return 0;
/*
* rq->nr_running can't be used but an updated version without the
* impact of p on cpu must be used instead. The updated nr_running
- * be computed and tested before calling idle_cpu_without().
+ * be computed and tested before calling idle_rq_without().
*/
if (rq->ttwu_pending)
@@ -13027,9 +13042,9 @@ static inline void update_sg_wakeup_stats(struct sched_domain *sd,
sgs->sum_nr_running += nr_running;
/*
- * No need to call idle_cpu_without() if nr_running is not 0
+ * No need to call idle_rq_without() if nr_running is not 0
*/
- if (!nr_running && idle_cpu_without(i, p))
+ if (!nr_running && idle_rq_without(rq, p))
sgs->idle_cpus++;
/* Check if task fits in the CPU */
--
2.53.0
^ permalink raw reply [flat|nested] 24+ messages in thread
* [PATCH 13/20] sched/eevdf: Try to push short slice task on a better CPU
2026-11-06 15:19 [PATCH 00/18 v3] Improving latency of short slice tasks Vincent Guittot
` (11 preceding siblings ...)
2026-11-06 15:19 ` [PATCH 12/20 v3] sched/fair: Support not wakeup case in select_idle_sibling Vincent Guittot
@ 2026-11-06 15:19 ` Vincent Guittot
2026-11-06 15:19 ` [PATCH 14/20 v3] sched/eevdf: Push short slice task that are not picked Vincent Guittot
` (6 subsequent siblings)
19 siblings, 0 replies; 24+ messages in thread
From: Vincent Guittot @ 2026-11-06 15:19 UTC (permalink / raw)
To: mingo, peterz, juri.lelli, dietmar.eggemann, rostedt, bsegall,
mgorman, vschneid, kprateek.nayak, linux-kernel, lukasz.luba,
rafael, linux-pm, tj, void, arighi, changwoo, sched-ext
Cc: qyousef, christian.loehle, pierre.gondois, sshegde, kayracizmeci,
tim.c.chen, Vincent Guittot
When a task is put back in the runnable list, we check if we should try to
push it on a better CPU, i.e. when fair is preempted by a higher class or
the task is preempted by another fair task.
Signed-off-by: Vincent Guittot <vincent.guittot@linaro.org>
---
kernel/sched/fair.c | 23 +++++++++++++++++++++++
1 file changed, 23 insertions(+)
diff --git a/kernel/sched/fair.c b/kernel/sched/fair.c
index 980eda9c935a..3923587b66e0 100644
--- a/kernel/sched/fair.c
+++ b/kernel/sched/fair.c
@@ -10112,6 +10112,23 @@ static bool __check_pushable_fair_task(struct rq *rq, struct task_struct *p)
return true;
}
+static bool check_pushable_short_task(struct rq *rq, struct task_struct *p)
+{
+ struct sched_entity *pse = &p->se;
+ struct cfs_rq *cfs_rq = &rq->cfs;
+
+ if (cfs_rq->h_nr_runnable <= 1)
+ return false;
+
+ if (!entity_eligible(cfs_rq, pse))
+ return false;
+
+ if (pse->slice < cfs_rq_max_slice(cfs_rq))
+ return true;
+
+ return false;
+}
+
static bool fair_check_pushable_task(struct rq *rq, struct task_struct *p, struct task_struct *next)
{
if (!__check_pushable_fair_task(rq, p))
@@ -10120,6 +10137,12 @@ static bool fair_check_pushable_task(struct rq *rq, struct task_struct *p, struc
if (next == p)
return false;
+ if (next && next->sched_class != p->sched_class)
+ return true;
+
+ if (check_pushable_short_task(rq, p))
+ return true;
+
return false;
}
--
2.53.0
^ permalink raw reply [flat|nested] 24+ messages in thread
* [PATCH 14/20 v3] sched/eevdf: Push short slice task that are not picked
2026-11-06 15:19 [PATCH 00/18 v3] Improving latency of short slice tasks Vincent Guittot
` (12 preceding siblings ...)
2026-11-06 15:19 ` [PATCH 13/20] sched/eevdf: Try to push short slice task on a better CPU Vincent Guittot
@ 2026-11-06 15:19 ` Vincent Guittot
2026-11-06 15:19 ` [PATCH 15/20 v3] sched/fair: Enable push task for preempt short Vincent Guittot
` (5 subsequent siblings)
19 siblings, 0 replies; 24+ messages in thread
From: Vincent Guittot @ 2026-11-06 15:19 UTC (permalink / raw)
To: mingo, peterz, juri.lelli, dietmar.eggemann, rostedt, bsegall,
mgorman, vschneid, kprateek.nayak, linux-kernel, lukasz.luba,
rafael, linux-pm, tj, void, arighi, changwoo, sched-ext
Cc: qyousef, christian.loehle, pierre.gondois, sshegde, kayracizmeci,
tim.c.chen, Vincent Guittot
Even when set as next buddy, a number of event can prevent short slice
task to be picked. Add the task in the pushable list in case it is not
picked so it get a chance to be pushed on a better CPU.
This can typically happen when several short slice tasks are pushed on the
same CPU at wake up.
If the task is picked, it will be removed from the pushable list before we
queue the callback otherwise we will look for a better CPU.
Signed-off-by: Vincent Guittot <vincent.guittot@linaro.org>
---
kernel/sched/fair.c | 10 +++++++++-
1 file changed, 9 insertions(+), 1 deletion(-)
diff --git a/kernel/sched/fair.c b/kernel/sched/fair.c
index 3923587b66e0..894bee51b99a 100644
--- a/kernel/sched/fair.c
+++ b/kernel/sched/fair.c
@@ -10273,6 +10273,12 @@ static void fair_add_pushable_prev(struct rq *rq, struct task_struct *prev,
__fair_add_pushable_task(rq, prev);
}
+static void fair_add_pushable_short(struct rq *rq, struct task_struct *p)
+{
+ if (sched_push_task_enabled() && __check_pushable_fair_task(rq, p))
+ __fair_add_pushable_task(rq, p);
+}
+
static int active_load_balance_cpu_stop(void *data);
/*
@@ -10723,8 +10729,10 @@ static void wakeup_preempt_fair(struct rq *rq, struct task_struct *p, int wake_f
preempt:
cancel_protect_slice(se);
- if (preempt_action == PREEMPT_WAKEUP_SHORT)
+ if (preempt_action == PREEMPT_WAKEUP_SHORT) {
set_short_buddy(cfs_rq, pse);
+ fair_add_pushable_short(rq, p);
+ }
resched_curr_lazy(rq);
}
--
2.53.0
^ permalink raw reply [flat|nested] 24+ messages in thread
* [PATCH 15/20 v3] sched/fair: Enable push task for preempt short
2026-11-06 15:19 [PATCH 00/18 v3] Improving latency of short slice tasks Vincent Guittot
` (13 preceding siblings ...)
2026-11-06 15:19 ` [PATCH 14/20 v3] sched/eevdf: Push short slice task that are not picked Vincent Guittot
@ 2026-11-06 15:19 ` Vincent Guittot
2026-11-06 15:19 ` [PATCH 16/20 v3] energy model: Add a get previous state function Vincent Guittot
` (4 subsequent siblings)
19 siblings, 0 replies; 24+ messages in thread
From: Vincent Guittot @ 2026-11-06 15:19 UTC (permalink / raw)
To: mingo, peterz, juri.lelli, dietmar.eggemann, rostedt, bsegall,
mgorman, vschneid, kprateek.nayak, linux-kernel, lukasz.luba,
rafael, linux-pm, tj, void, arighi, changwoo, sched-ext
Cc: qyousef, christian.loehle, pierre.gondois, sshegde, kayracizmeci,
tim.c.chen, Vincent Guittot
Enable push mecanism for the preempt short feature which is the
1st feature using it.
Signed-off-by: Vincent Guittot <vincent.guittot@linaro.org>
---
kernel/sched/topology.c | 3 +++
1 file changed, 3 insertions(+)
diff --git a/kernel/sched/topology.c b/kernel/sched/topology.c
index 919d0fb00bd9..5481f2472e17 100644
--- a/kernel/sched/topology.c
+++ b/kernel/sched/topology.c
@@ -3497,6 +3497,9 @@ static void partition_sched_domains_locked(int ndoms_new, cpumask_var_t doms_new
sched_energy_set(has_eas);
#endif
+ if (sched_feat(PREEMPT_SHORT) && !static_branch_likely(&sched_push_task))
+ static_branch_enable_cpuslocked(&sched_push_task);
+
/* Remember the new sched domains: */
if (doms_cur != &fallback_doms)
free_sched_domains(doms_cur, ndoms_cur);
--
2.53.0
^ permalink raw reply [flat|nested] 24+ messages in thread
* [PATCH 16/20 v3] energy model: Add a get previous state function
2026-11-06 15:19 [PATCH 00/18 v3] Improving latency of short slice tasks Vincent Guittot
` (14 preceding siblings ...)
2026-11-06 15:19 ` [PATCH 15/20 v3] sched/fair: Enable push task for preempt short Vincent Guittot
@ 2026-11-06 15:19 ` Vincent Guittot
2026-11-06 15:19 ` [PATCH 17/20 v3] sched/fair: Rework feec() to use cost instead of spare capacity Vincent Guittot
` (3 subsequent siblings)
19 siblings, 0 replies; 24+ messages in thread
From: Vincent Guittot @ 2026-11-06 15:19 UTC (permalink / raw)
To: mingo, peterz, juri.lelli, dietmar.eggemann, rostedt, bsegall,
mgorman, vschneid, kprateek.nayak, linux-kernel, lukasz.luba,
rafael, linux-pm, tj, void, arighi, changwoo, sched-ext
Cc: qyousef, christian.loehle, pierre.gondois, sshegde, kayracizmeci,
tim.c.chen, Vincent Guittot
Instead of parsing the entire EM table everytime, add a function to get the
previous state.
Will be used in the scheduler feec() function.
Signed-off-by: Vincent Guittot <vincent.guittot@linaro.org>
---
include/linux/energy_model.h | 32 ++++++++++++++++++++++++++++++++
1 file changed, 32 insertions(+)
diff --git a/include/linux/energy_model.h b/include/linux/energy_model.h
index c909a8ba22e8..89cf109cbca6 100644
--- a/include/linux/energy_model.h
+++ b/include/linux/energy_model.h
@@ -224,6 +224,26 @@ em_pd_get_efficient_state(struct em_perf_state *table,
return max_ps;
}
+static inline int
+em_pd_get_previous_state(struct em_perf_state *table,
+ struct em_perf_domain *pd, int idx)
+{
+ unsigned long pd_flags = pd->flags;
+ int min_ps = pd->min_perf_state;
+ struct em_perf_state *ps;
+ int i;
+
+ for (i = idx - 1; i >= min_ps; i--) {
+ ps = &table[i];
+ if (pd_flags & EM_PERF_DOMAIN_SKIP_INEFFICIENCIES &&
+ ps->flags & EM_PERF_STATE_INEFFICIENT)
+ continue;
+ return i;
+ }
+
+ return -1;
+}
+
/**
* em_cpu_energy() - Estimates the energy consumed by the CPUs of a
* performance domain
@@ -375,6 +395,18 @@ static inline struct em_perf_domain *em_pd_get(struct device *dev)
{
return NULL;
}
+static inline int
+em_pd_get_efficient_state(struct em_perf_state *table,
+ struct em_perf_domain *pd, unsigned long max_util)
+{
+ return 0;
+}
+static inline int
+em_pd_get_previous_state(struct em_perf_state *table,
+ struct em_perf_domain *pd, int idx)
+{
+ return -1;
+}
static inline unsigned long em_cpu_energy(struct em_perf_domain *pd,
unsigned long max_util, unsigned long sum_util,
unsigned long allowed_cpu_cap)
--
2.53.0
^ permalink raw reply [flat|nested] 24+ messages in thread
* [PATCH 17/20 v3] sched/fair: Rework feec() to use cost instead of spare capacity
2026-11-06 15:19 [PATCH 00/18 v3] Improving latency of short slice tasks Vincent Guittot
` (15 preceding siblings ...)
2026-11-06 15:19 ` [PATCH 16/20 v3] energy model: Add a get previous state function Vincent Guittot
@ 2026-11-06 15:19 ` Vincent Guittot
2026-11-06 15:19 ` [PATCH 18/20 v3] energy model: Remove unused em_cpu_energy() Vincent Guittot
` (2 subsequent siblings)
19 siblings, 0 replies; 24+ messages in thread
From: Vincent Guittot @ 2026-11-06 15:19 UTC (permalink / raw)
To: mingo, peterz, juri.lelli, dietmar.eggemann, rostedt, bsegall,
mgorman, vschneid, kprateek.nayak, linux-kernel, lukasz.luba,
rafael, linux-pm, tj, void, arighi, changwoo, sched-ext
Cc: qyousef, christian.loehle, pierre.gondois, sshegde, kayracizmeci,
tim.c.chen, Vincent Guittot
feec() looks for the CPU with highest spare capacity in a PD assuming that
it will be the best CPU from a energy efficiency PoV because it will
require the smallest increase of OPP. Although this is true generally
speaking, this policy also filters some other CPUs which would be as
efficients because they use the same OPP.
In fact, we really care about the cost of the new OPP that will be
selected to handle the waking task. In many cases, several CPUs will end
up selecting the same OPP and as a result use the same energy cost. In
these cases, we can use other metrics to select the best CPU for the same
energy cost.
Rework feec() to look 1st for the lowest cost in a PD and then the most
performant CPU between CPUs. The cost of the OPP remains the criteria
between Performance Domains.
Signed-off-by: Vincent Guittot <vincent.guittot@linaro.org>
---
kernel/sched/fair.c | 472 ++++++++++++++++++++++++--------------------
1 file changed, 255 insertions(+), 217 deletions(-)
diff --git a/kernel/sched/fair.c b/kernel/sched/fair.c
index 894bee51b99a..ea117c3d0dd7 100644
--- a/kernel/sched/fair.c
+++ b/kernel/sched/fair.c
@@ -9730,29 +9730,35 @@ unsigned long sched_cpu_util(int cpu)
}
/*
- * energy_env - Utilization landscape for energy estimation.
- * @task_busy_time: Utilization contribution by the task for which we test the
- * placement. Given by eenv_task_busy_time().
- * @pd_busy_time: Utilization of the whole perf domain without the task
- * contribution. Given by eenv_pd_busy_time().
- * @cpu_cap: Maximum CPU capacity for the perf domain.
- * @pd_cap: Entire perf domain capacity. (pd->nr_cpus * cpu_cap).
+ * energy_cpu_stat - Utilization landscape for energy estimation.
+ * @idx : Index of the OPP in the performance domain
+ * @cost : Cost of the OPP
+ * @max_perf : Compute capacity of OPP
+ * @min_perf : Compute capacity of the previous OPP
+ * @capa : Capacity of the CPU
+ * @runnable : runnable_avg of the CPU
+ * @nr_running : Number of cfs running task
+ * @fits : Fits level of the CPU
+ * @cpu : Current best CPU
*/
-struct energy_env {
- unsigned long task_busy_time;
- unsigned long pd_busy_time;
- unsigned long cpu_cap;
- unsigned long pd_cap;
+struct energy_cpu_stat {
+ unsigned long cost;
+ unsigned long max_perf;
+ unsigned long min_perf;
+ unsigned long capa;
+ unsigned long runnable;
+ unsigned int nr_running;
+ int fits;
+ int cpu;
};
/*
- * Compute the task busy time for compute_energy(). This time cannot be
- * injected directly into effective_cpu_util() because of the IRQ scaling.
+ * Compute the task busy time for computing its energy impact. This time cannot
+ * be injected directly into effective_cpu_util() because of the IRQ scaling.
* The latter only makes sense with the most recent CPUs where the task has
* run.
*/
-static inline void eenv_task_busy_time(struct energy_env *eenv,
- struct task_struct *p, int prev_cpu)
+static inline unsigned long task_busy_time(struct task_struct *p, int prev_cpu)
{
unsigned long busy_time, max_cap = arch_scale_cpu_capacity(prev_cpu);
unsigned long irq = cpu_util_irq(cpu_rq(prev_cpu));
@@ -9762,124 +9768,161 @@ static inline void eenv_task_busy_time(struct energy_env *eenv,
else
busy_time = scale_irq_capacity(task_util_est(p), irq, max_cap);
- eenv->task_busy_time = busy_time;
+ return busy_time;
}
-/*
- * Compute the perf_domain (PD) busy time for compute_energy(). Based on the
- * utilization for each @pd_cpus, it however doesn't take into account
- * clamping since the ratio (utilization / cpu_capacity) is already enough to
- * scale the EM reported power consumption at the (eventually clamped)
- * cpu_capacity.
- *
- * The contribution of the task @p for which we want to estimate the
- * energy cost is removed (by cpu_util()) and must be calculated
- * separately (see eenv_task_busy_time). This ensures:
- *
- * - A stable PD utilization, no matter which CPU of that PD we want to place
- * the task on.
- *
- * - A fair comparison between CPUs as the task contribution (task_util())
- * will always be the same no matter which CPU utilization we rely on
- * (util_avg or util_est).
- *
- * Set @eenv busy time for the PD that spans @pd_cpus. This busy time can't
- * exceed @eenv->pd_cap.
- */
-static inline void eenv_pd_busy_time(struct energy_env *eenv,
- struct cpumask *pd_cpus,
- struct task_struct *p)
+/* Estimate the utilization of the CPU that is then used to select the OPP */
+static unsigned long find_cpu_max_util(int cpu, struct task_struct *p, int dst_cpu)
{
- unsigned long busy_time = 0;
- int cpu;
+ unsigned long util = cpu_util(cpu, p, dst_cpu, 1);
+ unsigned long eff_util, min, max;
+
+ /*
+ * Performance domain frequency: utilization clamping
+ * must be considered since it affects the selection
+ * of the performance domain frequency.
+ */
+ eff_util = effective_cpu_util(cpu, util, &min, &max);
- for_each_cpu(cpu, pd_cpus) {
- unsigned long util = cpu_util(cpu, p, -1, 0);
+ /* Task's uclamp can modify min and max value */
+ if (uclamp_is_used() && cpu == dst_cpu) {
+ min = max(min, uclamp_eff_value(p, UCLAMP_MIN));
- busy_time += effective_cpu_util(cpu, util, NULL, NULL);
+ /*
+ * If there is no active max uclamp constraint,
+ * directly use task's one, otherwise keep max.
+ */
+ if (uclamp_rq_is_idle(cpu_rq(cpu)))
+ max = uclamp_eff_value(p, UCLAMP_MAX);
+ else
+ max = max(max, uclamp_eff_value(p, UCLAMP_MAX));
}
- eenv->pd_busy_time = min(eenv->pd_cap, busy_time);
+ eff_util = sugov_effective_cpu_perf(cpu, eff_util, min, max);
+ return eff_util;
}
-/*
- * Compute the maximum utilization for compute_energy() when the task @p
- * is placed on the cpu @dst_cpu.
- *
- * Returns the maximum utilization among @eenv->cpus. This utilization can't
- * exceed @eenv->cpu_cap.
- */
-static inline unsigned long
-eenv_pd_max_util(struct energy_env *eenv, struct cpumask *pd_cpus,
- struct task_struct *p, int dst_cpu)
+/* Estimate the utilization of the CPU without the task */
+static unsigned long find_cpu_actual_util(int cpu, struct task_struct *p)
{
- unsigned long max_util = 0;
- int cpu;
+ unsigned long util = cpu_util(cpu, p, -1, 0);
+ unsigned long eff_util;
- for_each_cpu(cpu, pd_cpus) {
- struct task_struct *tsk = (cpu == dst_cpu) ? p : NULL;
- unsigned long util = cpu_util(cpu, p, dst_cpu, 1);
- unsigned long eff_util, min, max;
+ eff_util = effective_cpu_util(cpu, util, NULL, NULL);
- /*
- * Performance domain frequency: utilization clamping
- * must be considered since it affects the selection
- * of the performance domain frequency.
- * NOTE: in case RT tasks are running, by default the min
- * utilization can be max OPP.
- */
- eff_util = effective_cpu_util(cpu, util, &min, &max);
+ return eff_util;
+}
- /* Task's uclamp can modify min and max value */
- if (tsk && uclamp_is_used()) {
- min = max(min, uclamp_eff_value(p, UCLAMP_MIN));
+/* Find the cost of a performance domain for the estimated utilization */
+static inline void find_pd_cost(struct em_perf_domain *pd,
+ unsigned long max_util,
+ struct energy_cpu_stat *stat)
+{
+ struct em_perf_table *em_table;
+ struct em_perf_state *ps;
+ int i;
- /*
- * If there is no active max uclamp constraint,
- * directly use task's one, otherwise keep max.
- */
- if (uclamp_rq_is_idle(cpu_rq(cpu)))
- max = uclamp_eff_value(p, UCLAMP_MAX);
- else
- max = max(max, uclamp_eff_value(p, UCLAMP_MAX));
- }
+ /*
+ * Find the lowest performance state of the Energy Model above the
+ * requested performance.
+ */
+ em_table = rcu_dereference_all(pd->em_table);
+ i = em_pd_get_efficient_state(em_table->state, pd, max_util);
+ ps = &em_table->state[i];
- eff_util = sugov_effective_cpu_perf(cpu, eff_util, min, max);
- max_util = max(max_util, eff_util);
+ /* Save the cost and performance range of the OPP */
+ stat->max_perf = ps->performance;
+ stat->cost = ps->cost;
+ i = em_pd_get_previous_state(em_table->state, pd, i);
+ if (i < 0) {
+ stat->min_perf = 0;
+ } else {
+ ps = &em_table->state[i];
+ stat->min_perf = ps->performance;
}
+}
- return min(max_util, eenv->cpu_cap);
+/*Check if the CPU can handle the waking task */
+static int check_cpu_with_task(struct task_struct *p, int cpu)
+{
+ unsigned long p_util_min = uclamp_is_used() ? uclamp_eff_value(p, UCLAMP_MIN) : 0;
+ unsigned long p_util_max = uclamp_is_used() ? uclamp_eff_value(p, UCLAMP_MAX) : 1024;
+ unsigned long util_min = p_util_min;
+ unsigned long util_max = p_util_max;
+ unsigned long util = cpu_util(cpu, p, cpu, 0);
+ struct rq *rq = cpu_rq(cpu);
+
+ /*
+ * Skip CPUs that cannot satisfy the capacity request.
+ * IOW, placing the task there would make the CPU
+ * overutilized. Take uclamp into account to see how
+ * much capacity we can get out of the CPU; this is
+ * aligned with sched_cpu_util().
+ */
+ if (uclamp_is_used() && !uclamp_rq_is_idle(rq)) {
+ unsigned long rq_util_min, rq_util_max;
+ /*
+ * Open code uclamp_rq_util_with() except for
+ * the clamp() part. I.e.: apply max aggregation
+ * only. util_fits_cpu() logic requires to
+ * operate on non clamped util but must use the
+ * max-aggregated uclamp_{min, max}.
+ */
+ rq_util_min = uclamp_rq_get(rq, UCLAMP_MIN);
+ rq_util_max = uclamp_rq_get(rq, UCLAMP_MAX);
+ util_min = max(rq_util_min, p_util_min);
+ util_max = max(rq_util_max, p_util_max);
+ }
+ return util_fits_cpu(util, util_min, util_max, cpu);
}
/*
- * compute_energy(): Use the Energy Model to estimate the energy that @pd would
- * consume for a given utilization landscape @eenv. When @dst_cpu < 0, the task
- * contribution is ignored.
+ * For the same cost, select the CPU that will provide best performance for the
+ * task.
*/
-static inline unsigned long
-compute_energy(struct energy_env *eenv, struct perf_domain *pd,
- struct cpumask *pd_cpus, struct task_struct *p, int dst_cpu)
+static bool update_best_cpu(struct energy_cpu_stat *target,
+ struct energy_cpu_stat *min,
+ int prev, struct sched_domain *sd,
+ struct task_struct *p)
{
- unsigned long max_util = eenv_pd_max_util(eenv, pd_cpus, p, dst_cpu);
- unsigned long busy_time = eenv->pd_busy_time;
- unsigned long energy;
+ bool target_idle = choose_idle_cpu(target->cpu, p);
+ bool min_idle = choose_idle_cpu(min->cpu, p);
+
+ /* Select the one which is idle */
+ if (target_idle != min_idle)
+ return target_idle;
- if (dst_cpu >= 0)
- busy_time = min(eenv->pd_cap, busy_time + eenv->task_busy_time);
+ /* Favor previous CPU */
+ if (target->cpu == prev)
+ return true;
+ if (min->cpu == prev)
+ return false;
- energy = em_cpu_energy(pd->em_pd, max_util, busy_time, eenv->cpu_cap);
+ /* Select the one with the least number of running tasks otherwise */
+ if (target->nr_running < min->nr_running)
+ return true;
+ if (target->nr_running > min->nr_running)
+ return false;
- trace_sched_compute_energy_tp(p, dst_cpu, energy, max_util, busy_time);
+ /*
+ * Choose CPU with lowest contention. One might want to consider load
+ * instead of runnable but we are supposed to not be overutilized so
+ * there is enough compute capacity for everybody.
+ */
+ if ((target->runnable * min->capa * sd->imbalance_pct) >=
+ (min->runnable * target->capa * 100))
+ return false;
- return energy;
+ return true;
}
/*
* find_energy_efficient_cpu(): Find most energy-efficient target CPU for the
- * waking task. find_energy_efficient_cpu() looks for the CPU with maximum
- * spare capacity in each performance domain and uses it as a potential
- * candidate to execute the task. Then, it uses the Energy Model to figure
- * out which of the CPU candidates is the most energy-efficient.
+ * waking task. find_energy_efficient_cpu() looks for the CPU with the lowest
+ * power cost (usually with maximum spare capacity but not always) in each
+ * performance domain and uses it as a potential candidate to execute the task.
+ * Then, it uses the Energy Model to figure out which of the CPU candidates is
+ * the most energy-efficient.
*
* The rationale for this heuristic is as follows. In a performance domain,
* all the most energy efficient CPU candidates (according to the Energy
@@ -9916,17 +9959,14 @@ compute_energy(struct energy_env *eenv, struct perf_domain *pd,
static int find_energy_efficient_cpu(struct task_struct *p, int prev_cpu)
{
struct cpumask *cpus = this_cpu_cpumask_var_ptr(select_rq_mask);
- unsigned long prev_delta = ULONG_MAX, best_delta = ULONG_MAX;
- unsigned long p_util_min = uclamp_is_used() ? uclamp_eff_value(p, UCLAMP_MIN) : 0;
- unsigned long p_util_max = uclamp_is_used() ? uclamp_eff_value(p, UCLAMP_MAX) : 1024;
struct root_domain *rd = this_rq()->rd;
- int cpu, best_energy_cpu, target = -1;
- int prev_fits = -1, best_fits = -1;
- unsigned long best_actual_cap = 0;
- unsigned long prev_actual_cap = 0;
+ unsigned long best_nrg = ULONG_MAX;
+ unsigned long task_util;
struct sched_domain *sd;
struct perf_domain *pd;
- struct energy_env eenv;
+ int cpu, target = -1;
+ int best_fits = -1;
+ int best_cpu = -1;
pd = rcu_dereference_all(rd->pd);
if (!pd)
@@ -9945,19 +9985,19 @@ static int find_energy_efficient_cpu(struct task_struct *p, int prev_cpu)
target = prev_cpu;
sync_task_load_avg(p);
- if (!task_util_est(p) && p_util_min == 0)
- return target;
-
- eenv_task_busy_time(&eenv, p, prev_cpu);
+ task_util = task_busy_time(p, prev_cpu);
for (; pd; pd = pd->next) {
- unsigned long util_min = p_util_min, util_max = p_util_max;
- unsigned long cpu_cap, cpu_actual_cap, util;
- long prev_spare_cap = -1, max_spare_cap = -1;
- unsigned long rq_util_min, rq_util_max;
- unsigned long cur_delta, base_energy;
- int max_spare_cap_cpu = -1;
- int fits, max_fits = -1;
+ unsigned long pd_actual_util = 0, delta_nrg = 0;
+ unsigned long cpu_actual_cap, max_cost = 0;
+ struct energy_cpu_stat target_stat;
+ struct energy_cpu_stat min_stat = {
+ .cost = ULONG_MAX,
+ .max_perf = ULONG_MAX,
+ .min_perf = ULONG_MAX,
+ .fits = -2,
+ .cpu = -1,
+ };
if (!cpumask_and(cpus, perf_domain_span(pd), cpu_online_mask))
continue;
@@ -9966,13 +10006,9 @@ static int find_energy_efficient_cpu(struct task_struct *p, int prev_cpu)
cpu = cpumask_first(cpus);
cpu_actual_cap = get_actual_cpu_capacity(cpu);
- eenv.cpu_cap = cpu_actual_cap;
- eenv.pd_cap = 0;
-
+ /* In a PD, the CPU with the lowest cost will be the most efficient */
for_each_cpu(cpu, cpus) {
- struct rq *rq = cpu_rq(cpu);
-
- eenv.pd_cap += cpu_actual_cap;
+ unsigned long target_perf;
if (!cpumask_test_cpu(cpu, sched_domain_span(sd)))
continue;
@@ -9980,113 +10016,115 @@ static int find_energy_efficient_cpu(struct task_struct *p, int prev_cpu)
if (!cpumask_test_cpu(cpu, p->cpus_ptr))
continue;
- util = cpu_util(cpu, p, cpu, 0);
- cpu_cap = capacity_of(cpu);
+ target_stat.fits = check_cpu_with_task(p, cpu);
- /*
- * Skip CPUs that cannot satisfy the capacity request.
- * IOW, placing the task there would make the CPU
- * overutilized. Take uclamp into account to see how
- * much capacity we can get out of the CPU; this is
- * aligned with sched_cpu_util().
- */
- if (uclamp_is_used() && !uclamp_rq_is_idle(rq)) {
- /*
- * Open code uclamp_rq_util_with() except for
- * the clamp() part. I.e.: apply max aggregation
- * only. util_fits_cpu() logic requires to
- * operate on non clamped util but must use the
- * max-aggregated uclamp_{min, max}.
- */
- rq_util_min = uclamp_rq_get(rq, UCLAMP_MIN);
- rq_util_max = uclamp_rq_get(rq, UCLAMP_MAX);
+ if (!target_stat.fits)
+ continue;
- util_min = max(rq_util_min, p_util_min);
- util_max = max(rq_util_max, p_util_max);
- }
+ /* 1st select the CPU that fits best */
+ if (target_stat.fits < min_stat.fits)
+ continue;
+
+ /* Then select the CPU with lowest cost */
+
+ /* Get the performance of the CPU w/ the waking task */
+ target_perf = find_cpu_max_util(cpu, p, cpu);
+ target_perf = min(target_perf, cpu_actual_cap);
- fits = util_fits_cpu(util, util_min, util_max, cpu);
- if (!fits)
+ /* Needing a higher OPP means a higher cost */
+ if (target_perf > min_stat.max_perf)
continue;
- lsub_positive(&cpu_cap, util);
+ /*
+ * At this point, target's cost can be either equal or
+ * lower than the current minimum cost.
+ */
- if (cpu == prev_cpu) {
- /* Always use prev_cpu as a candidate. */
- prev_spare_cap = cpu_cap;
- prev_fits = fits;
- } else if ((fits > max_fits) ||
- ((fits == max_fits) && ((long)cpu_cap > max_spare_cap))) {
- /*
- * Find the CPU with the maximum spare capacity
- * among the remaining CPUs in the performance
- * domain.
- */
- max_spare_cap = cpu_cap;
- max_spare_cap_cpu = cpu;
- max_fits = fits;
- }
+ /* Gather more statistics */
+ target_stat.cpu = cpu;
+ target_stat.runnable = cpu_runnable(cpu_rq(cpu));
+ target_stat.capa = capacity_of(cpu);
+ target_stat.nr_running = cpu_rq(cpu)->cfs.h_nr_runnable;
+ if (p->on_rq && !p->se.sched_delayed && cpu == prev_cpu)
+ target_stat.nr_running--;
+
+ /* If the target needs a lower OPP, then look up for
+ * the corresponding OPP and its associated cost.
+ * Otherwise at same cost level, select the CPU which
+ * provides best performance.
+ */
+ if (target_perf < min_stat.min_perf)
+ find_pd_cost(pd->em_pd, target_perf, &target_stat);
+ else if (!update_best_cpu(&target_stat, &min_stat, prev_cpu, sd))
+ continue;
+
+ /* Save the new most efficient CPU of the PD */
+ min_stat = target_stat;
}
- if (max_spare_cap_cpu < 0 && prev_spare_cap < 0)
+ if (min_stat.cpu == -1)
continue;
- eenv_pd_busy_time(&eenv, cpus, p);
- /* Compute the 'base' energy of the pd, without @p */
- base_energy = compute_energy(&eenv, pd, cpus, p, -1);
-
- /* Evaluate the energy impact of using prev_cpu. */
- if (prev_spare_cap > -1) {
- prev_delta = compute_energy(&eenv, pd, cpus, p,
- prev_cpu);
- /* CPU utilization has changed */
- if (prev_delta < base_energy)
- return target;
- prev_delta -= base_energy;
- prev_actual_cap = cpu_actual_cap;
- best_delta = min(best_delta, prev_delta);
- }
+ if (min_stat.fits < best_fits)
+ continue;
- /* Evaluate the energy impact of using max_spare_cap_cpu. */
- if (max_spare_cap_cpu >= 0 && max_spare_cap > prev_spare_cap) {
- /* Current best energy cpu fits better */
- if (max_fits < best_fits)
- continue;
+ /* Idle system costs nothing */
+ target_stat.max_perf = 0;
+ target_stat.cost = 0;
- /*
- * Both don't fit performance hint (i.e. uclamp_min)
- * but best energy cpu has better capacity.
- */
- if ((max_fits < 0) &&
- (cpu_actual_cap <= best_actual_cap))
- continue;
+ /* Estimate utilization and cost without p */
+ for_each_cpu(cpu, cpus) {
+ unsigned long target_util;
- cur_delta = compute_energy(&eenv, pd, cpus, p,
- max_spare_cap_cpu);
- /* CPU utilization has changed */
- if (cur_delta < base_energy)
- return target;
- cur_delta -= base_energy;
+ /* Accumulate actual utilization w/o task p */
+ pd_actual_util += find_cpu_actual_util(cpu, p);
- /*
- * Both fit for the task but best energy cpu has lower
- * energy impact.
- */
- if ((max_fits > 0) && (best_fits > 0) &&
- (cur_delta >= best_delta))
+ /* Get the max utilization of the CPU w/o task p */
+ target_util = find_cpu_max_util(cpu, p, -1);
+ target_util = min(target_util, cpu_actual_cap);
+
+ /* Current OPP is enough */
+ if (target_util <= target_stat.max_perf)
continue;
- best_delta = cur_delta;
- best_energy_cpu = max_spare_cap_cpu;
- best_fits = max_fits;
- best_actual_cap = cpu_actual_cap;
+ /* Compute and save the cost of the OPP */
+ find_pd_cost(pd->em_pd, target_util, &target_stat);
+ max_cost = target_stat.cost;
}
+
+ /* Add the energy cost of p */
+ delta_nrg = task_util * min_stat.cost;
+
+ /*
+ * Compute the energy cost of others running at higher OPP
+ * because of p.
+ */
+ if (min_stat.cost > max_cost)
+ delta_nrg += pd_actual_util * (min_stat.cost - max_cost);
+
+ /* Delta energy with p */
+ trace_sched_compute_energy_tp(p, min_stat.cpu, delta_nrg,
+ min_stat.max_perf,
+ pd_actual_util + task_util);
+
+ /*
+ * The probability that delta energies are equals is almost
+ * null. PDs being sorted by max capacity, keep the one with
+ * highest max capacity if this happens.
+ * TODO: add a margin in energy cost and take into account
+ * other stats.
+ */
+ if (min_stat.fits == best_fits &&
+ delta_nrg >= best_nrg)
+ continue;
+
+ best_fits = min_stat.fits;
+ best_nrg = delta_nrg;
+ best_cpu = min_stat.cpu;
}
- if ((best_fits > prev_fits) ||
- ((best_fits > 0) && (best_delta < prev_delta)) ||
- ((best_fits < 0) && (best_actual_cap > prev_actual_cap)))
- target = best_energy_cpu;
+ if (best_cpu >= 0)
+ target = best_cpu;
return target;
}
--
2.53.0
^ permalink raw reply [flat|nested] 24+ messages in thread
* [PATCH 18/20 v3] energy model: Remove unused em_cpu_energy()
2026-11-06 15:19 [PATCH 00/18 v3] Improving latency of short slice tasks Vincent Guittot
` (16 preceding siblings ...)
2026-11-06 15:19 ` [PATCH 17/20 v3] sched/fair: Rework feec() to use cost instead of spare capacity Vincent Guittot
@ 2026-11-06 15:19 ` Vincent Guittot
2026-11-06 15:19 ` [PATCH 19/20 v3] sched/fair: Take into account slice in EAS Vincent Guittot
2026-11-06 15:19 ` [PATCH 20/20 v3] sched/fair: Fix tasks stacking on same idle CPU Vincent Guittot
19 siblings, 0 replies; 24+ messages in thread
From: Vincent Guittot @ 2026-11-06 15:19 UTC (permalink / raw)
To: mingo, peterz, juri.lelli, dietmar.eggemann, rostedt, bsegall,
mgorman, vschneid, kprateek.nayak, linux-kernel, lukasz.luba,
rafael, linux-pm, tj, void, arighi, changwoo, sched-ext
Cc: qyousef, christian.loehle, pierre.gondois, sshegde, kayracizmeci,
tim.c.chen, Vincent Guittot
Remove the unused function em_cpu_energy()
Signed-off-by: Vincent Guittot <vincent.guittot@linaro.org>
---
include/linux/energy_model.h | 97 ------------------------------------
1 file changed, 97 deletions(-)
diff --git a/include/linux/energy_model.h b/include/linux/energy_model.h
index 89cf109cbca6..c4f9558b58d1 100644
--- a/include/linux/energy_model.h
+++ b/include/linux/energy_model.h
@@ -244,97 +244,6 @@ em_pd_get_previous_state(struct em_perf_state *table,
return -1;
}
-/**
- * em_cpu_energy() - Estimates the energy consumed by the CPUs of a
- * performance domain
- * @pd : performance domain for which energy has to be estimated
- * @max_util : highest utilization among CPUs of the domain
- * @sum_util : sum of the utilization of all CPUs in the domain
- * @allowed_cpu_cap : maximum allowed CPU capacity for the @pd, which
- * might reflect reduced frequency (due to thermal)
- *
- * This function must be used only for CPU devices. There is no validation,
- * i.e. if the EM is a CPU type and has cpumask allocated. It is called from
- * the scheduler code quite frequently and that is why there is not checks.
- *
- * Return: the sum of the energy consumed by the CPUs of the domain assuming
- * a capacity state satisfying the max utilization of the domain.
- */
-static inline unsigned long em_cpu_energy(struct em_perf_domain *pd,
- unsigned long max_util, unsigned long sum_util,
- unsigned long allowed_cpu_cap)
-{
- struct em_perf_table *em_table;
- struct em_perf_state *ps;
- int i;
-
- lockdep_assert(rcu_read_lock_any_held());
-
- if (!sum_util)
- return 0;
-
- /*
- * In order to predict the performance state, map the utilization of
- * the most utilized CPU of the performance domain to a requested
- * performance, like schedutil. Take also into account that the real
- * performance might be set lower (due to thermal capping). Thus, clamp
- * max utilization to the allowed CPU capacity before calculating
- * effective performance.
- */
- max_util = min(max_util, allowed_cpu_cap);
-
- /*
- * Find the lowest performance state of the Energy Model above the
- * requested performance.
- */
- em_table = rcu_dereference_all(pd->em_table);
- i = em_pd_get_efficient_state(em_table->state, pd, max_util);
- ps = &em_table->state[i];
-
- /*
- * The performance (capacity) of a CPU in the domain at the performance
- * state (ps) can be computed as:
- *
- * ps->freq * scale_cpu
- * ps->performance = -------------------- (1)
- * cpu_max_freq
- *
- * So, ignoring the costs of idle states (which are not available in
- * the EM), the energy consumed by this CPU at that performance state
- * is estimated as:
- *
- * ps->power * cpu_util
- * cpu_nrg = -------------------- (2)
- * ps->performance
- *
- * since 'cpu_util / ps->performance' represents its percentage of busy
- * time.
- *
- * NOTE: Although the result of this computation actually is in
- * units of power, it can be manipulated as an energy value
- * over a scheduling period, since it is assumed to be
- * constant during that interval.
- *
- * By injecting (1) in (2), 'cpu_nrg' can be re-expressed as a product
- * of two terms:
- *
- * ps->power * cpu_max_freq
- * cpu_nrg = ------------------------ * cpu_util (3)
- * ps->freq * scale_cpu
- *
- * The first term is static, and is stored in the em_perf_state struct
- * as 'ps->cost'.
- *
- * Since all CPUs of the domain have the same micro-architecture, they
- * share the same 'ps->cost', and the same CPU capacity. Hence, the
- * total energy of the domain (which is the simple sum of the energy of
- * all of its CPUs) can be factorized as:
- *
- * pd_nrg = ps->cost * \Sum cpu_util (4)
- */
- return ps->cost * sum_util;
-}
-
/**
* em_pd_nr_perf_states() - Get the number of performance states of a perf.
* domain
@@ -407,12 +316,6 @@ em_pd_get_previous_state(struct em_perf_state *table,
{
return -1;
}
-static inline unsigned long em_cpu_energy(struct em_perf_domain *pd,
- unsigned long max_util, unsigned long sum_util,
- unsigned long allowed_cpu_cap)
-{
- return 0;
-}
static inline int em_pd_nr_perf_states(struct em_perf_domain *pd)
{
return 0;
--
2.53.0
^ permalink raw reply [flat|nested] 24+ messages in thread
* [PATCH 19/20 v3] sched/fair: Take into account slice in EAS
2026-11-06 15:19 [PATCH 00/18 v3] Improving latency of short slice tasks Vincent Guittot
` (17 preceding siblings ...)
2026-11-06 15:19 ` [PATCH 18/20 v3] energy model: Remove unused em_cpu_energy() Vincent Guittot
@ 2026-11-06 15:19 ` Vincent Guittot
2026-11-06 15:19 ` [PATCH 20/20 v3] sched/fair: Fix tasks stacking on same idle CPU Vincent Guittot
19 siblings, 0 replies; 24+ messages in thread
From: Vincent Guittot @ 2026-11-06 15:19 UTC (permalink / raw)
To: mingo, peterz, juri.lelli, dietmar.eggemann, rostedt, bsegall,
mgorman, vschneid, kprateek.nayak, linux-kernel, lukasz.luba,
rafael, linux-pm, tj, void, arighi, changwoo, sched-ext
Cc: qyousef, christian.loehle, pierre.gondois, sshegde, kayracizmeci,
tim.c.chen, Vincent Guittot
When the cost is the same, take into account the slice of a task to try to
select a CPU where is will run first.
Signed-off-by: Vincent Guittot <vincent.guittot@linaro.org>
---
kernel/sched/fair.c | 11 ++++++++++-
1 file changed, 10 insertions(+), 1 deletion(-)
diff --git a/kernel/sched/fair.c b/kernel/sched/fair.c
index ea117c3d0dd7..da9c4416f1b5 100644
--- a/kernel/sched/fair.c
+++ b/kernel/sched/fair.c
@@ -9885,13 +9885,22 @@ static bool update_best_cpu(struct energy_cpu_stat *target,
int prev, struct sched_domain *sd,
struct task_struct *p)
{
+ unsigned long task_slice = p->se.slice;
bool target_idle = choose_idle_cpu(target->cpu, p);
bool min_idle = choose_idle_cpu(min->cpu, p);
+ bool target_first, min_first;
/* Select the one which is idle */
if (target_idle != min_idle)
return target_idle;
+ target_first = task_slice < get_rq_min_slice(cpu_rq(target->cpu));
+ min_first = task_slice < get_rq_min_slice(cpu_rq(min->cpu));
+
+ /* Select the one where you can run first */
+ if (target_first != min_first)
+ return target_first;
+
/* Favor previous CPU */
if (target->cpu == prev)
return true;
@@ -10055,7 +10064,7 @@ static int find_energy_efficient_cpu(struct task_struct *p, int prev_cpu)
*/
if (target_perf < min_stat.min_perf)
find_pd_cost(pd->em_pd, target_perf, &target_stat);
- else if (!update_best_cpu(&target_stat, &min_stat, prev_cpu, sd))
+ else if (!update_best_cpu(&target_stat, &min_stat, prev_cpu, sd, p))
continue;
/* Save the new most efficient CPU of the PD */
--
2.53.0
^ permalink raw reply [flat|nested] 24+ messages in thread
* [PATCH 20/20 v3] sched/fair: Fix tasks stacking on same idle CPU
2026-11-06 15:19 [PATCH 00/18 v3] Improving latency of short slice tasks Vincent Guittot
` (18 preceding siblings ...)
2026-11-06 15:19 ` [PATCH 19/20 v3] sched/fair: Take into account slice in EAS Vincent Guittot
@ 2026-11-06 15:19 ` Vincent Guittot
19 siblings, 0 replies; 24+ messages in thread
From: Vincent Guittot @ 2026-11-06 15:19 UTC (permalink / raw)
To: mingo, peterz, juri.lelli, dietmar.eggemann, rostedt, bsegall,
mgorman, vschneid, kprateek.nayak, linux-kernel, lukasz.luba,
rafael, linux-pm, tj, void, arighi, changwoo, sched-ext
Cc: qyousef, christian.loehle, pierre.gondois, sshegde, kayracizmeci,
tim.c.chen, Vincent Guittot
When tasks wake up simultaneously, they can easily stack on the same idle
CPU but only one will run immediately. For others, a different CPU might
finally be a better choice. Take advantage of the push callback to try to
find another CPU for those tasks which lost the fight and haven't been
picked.
Call also sched_class->wakeup_preempt for the higher class so fair can be
prepared of handling several tasks stacked on the CPU. Other class returns
immadiately.
Signed-off-by: Vincent Guittot <vincent.guittot@linaro.org>
---
kernel/sched/core.c | 1 +
kernel/sched/fair.c | 16 ++++++++++++++--
2 files changed, 15 insertions(+), 2 deletions(-)
diff --git a/kernel/sched/core.c b/kernel/sched/core.c
index a1a3aa45ecf6..ef4cbcca2437 100644
--- a/kernel/sched/core.c
+++ b/kernel/sched/core.c
@@ -2311,6 +2311,7 @@ void wakeup_preempt(struct rq *rq, struct task_struct *p, int flags)
rq->next_class->wakeup_preempt(rq, p, flags);
resched_curr(rq);
rq->next_class = p->sched_class;
+ rq->next_class->wakeup_preempt(rq, p, flags);
}
/*
diff --git a/kernel/sched/fair.c b/kernel/sched/fair.c
index da9c4416f1b5..262626455497 100644
--- a/kernel/sched/fair.c
+++ b/kernel/sched/fair.c
@@ -10649,10 +10649,22 @@ static void wakeup_preempt_fair(struct rq *rq, struct task_struct *p, int wake_f
/*
* XXX Getting preempted by higher class, try and find idle CPU?
*/
- if (p->sched_class != &fair_sched_class ||
- donor->sched_class != &fair_sched_class)
+ if (p->sched_class != &fair_sched_class)
return;
+ if (donor->sched_class != &fair_sched_class) {
+ /*
+ * When rq is idle, several tasks can select this rq at wakeup
+ * without knowing others did the same. Give another chance to
+ * select another rq if not picked.
+ */
+ if (sched_class_above(&fair_sched_class, donor->sched_class) &&
+ entity_eligible(cfs_rq, pse))
+ fair_add_pushable_short(rq, p);
+
+ return;
+ }
+
if (unlikely(se == pse))
return;
--
2.53.0
^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [PATCH 02/20 v3] sched/eevdf: Reset lag at wakeup when prev CPU has been idle
2026-11-06 15:19 ` [PATCH 02/20 v3] sched/eevdf: Reset lag at wakeup when prev CPU has been idle Vincent Guittot
@ 2026-11-06 16:22 ` Kayra Cizmeci
0 siblings, 0 replies; 24+ messages in thread
From: Kayra Cizmeci @ 2026-11-06 16:22 UTC (permalink / raw)
To: Vincent Guittot
Cc: mingo, peterz, juri.lelli, dietmar.eggemann, rostedt, bsegall,
mgorman, vschneid, kprateek.nayak, linux-kernel, lukasz.luba,
rafael, linux-pm, tj, void, arighi, changwoo, sched-ext, qyousef,
christian.loehle, pierre.gondois, sshegde, tim.c.chen
On Fri, Nov 06, 2026 at 04:19:29PM +0100, Vincent Guittot wrote:
> @@ -8376,6 +8383,9 @@ static bool __dequeue_task(struct rq *rq, struct task_struct *p, int flags)
>
> dequeue_hierarchy(p, flags);
>
> + if (!(flags & DEQUEUE_SAVE) && !cfs_rq->h_nr_queued)
> + cfs_rq->idle_seq++;
This needs WRITE_ONCE, no? 3/20 uses READ_ONCE().
Cheers,
Kayra
^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [PATCH 03/20 v3] sched/eevdf: Reset lag at wakeup when migrating and prev has been idle
2026-11-06 15:19 ` [PATCH 03/20 v3] sched/eevdf: Reset lag at wakeup when migrating and prev " Vincent Guittot
@ 2026-11-06 18:05 ` Kayra Cizmeci
0 siblings, 0 replies; 24+ messages in thread
From: Kayra Cizmeci @ 2026-11-06 18:05 UTC (permalink / raw)
To: Vincent Guittot
Cc: mingo, peterz, juri.lelli, dietmar.eggemann, rostedt, bsegall,
mgorman, vschneid, kprateek.nayak, linux-kernel, lukasz.luba,
rafael, linux-pm, tj, void, arighi, changwoo, sched-ext, qyousef,
christian.loehle, pierre.gondois, sshegde, tim.c.chen
On Fri, Nov 06, 2026 at 04:19:30PM +0100, Vincent Guittot wrote:
> When prev CPU entered idle while a task was sleeping, we assume that the
> latter received enough running time and the lag can be cleared similarly to
> what happens for a negative lag.
> While migrating the waking task to a new cpu, Check if prev CPU entered
> idle in the meantime.
>
> Signed-off-by: Vincent Guittot <vincent.guittot@linaro.org>
> ---
> kernel/sched/fair.c | 27 +++++++++++++++++++++++++++
> 1 file changed, 27 insertions(+)
>
> diff --git a/kernel/sched/fair.c b/kernel/sched/fair.c
> index 79f53811274e..945de0215abb 100644
> --- a/kernel/sched/fair.c
> +++ b/kernel/sched/fair.c
> @@ -932,6 +932,27 @@ bool update_entity_lag(struct cfs_rq *cfs_rq, struct sched_entity *se)
> static inline unsigned long cfs_rq_load_avg(struct cfs_rq *cfs_rq);
> static unsigned long task_h_load(struct task_struct *p);
>
> +static __always_inline
> +void entity_clear_lag(struct task_struct *p)
> +{
> + struct sched_entity *se;
> + struct cfs_rq *cfs_rq;
> +
> + if (READ_ONCE(p->__state) != TASK_WAKING)
> + return;
> +
> + se = &p->se;
> + /* Negative lag implies delayed dequeue */
> + if (se->vlag <= 0)
> + return;
> +
> + cfs_rq = &task_rq(p)->cfs;
> +
> + /* CPU has been idle in between so the lag has been removed */
> + if (se->vlag_seq != READ_ONCE(cfs_rq->idle_seq))
> + se->vlag = 0;
> +}
> +
> static __always_inline
> void decay_entity_lag(struct cfs_rq *cfs_rq, struct task_struct *p, int flags)
> {
> @@ -10049,6 +10070,12 @@ static void migrate_task_rq_fair(struct task_struct *p, int new_cpu)
> struct sched_entity *se = &p->se;
>
> if (!task_on_rq_migrating(p)) {
> + /*
> + * Clear the positive lag of a waking task before migrating to
> + * the new cpu.
> + */
> + entity_clear_lag(p);
> +
> remove_entity_load_avg(se);
>
> /*
> --
> 2.53.0
>
Yeah well looks good. I need to test this on my head tho:
CPU1: CPU2:
TA TD
TB
TC
Let's say TA sleeps and has 12 lags. And while its sleeping TB and
TC leaves:
CPU1:
TA (Sleeping)
Then some new tasks come:
CPU1:
TA (Sleeping)
TW
TR
Now TA wakes up, we enter try_to_wake_up(), from there we pick CPU2 and
enter set_task_cpu(), from there migrate_task_rq_fair(), and to
entity_clear_lag().
And from there this just works correctly.
Reviewed-by: Kayra Cizmeci <kayracizmeci@gmail.com>
Cheers,
Kayra
^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [PATCH 04/20 v3] sched/eevdf: Reset lag when waking up on idle cpu
2026-11-06 15:19 ` [PATCH 04/20 v3] sched/eevdf: Reset lag when waking up on idle cpu Vincent Guittot
@ 2026-11-06 18:39 ` Kayra Cizmeci
0 siblings, 0 replies; 24+ messages in thread
From: Kayra Cizmeci @ 2026-11-06 18:39 UTC (permalink / raw)
To: Vincent Guittot
Cc: mingo, peterz, juri.lelli, dietmar.eggemann, rostedt, bsegall,
mgorman, vschneid, kprateek.nayak, linux-kernel, lukasz.luba,
rafael, linux-pm, tj, void, arighi, changwoo, sched-ext, qyousef,
christian.loehle, pierre.gondois, sshegde, tim.c.chen
On Fri, Nov 06, 2026 at 04:19:31PM +0100, Vincent Guittot wrote:
> When several tasks wake up simultaneously on an idle CPU but their prev
> CPUs were not idle, their final vlag will depend on the enqueue ordering
> as the first one will lose its lag but not the next ones.
>
> Reset the lag during the enqueue if no fair task has already been picked
> and set as the running task.
>
> As a typical example:
> - CPU0 is idle
> - TA with vlag 0ms and TB with vlag 5ms wake up on CPU0 simultaneously.
>
> Depending which task grabs the lock 1st, the behavior will be different:
> - If TA is enqueued 1st, TB will be enqueued with a positive lag and will
> be picked 1st.
> - If TB is enqueued 1st, it will lose its positive vlag and both TA and
> TB will have 0 vlag when eevdf picks next task.
>
> Signed-off-by: Vincent Guittot <vincent.guittot@linaro.org>
> ---
> kernel/sched/fair.c | 10 ++++++++--
> 1 file changed, 8 insertions(+), 2 deletions(-)
>
> diff --git a/kernel/sched/fair.c b/kernel/sched/fair.c
> index 945de0215abb..6ce4162a7703 100644
> --- a/kernel/sched/fair.c
> +++ b/kernel/sched/fair.c
> @@ -967,6 +967,14 @@ void decay_entity_lag(struct cfs_rq *cfs_rq, struct task_struct *p, int flags)
> if (vlag <= 0)
> return;
>
> + rq = rq_of(cfs_rq);
> +
> + /* You can't claim any lag when waking on idle CPU */
> + if (rq->curr == rq->idle) {
> + se->vlag = 0;
> + return;
> + }
> +
> /* No fast access to prev rq task clock */
> if (flags & ENQUEUE_MIGRATED)
> return;
> @@ -977,8 +985,6 @@ void decay_entity_lag(struct cfs_rq *cfs_rq, struct task_struct *p, int flags)
> return;
> }
>
> - rq = rq_of(cfs_rq);
> -
> /* Compute sleep time */
> delta_exec = rq_clock_task(rq) - se->exec_start;
> if (unlikely(delta_exec <= 0))
> --
> 2.53.0
>
LGTM.
Reviewed-by: Kayra Cizmeci <kayracizmeci@gmail.com>
Cheers,
Kayra
^ permalink raw reply [flat|nested] 24+ messages in thread
end of thread, other threads:[~2026-11-06 18:39 UTC | newest]
Thread overview: 24+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-11-06 15:19 [PATCH 00/18 v3] Improving latency of short slice tasks Vincent Guittot
2026-11-06 15:19 ` [PATCH 01/20 v3] sched/eevdf: Decay positive lag of sleeping entities Vincent Guittot
2026-11-06 15:19 ` [PATCH 02/20 v3] sched/eevdf: Reset lag at wakeup when prev CPU has been idle Vincent Guittot
2026-11-06 16:22 ` Kayra Cizmeci
2026-11-06 15:19 ` [PATCH 03/20 v3] sched/eevdf: Reset lag at wakeup when migrating and prev " Vincent Guittot
2026-11-06 18:05 ` Kayra Cizmeci
2026-11-06 15:19 ` [PATCH 04/20 v3] sched/eevdf: Reset lag when waking up on idle cpu Vincent Guittot
2026-11-06 18:39 ` Kayra Cizmeci
2026-11-06 15:19 ` [PATCH 05/20 v3] sched/eevdf: Add per cpu cached min_slice Vincent Guittot
2026-11-06 15:19 ` [PATCH 06/20 v3] sched/eevdf: Add min slice check when selecting CPU Vincent Guittot
2026-11-06 15:19 ` [PATCH 07/20 v3] sched/fair: Prepare select_task_rq_fair() to be called for new cases Vincent Guittot
2026-11-06 15:19 ` [PATCH 08/20 v3] sched/fair: Add push task mechanism for fair Vincent Guittot
2026-11-06 15:19 ` [PATCH 09/20 v3] sched/fair: Optimize " Vincent Guittot
2026-11-06 15:19 ` [PATCH 10/20 v3] sched/core: Add rq flag to tick parameters Vincent Guittot
2026-11-06 15:19 ` [PATCH 11/20 v3] sched/fair: Add force push task mechanism for fair Vincent Guittot
2026-11-06 15:19 ` [PATCH 12/20 v3] sched/fair: Support not wakeup case in select_idle_sibling Vincent Guittot
2026-11-06 15:19 ` [PATCH 13/20] sched/eevdf: Try to push short slice task on a better CPU Vincent Guittot
2026-11-06 15:19 ` [PATCH 14/20 v3] sched/eevdf: Push short slice task that are not picked Vincent Guittot
2026-11-06 15:19 ` [PATCH 15/20 v3] sched/fair: Enable push task for preempt short Vincent Guittot
2026-11-06 15:19 ` [PATCH 16/20 v3] energy model: Add a get previous state function Vincent Guittot
2026-11-06 15:19 ` [PATCH 17/20 v3] sched/fair: Rework feec() to use cost instead of spare capacity Vincent Guittot
2026-11-06 15:19 ` [PATCH 18/20 v3] energy model: Remove unused em_cpu_energy() Vincent Guittot
2026-11-06 15:19 ` [PATCH 19/20 v3] sched/fair: Take into account slice in EAS Vincent Guittot
2026-11-06 15:19 ` [PATCH 20/20 v3] sched/fair: Fix tasks stacking on same idle CPU Vincent Guittot
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®