* [PATCH v4 1/6] sched/fair: Do not skip CPUs of similar capacity with busy SMT siblings
2026-06-08 12:57 [PATCH v4 0/6] sched: Fix cluster scheduling in the presence of asymmetric capacity Ricardo Neri
@ 2026-06-08 12:57 ` Ricardo Neri
2026-06-08 13:50 ` Christian Loehle
2026-06-09 4:26 ` K Prateek Nayak
2026-06-08 12:57 ` [PATCH v4 2/6] sched/fair: Also gate overloaded status update for SD_ASYM_CPUCAPACITY Ricardo Neri
` (5 subsequent siblings)
6 siblings, 2 replies; 17+ messages in thread
From: Ricardo Neri @ 2026-06-08 12:57 UTC (permalink / raw)
To: Ingo Molnar, Peter Zijlstra, Juri Lelli, Vincent Guittot,
Dietmar Eggemann, Steven Rostedt, Ben Segall, Mel Gorman,
Valentin Schneider, Tim C Chen, Chen Yu, Christian Loehle,
Barry Song
Cc: Rafael J. Wysocki, Andrea Righi, K Prateek Nayak, Len Brown,
ricardo.neri, linux-kernel, Ricardo Neri
When picking a busiest CPU with only one running task, the function
sched_balance_find_src_rq() skips candidate CPUs if the destination CPU has
less than ~5% extra capacity. This condition only holds if all the SMT
siblings of a CPU are idle.
SMT siblings share the computing resources of a physical core and this
results in reduced capacity if more than one sibling is busy.
Skipping a CPU as described would prevent the load balancer from pulling
tasks from a scheduling group previously and correctly identified as
group_smt_balance (i.e., one with more than one task running).
Do not skip a candidate CPU of similar capacity if it has busy SMT
siblings.
Signed-off-by: Ricardo Neri <ricardo.neri-calderon@linux.intel.com>
---
Changes in v4:
* Introduced this patch.
Changes in v3:
* N/A
Changes in v2:
* N/A
---
kernel/sched/fair.c | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/kernel/sched/fair.c b/kernel/sched/fair.c
index f4ed841f766f..229f32cebf1f 100644
--- a/kernel/sched/fair.c
+++ b/kernel/sched/fair.c
@@ -12924,6 +12924,7 @@ static struct rq *sched_balance_find_src_rq(struct lb_env *env,
int i;
for_each_cpu_and(i, sched_group_span(group), env->cpus) {
+ bool smt_degraded_cap = sched_smt_active() && !is_core_idle(i);
unsigned long capacity, load, util;
unsigned int nr_running;
enum fbq_type rt;
@@ -12964,8 +12965,12 @@ static struct rq *sched_balance_find_src_rq(struct lb_env *env,
* eventually lead to active_balancing high->low capacity.
* Higher per-CPU capacity is considered better than balancing
* average load.
+ *
+ * Busy SMT siblings reduce the capacity of CPU i. Do not skip
+ * it in this case.
*/
if (env->sd->flags & SD_ASYM_CPUCAPACITY &&
+ !smt_degraded_cap &&
!capacity_greater(capacity_of(env->dst_cpu), capacity) &&
nr_running == 1)
continue;
--
2.43.0
^ permalink raw reply [flat|nested] 17+ messages in thread* Re: [PATCH v4 1/6] sched/fair: Do not skip CPUs of similar capacity with busy SMT siblings
2026-06-08 12:57 ` [PATCH v4 1/6] sched/fair: Do not skip CPUs of similar capacity with busy SMT siblings Ricardo Neri
@ 2026-06-08 13:50 ` Christian Loehle
2026-06-10 3:32 ` Ricardo Neri
2026-06-09 4:26 ` K Prateek Nayak
1 sibling, 1 reply; 17+ messages in thread
From: Christian Loehle @ 2026-06-08 13:50 UTC (permalink / raw)
To: Ricardo Neri, Ingo Molnar, Peter Zijlstra, Juri Lelli,
Vincent Guittot, Dietmar Eggemann, Steven Rostedt, Ben Segall,
Mel Gorman, Valentin Schneider, Tim C Chen, Chen Yu, Barry Song
Cc: Rafael J. Wysocki, Andrea Righi, K Prateek Nayak, Len Brown,
ricardo.neri, linux-kernel
On 6/8/26 13:57, Ricardo Neri wrote:
> When picking a busiest CPU with only one running task, the function
> sched_balance_find_src_rq() skips candidate CPUs if the destination CPU has
> less than ~5% extra capacity. This condition only holds if all the SMT
> siblings of a CPU are idle.
>
> SMT siblings share the computing resources of a physical core and this
> results in reduced capacity if more than one sibling is busy.
>
> Skipping a CPU as described would prevent the load balancer from pulling
> tasks from a scheduling group previously and correctly identified as
> group_smt_balance (i.e., one with more than one task running).
>
> Do not skip a candidate CPU of similar capacity if it has busy SMT
> siblings.
>
> Signed-off-by: Ricardo Neri <ricardo.neri-calderon@linux.intel.com>
Nice find!
Reviewed-by: Christian Loehle <christian.loehle@arm.com>
> [snip]
^ permalink raw reply [flat|nested] 17+ messages in thread
* Re: [PATCH v4 1/6] sched/fair: Do not skip CPUs of similar capacity with busy SMT siblings
2026-06-08 13:50 ` Christian Loehle
@ 2026-06-10 3:32 ` Ricardo Neri
0 siblings, 0 replies; 17+ messages in thread
From: Ricardo Neri @ 2026-06-10 3:32 UTC (permalink / raw)
To: Christian Loehle
Cc: Ingo Molnar, Peter Zijlstra, Juri Lelli, Vincent Guittot,
Dietmar Eggemann, Steven Rostedt, Ben Segall, Mel Gorman,
Valentin Schneider, Tim C Chen, Chen Yu, Barry Song,
Rafael J. Wysocki, Andrea Righi, K Prateek Nayak, Len Brown,
ricardo.neri, linux-kernel
On Mon, Jun 08, 2026 at 02:50:38PM +0100, Christian Loehle wrote:
> On 6/8/26 13:57, Ricardo Neri wrote:
> > When picking a busiest CPU with only one running task, the function
> > sched_balance_find_src_rq() skips candidate CPUs if the destination CPU has
> > less than ~5% extra capacity. This condition only holds if all the SMT
> > siblings of a CPU are idle.
> >
> > SMT siblings share the computing resources of a physical core and this
> > results in reduced capacity if more than one sibling is busy.
> >
> > Skipping a CPU as described would prevent the load balancer from pulling
> > tasks from a scheduling group previously and correctly identified as
> > group_smt_balance (i.e., one with more than one task running).
> >
> > Do not skip a candidate CPU of similar capacity if it has busy SMT
> > siblings.
> >
> > Signed-off-by: Ricardo Neri <ricardo.neri-calderon@linux.intel.com>
>
> Nice find!
> Reviewed-by: Christian Loehle <christian.loehle@arm.com>
Thanks! I will make more changes based on feedback from Chen Yu and
Prateek. You may want to reissue your tag after that :)
^ permalink raw reply [flat|nested] 17+ messages in thread
* Re: [PATCH v4 1/6] sched/fair: Do not skip CPUs of similar capacity with busy SMT siblings
2026-06-08 12:57 ` [PATCH v4 1/6] sched/fair: Do not skip CPUs of similar capacity with busy SMT siblings Ricardo Neri
2026-06-08 13:50 ` Christian Loehle
@ 2026-06-09 4:26 ` K Prateek Nayak
2026-06-09 6:24 ` Chen, Yu C
1 sibling, 1 reply; 17+ messages in thread
From: K Prateek Nayak @ 2026-06-09 4:26 UTC (permalink / raw)
To: Ricardo Neri, Ingo Molnar, Peter Zijlstra, Juri Lelli,
Vincent Guittot, Dietmar Eggemann, Steven Rostedt, Ben Segall,
Mel Gorman, Valentin Schneider, Tim C Chen, Chen Yu,
Christian Loehle, Barry Song
Cc: Rafael J. Wysocki, Andrea Righi, Len Brown, ricardo.neri, linux-kernel
Hello Ricardo,
On 6/8/2026 6:27 PM, Ricardo Neri wrote:
> diff --git a/kernel/sched/fair.c b/kernel/sched/fair.c
> index f4ed841f766f..229f32cebf1f 100644
> --- a/kernel/sched/fair.c
> +++ b/kernel/sched/fair.c
> @@ -12924,6 +12924,7 @@ static struct rq *sched_balance_find_src_rq(struct lb_env *env,
> int i;
>
> for_each_cpu_and(i, sched_group_span(group), env->cpus) {
> + bool smt_degraded_cap = sched_smt_active() && !is_core_idle(i);
Based on a quick look at the generated asm, we seem to be calculating
"smt_degraded_cap" regardless of whether we are on a
"SD_ASYM_CPUCAPACITY" domain or not which is an unnecessary overhead of
doing a is_core_idle() on symmetric domains.
We can have an indicator up top like:
bool asym_balance = !!(env->sd->flags & SD_ASYM_CPUCAPACITY);
and later use that when computing smt_degraded_cap:
bool smt_degraded_cap = asym_balance && (sched_smt_active() && !is_core_idle(i));
I would even suggest pushing this until later and do ...
> unsigned long capacity, load, util;
> unsigned int nr_running;
> enum fbq_type rt;
> @@ -12964,8 +12965,12 @@ static struct rq *sched_balance_find_src_rq(struct lb_env *env,
> * eventually lead to active_balancing high->low capacity.
> * Higher per-CPU capacity is considered better than balancing
> * average load.
> + *
> + * Busy SMT siblings reduce the capacity of CPU i. Do not skip
> + * it in this case.
> */
> if (env->sd->flags & SD_ASYM_CPUCAPACITY &&
...
if (asym_balance) {
/* Busy SMT reduces capacity */
if (sched_smt_active() && !is_core_idle(i))
continue;
/* Other bits */
if (...)
continue;
}
Thoughts?
Aside from that, the changes makes sense to me. Feel free to include:
Reviewed-by: K Prateek Nayak <kprateek.nayak@amd.com>
> + !smt_degraded_cap &&
> !capacity_greater(capacity_of(env->dst_cpu), capacity) &&
> nr_running == 1)
> continue;
>
> --
> 2.43.0
>
--
Thanks and Regards,
Prateek
^ permalink raw reply [flat|nested] 17+ messages in thread* Re: [PATCH v4 1/6] sched/fair: Do not skip CPUs of similar capacity with busy SMT siblings
2026-06-09 4:26 ` K Prateek Nayak
@ 2026-06-09 6:24 ` Chen, Yu C
2026-06-09 6:29 ` K Prateek Nayak
0 siblings, 1 reply; 17+ messages in thread
From: Chen, Yu C @ 2026-06-09 6:24 UTC (permalink / raw)
To: K Prateek Nayak, Ricardo Neri
Cc: Rafael J. Wysocki, Ingo Molnar, Peter Zijlstra, Andrea Righi,
Len Brown, ricardo.neri, linux-kernel, Juri Lelli,
Steven Rostedt, Vincent Guittot, Dietmar Eggemann, Ben Segall,
Mel Gorman, Tim C Chen, Valentin Schneider, Christian Loehle,
Barry Song
On 6/9/2026 12:26 PM, K Prateek Nayak wrote:
> Hello Ricardo,
>
> On 6/8/2026 6:27 PM, Ricardo Neri wrote:
>> diff --git a/kernel/sched/fair.c b/kernel/sched/fair.c
>> index f4ed841f766f..229f32cebf1f 100644
>> --- a/kernel/sched/fair.c
>> +++ b/kernel/sched/fair.c
>> @@ -12924,6 +12924,7 @@ static struct rq *sched_balance_find_src_rq(struct lb_env *env,
>> int i;
>>
>> for_each_cpu_and(i, sched_group_span(group), env->cpus) {
>> + bool smt_degraded_cap = sched_smt_active() && !is_core_idle(i);
>
> Based on a quick look at the generated asm, we seem to be calculating
> "smt_degraded_cap" regardless of whether we are on a
> "SD_ASYM_CPUCAPACITY" domain or not which is an unnecessary overhead of
> doing a is_core_idle() on symmetric domains.
>
> We can have an indicator up top like:
>
> bool asym_balance = !!(env->sd->flags & SD_ASYM_CPUCAPACITY);
>
> and later use that when computing smt_degraded_cap:
>
> bool smt_degraded_cap = asym_balance && (sched_smt_active() && !is_core_idle(i));
>
> I would even suggest pushing this until later and do ...
>
Right, this can leverage if (!nr_running) to skip the idle i, thus
avoid doing is_core_idle().
>> unsigned long capacity, load, util;
>> unsigned int nr_running;
>> enum fbq_type rt;
>> @@ -12964,8 +12965,12 @@ static struct rq *sched_balance_find_src_rq(struct lb_env *env,
>> * eventually lead to active_balancing high->low capacity.
>> * Higher per-CPU capacity is considered better than balancing
>> * average load.
>> + *
>> + * Busy SMT siblings reduce the capacity of CPU i. Do not skip
>> + * it in this case.
>> */
>> if (env->sd->flags & SD_ASYM_CPUCAPACITY &&
>
> ...
>
> if (asym_balance) {
> /* Busy SMT reduces capacity */
> if (sched_smt_active() && !is_core_idle(i))
> continue;
We might not want to skip i if it has SMT busy siblings?
if ((!sched_smt_active() || is_core_idle(i)) && ...
thanks,
Chenyu
^ permalink raw reply [flat|nested] 17+ messages in thread* Re: [PATCH v4 1/6] sched/fair: Do not skip CPUs of similar capacity with busy SMT siblings
2026-06-09 6:24 ` Chen, Yu C
@ 2026-06-09 6:29 ` K Prateek Nayak
2026-06-10 3:29 ` Ricardo Neri
0 siblings, 1 reply; 17+ messages in thread
From: K Prateek Nayak @ 2026-06-09 6:29 UTC (permalink / raw)
To: Chen, Yu C, Ricardo Neri
Cc: Rafael J. Wysocki, Ingo Molnar, Peter Zijlstra, Andrea Righi,
Len Brown, ricardo.neri, linux-kernel, Juri Lelli,
Steven Rostedt, Vincent Guittot, Dietmar Eggemann, Ben Segall,
Mel Gorman, Tim C Chen, Valentin Schneider, Christian Loehle,
Barry Song
Hello Chenyu,
On 6/9/2026 11:54 AM, Chen, Yu C wrote:
>>> @@ -12964,8 +12965,12 @@ static struct rq *sched_balance_find_src_rq(struct lb_env *env,
>>> * eventually lead to active_balancing high->low capacity.
>>> * Higher per-CPU capacity is considered better than balancing
>>> * average load.
>>> + *
>>> + * Busy SMT siblings reduce the capacity of CPU i. Do not skip
>>> + * it in this case.
>>> */
>>> if (env->sd->flags & SD_ASYM_CPUCAPACITY &&
>>
>> ...
>>
>> if (asym_balance) {
>> /* Busy SMT reduces capacity */
>> if (sched_smt_active() && !is_core_idle(i))
>> continue;
>
> We might not want to skip i if it has SMT busy siblings?
> if ((!sched_smt_active() || is_core_idle(i)) && ...
Ah yes! I forgot to do the ! of the condition :-)
Thank you for the correction.
--
Thanks and Regards,
Prateek
^ permalink raw reply [flat|nested] 17+ messages in thread* Re: [PATCH v4 1/6] sched/fair: Do not skip CPUs of similar capacity with busy SMT siblings
2026-06-09 6:29 ` K Prateek Nayak
@ 2026-06-10 3:29 ` Ricardo Neri
0 siblings, 0 replies; 17+ messages in thread
From: Ricardo Neri @ 2026-06-10 3:29 UTC (permalink / raw)
To: K Prateek Nayak
Cc: Chen, Yu C, Rafael J. Wysocki, Ingo Molnar, Peter Zijlstra,
Andrea Righi, Len Brown, ricardo.neri, linux-kernel, Juri Lelli,
Steven Rostedt, Vincent Guittot, Dietmar Eggemann, Ben Segall,
Mel Gorman, Tim C Chen, Valentin Schneider, Christian Loehle,
Barry Song
On Tue, Jun 09, 2026 at 11:59:00AM +0530, K Prateek Nayak wrote:
> Hello Chenyu,
>
> On 6/9/2026 11:54 AM, Chen, Yu C wrote:
> >>> @@ -12964,8 +12965,12 @@ static struct rq *sched_balance_find_src_rq(struct lb_env *env,
> >>> * eventually lead to active_balancing high->low capacity.
> >>> * Higher per-CPU capacity is considered better than balancing
> >>> * average load.
> >>> + *
> >>> + * Busy SMT siblings reduce the capacity of CPU i. Do not skip
> >>> + * it in this case.
> >>> */
> >>> if (env->sd->flags & SD_ASYM_CPUCAPACITY &&
> >>
> >> ...
> >>
> >> if (asym_balance) {
> >> /* Busy SMT reduces capacity */
> >> if (sched_smt_active() && !is_core_idle(i))
> >> continue;
> >
> > We might not want to skip i if it has SMT busy siblings?
> > if ((!sched_smt_active() || is_core_idle(i)) && ...
>
> Ah yes! I forgot to do the ! of the condition :-)
> Thank you for the correction.
Thanks for these suggestions. It makes sense to me to only evaluate the
state of SMT siblings when actually needed. I will make the change.
^ permalink raw reply [flat|nested] 17+ messages in thread
* [PATCH v4 2/6] sched/fair: Also gate overloaded status update for SD_ASYM_CPUCAPACITY
2026-06-08 12:57 [PATCH v4 0/6] sched: Fix cluster scheduling in the presence of asymmetric capacity Ricardo Neri
2026-06-08 12:57 ` [PATCH v4 1/6] sched/fair: Do not skip CPUs of similar capacity with busy SMT siblings Ricardo Neri
@ 2026-06-08 12:57 ` Ricardo Neri
2026-06-08 12:57 ` [PATCH v4 3/6] sched/fair: Check CPU capacity before comparing group types during load balance Ricardo Neri
` (4 subsequent siblings)
6 siblings, 0 replies; 17+ messages in thread
From: Ricardo Neri @ 2026-06-08 12:57 UTC (permalink / raw)
To: Ingo Molnar, Peter Zijlstra, Juri Lelli, Vincent Guittot,
Dietmar Eggemann, Steven Rostedt, Ben Segall, Mel Gorman,
Valentin Schneider, Tim C Chen, Chen Yu, Christian Loehle,
Barry Song
Cc: Rafael J. Wysocki, Andrea Righi, K Prateek Nayak, Len Brown,
ricardo.neri, linux-kernel, Ricardo Neri
The argument sg_overloaded of update_sg_lb_stats() is only consumed when
balancing at the root domain. It only makes sense to update it in such a
case. Commit 3229adbe7875 ("sched/fair: Do not compute overloaded status
unnecessarily during lb") updated the logic accordingly but missed the case
in which the root domain has the SD_ASYM_CPUCAPACITY flag. Fix this.
Fixes: 3229adbe7875 ("sched/fair: Do not compute overloaded status unnecessarily during lb")
Reported-by: Chen Yu <yu.c.chen@intel.com>
Signed-off-by: Ricardo Neri <ricardo.neri-calderon@linux.intel.com>
---
Changes in v4:
* Introduced this patch.
Changes in v3:
* N/A
Changes in v2:
* N/A
---
kernel/sched/fair.c | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/kernel/sched/fair.c b/kernel/sched/fair.c
index 229f32cebf1f..86987c69bddd 100644
--- a/kernel/sched/fair.c
+++ b/kernel/sched/fair.c
@@ -11853,7 +11853,9 @@ static inline void update_sg_lb_stats(struct lb_env *env,
/* Check for a misfit task on the cpu */
if (sgs->group_misfit_task_load < rq->misfit_task_load) {
sgs->group_misfit_task_load = rq->misfit_task_load;
- *sg_overloaded = 1;
+
+ if (balancing_at_rd)
+ *sg_overloaded = 1;
}
} else if (env->idle && sched_reduced_capacity(rq, env->sd)) {
/* Check for a task running on a CPU with reduced capacity */
--
2.43.0
^ permalink raw reply [flat|nested] 17+ messages in thread* [PATCH v4 3/6] sched/fair: Check CPU capacity before comparing group types during load balance
2026-06-08 12:57 [PATCH v4 0/6] sched: Fix cluster scheduling in the presence of asymmetric capacity Ricardo Neri
2026-06-08 12:57 ` [PATCH v4 1/6] sched/fair: Do not skip CPUs of similar capacity with busy SMT siblings Ricardo Neri
2026-06-08 12:57 ` [PATCH v4 2/6] sched/fair: Also gate overloaded status update for SD_ASYM_CPUCAPACITY Ricardo Neri
@ 2026-06-08 12:57 ` Ricardo Neri
2026-06-08 12:57 ` [PATCH v4 4/6] sched/fair: Skip misfit load accounting when the destination CPU cannot help Ricardo Neri
` (3 subsequent siblings)
6 siblings, 0 replies; 17+ messages in thread
From: Ricardo Neri @ 2026-06-08 12:57 UTC (permalink / raw)
To: Ingo Molnar, Peter Zijlstra, Juri Lelli, Vincent Guittot,
Dietmar Eggemann, Steven Rostedt, Ben Segall, Mel Gorman,
Valentin Schneider, Tim C Chen, Chen Yu, Christian Loehle,
Barry Song
Cc: Rafael J. Wysocki, Andrea Righi, K Prateek Nayak, Len Brown,
ricardo.neri, linux-kernel, Vincent Guittot, Ricardo Neri
update_sd_pick_busiest() may incorrectly select a fully_busy group as the
busiest group when its per-CPU capacity exceeds that of the destination
CPU. This happens because the type of busiest group is initialized to
group_has_spare and allows the fully_busy group to win the type comparison.
update_sd_pick_busiest() should not choose a candidate scheduling group
with at most one runnable task if its per-CPU capacity is greater than that
of the destination CPU. Such a check already exists, but it is done too
late: after the type comparison, preventing a subsequent fully_busy group
of equal per-CPU capacity from being correctly selected.
Move this check to occur before comparing group types.
Fixes: 0b0695f2b34a ("sched/fair: Rework load_balance()")
Reviewed-by: Christian Loehle <christian.loehle@arm.com>
Reviewed-by: Chen Yu <yu.c.chen@intel.com>
Reviewed-by: Tim Chen <tim.c.chen@linux.intel.com>
Reviewed-by: Vincent Guittot <vincent.guitto@linaro.org>
Signed-off-by: Ricardo Neri <ricardo.neri-calderon@linux.intel.com>
---
Changes in v4:
* Dropped note on SMT not being affected since SMT + asym capacity is
now supported.
* Added Reviewed-by tags from Vincent, Tim, and Chen Yu. Thanks!
Changes in v3:
* Added a Fixes tag. (Christian)
* Added Reviewed-by tag from Christian. Thanks!
Changes in v2:
* Added a note clarifying that SMT and SD_ASYM_CPUCAPACITY are mutually
exclusive. (Tim)
* Kept parentheses around bitwise operators for clarity.
* Rewrote patch description for clarity.
---
kernel/sched/fair.c | 22 +++++++++++-----------
1 file changed, 11 insertions(+), 11 deletions(-)
diff --git a/kernel/sched/fair.c b/kernel/sched/fair.c
index 86987c69bddd..a30ba02df688 100644
--- a/kernel/sched/fair.c
+++ b/kernel/sched/fair.c
@@ -11934,6 +11934,17 @@ static bool update_sd_pick_busiest(struct lb_env *env,
sds->local_stat.group_type != group_has_spare))
return false;
+ /*
+ * Candidate sg has no more than one task per CPU and has higher
+ * per-CPU capacity. Migrating tasks to less capable CPUs may harm
+ * throughput. Maximize throughput, power/energy consequences are not
+ * considered.
+ */
+ if ((env->sd->flags & SD_ASYM_CPUCAPACITY) &&
+ (sgs->group_type <= group_fully_busy) &&
+ (capacity_greater(sg->sgc->min_capacity, capacity_of(env->dst_cpu))))
+ return false;
+
if (sgs->group_type > busiest->group_type)
return true;
@@ -12040,17 +12051,6 @@ static bool update_sd_pick_busiest(struct lb_env *env,
break;
}
- /*
- * Candidate sg has no more than one task per CPU and has higher
- * per-CPU capacity. Migrating tasks to less capable CPUs may harm
- * throughput. Maximize throughput, power/energy consequences are not
- * considered.
- */
- if ((env->sd->flags & SD_ASYM_CPUCAPACITY) &&
- (sgs->group_type <= group_fully_busy) &&
- (capacity_greater(sg->sgc->min_capacity, capacity_of(env->dst_cpu))))
- return false;
-
return true;
}
--
2.43.0
^ permalink raw reply [flat|nested] 17+ messages in thread* [PATCH v4 4/6] sched/fair: Skip misfit load accounting when the destination CPU cannot help
2026-06-08 12:57 [PATCH v4 0/6] sched: Fix cluster scheduling in the presence of asymmetric capacity Ricardo Neri
` (2 preceding siblings ...)
2026-06-08 12:57 ` [PATCH v4 3/6] sched/fair: Check CPU capacity before comparing group types during load balance Ricardo Neri
@ 2026-06-08 12:57 ` Ricardo Neri
2026-06-08 12:57 ` [PATCH v4 5/6] sched/fair: Allow load balancing between CPUs of identical capacity Ricardo Neri
` (2 subsequent siblings)
6 siblings, 0 replies; 17+ messages in thread
From: Ricardo Neri @ 2026-06-08 12:57 UTC (permalink / raw)
To: Ingo Molnar, Peter Zijlstra, Juri Lelli, Vincent Guittot,
Dietmar Eggemann, Steven Rostedt, Ben Segall, Mel Gorman,
Valentin Schneider, Tim C Chen, Chen Yu, Christian Loehle,
Barry Song
Cc: Rafael J. Wysocki, Andrea Righi, K Prateek Nayak, Len Brown,
ricardo.neri, linux-kernel, Ricardo Neri
In domains with asymmetric capacity, identifying misfit load in a
scheduling group is not useful when the destination CPU cannot help (i.e.,
its capacity exceeds the group's maximum CPU capacity by less than ~5%). In
such cases, it also prevents load balance among clusters of equal capacity
when CONFIG_SCHED_CLUSTER is enabled. This happens because
update_sd_pick_busiest() skips candidate groups of type misfit_task if the
destination CPU has similar capacity.
Skipping misfit load accounting in this situation allows the group to be
classified as has_spare or fully_busy and lets load balancing proceed. Keep
marking scheduling groups as overloaded when misfit tasks are present. The
sg_overloaded flag propagates to the root domain and allows bigger CPUs in
it to help via newly idle balance.
Reviewed-by: Christian Loehle <christian.loehle@arm.com>
Reviewed-by: Chen Yu <yu.c.chen@intel.com>
Reviewed-by: Vincent Guittot <vincent.guittot@linaro.org>
Signed-off-by: Ricardo Neri <ricardo.neri-calderon@linux.intel.com>
---
Changes in v4:
* Added Reviewed-by tags from Vincent and Chen Yu. Thanks!
Changes in v3:
* Added Reviewed-by tag from Christian. Thanks!
Changes in v2:
* Moved the check of the destination CPU capacity inside the code block
used for SD_ASYM_CPUCAPACITY. v1 inadvertently broke the mutual
exclusion of the sched_reduced_capacity() path.
* Keep marking the root domain as overloaded to allow bigger CPUs to
help. (sashiko)
* Fixed patch description to clarify that the capacity_greater() looks
for differences of 5% or more. (Christian)
* Reworded the patch description for clarity.
* I did not include the Reviewed-by tag from Christian since the patch
changed functionally.
---
kernel/sched/fair.c | 21 +++++++++++++++++----
1 file changed, 17 insertions(+), 4 deletions(-)
diff --git a/kernel/sched/fair.c b/kernel/sched/fair.c
index a30ba02df688..77554d7410ff 100644
--- a/kernel/sched/fair.c
+++ b/kernel/sched/fair.c
@@ -11850,12 +11850,25 @@ static inline void update_sg_lb_stats(struct lb_env *env,
continue;
if (sd_flags & SD_ASYM_CPUCAPACITY) {
- /* Check for a misfit task on the cpu */
- if (sgs->group_misfit_task_load < rq->misfit_task_load) {
- sgs->group_misfit_task_load = rq->misfit_task_load;
-
+ if (rq->misfit_task_load) {
+ /*
+ * Always mark the root domain overloaded so big
+ * CPUs can pick up misfit tasks via newly idle
+ * balance.
+ */
if (balancing_at_rd)
*sg_overloaded = 1;
+
+ /*
+ * Only account misfit load if @dst_cpu can
+ * help; otherwise, the group may be classified
+ * as misfit_task and update_sd_pick_busiest()
+ * will skip it.
+ */
+ if (capacity_greater(capacity_of(env->dst_cpu),
+ group->sgc->max_capacity) &&
+ (sgs->group_misfit_task_load < rq->misfit_task_load))
+ sgs->group_misfit_task_load = rq->misfit_task_load;
}
} else if (env->idle && sched_reduced_capacity(rq, env->sd)) {
/* Check for a task running on a CPU with reduced capacity */
--
2.43.0
^ permalink raw reply [flat|nested] 17+ messages in thread* [PATCH v4 5/6] sched/fair: Allow load balancing between CPUs of identical capacity
2026-06-08 12:57 [PATCH v4 0/6] sched: Fix cluster scheduling in the presence of asymmetric capacity Ricardo Neri
` (3 preceding siblings ...)
2026-06-08 12:57 ` [PATCH v4 4/6] sched/fair: Skip misfit load accounting when the destination CPU cannot help Ricardo Neri
@ 2026-06-08 12:57 ` Ricardo Neri
2026-06-08 12:57 ` [PATCH v4 6/6] sched/topology: Do not clear SD_PREFER_SIBLING in domains with clusters Ricardo Neri
2026-06-08 17:37 ` [PATCH v4 0/6] sched: Fix cluster scheduling in the presence of asymmetric capacity Christian Loehle
6 siblings, 0 replies; 17+ messages in thread
From: Ricardo Neri @ 2026-06-08 12:57 UTC (permalink / raw)
To: Ingo Molnar, Peter Zijlstra, Juri Lelli, Vincent Guittot,
Dietmar Eggemann, Steven Rostedt, Ben Segall, Mel Gorman,
Valentin Schneider, Tim C Chen, Chen Yu, Christian Loehle,
Barry Song
Cc: Rafael J. Wysocki, Andrea Righi, K Prateek Nayak, Len Brown,
ricardo.neri, linux-kernel, Ricardo Neri
sched_balance_find_src_rq() avoids selecting a runqueue with a single
running task as busiest if doing so results in migrating the task to a
CPU with less than ~5% of extra capacity. It also unintentionally
prevents migrations between CPUs of identical capacity.
When CONFIG_SCHED_CLUSTER is enabled, load should be balanced across
clusters of CPUs with the same capacity. Allowing migration between CPUs
of identical capacity is necessary to meet this goal.
Use arch_scale_cpu_capacity() to reflect architectural capacity, excluding
runtime reductions due to side activity or thermal pressure. Guard this
check with the sched_cluster_active static key so that systems without
cluster topology are unaffected.
Signed-off-by: Ricardo Neri <ricardo.neri-calderon@linux.intel.com>
---
Changes in v4:
* Implemented the check for cluster with a local variable for improved
readability.
Changes in v3:
* Reverted the inverted capacity check; the inverted form incorrectly
allows migrations to CPUs of slightly less capacity.
* Guarded the check for architectural capacity with the
sched_cluster_active static key.
Changes in v2:
* Used arch_scale_cpu_capacity() instead of capacity_of() to ignore
runtime variability.
* Inverted the check for runtime capacity. (Christian)
* Reworded patch description for clarity.
---
kernel/sched/fair.c | 9 ++++++++-
1 file changed, 8 insertions(+), 1 deletion(-)
diff --git a/kernel/sched/fair.c b/kernel/sched/fair.c
index 77554d7410ff..74b9669d149b 100644
--- a/kernel/sched/fair.c
+++ b/kernel/sched/fair.c
@@ -12939,6 +12939,9 @@ static struct rq *sched_balance_find_src_rq(struct lb_env *env,
int i;
for_each_cpu_and(i, sched_group_span(group), env->cpus) {
+ bool same_arch_cluster = static_branch_unlikely(&sched_cluster_active) &&
+ (arch_scale_cpu_capacity(env->dst_cpu) ==
+ arch_scale_cpu_capacity(i));
bool smt_degraded_cap = sched_smt_active() && !is_core_idle(i);
unsigned long capacity, load, util;
unsigned int nr_running;
@@ -12983,9 +12986,13 @@ static struct rq *sched_balance_find_src_rq(struct lb_env *env,
*
* Busy SMT siblings reduce the capacity of CPU i. Do not skip
* it in this case.
+ *
+ * CONFIG_SCHED_CLUSTER requires balancing load across clusters
+ * of identical capacity. Use architectural capacity to ignore
+ * runtime variability.
*/
if (env->sd->flags & SD_ASYM_CPUCAPACITY &&
- !smt_degraded_cap &&
+ !smt_degraded_cap && !same_arch_cluster &&
!capacity_greater(capacity_of(env->dst_cpu), capacity) &&
nr_running == 1)
continue;
--
2.43.0
^ permalink raw reply [flat|nested] 17+ messages in thread* [PATCH v4 6/6] sched/topology: Do not clear SD_PREFER_SIBLING in domains with clusters
2026-06-08 12:57 [PATCH v4 0/6] sched: Fix cluster scheduling in the presence of asymmetric capacity Ricardo Neri
` (4 preceding siblings ...)
2026-06-08 12:57 ` [PATCH v4 5/6] sched/fair: Allow load balancing between CPUs of identical capacity Ricardo Neri
@ 2026-06-08 12:57 ` Ricardo Neri
2026-06-08 17:37 ` [PATCH v4 0/6] sched: Fix cluster scheduling in the presence of asymmetric capacity Christian Loehle
6 siblings, 0 replies; 17+ messages in thread
From: Ricardo Neri @ 2026-06-08 12:57 UTC (permalink / raw)
To: Ingo Molnar, Peter Zijlstra, Juri Lelli, Vincent Guittot,
Dietmar Eggemann, Steven Rostedt, Ben Segall, Mel Gorman,
Valentin Schneider, Tim C Chen, Chen Yu, Christian Loehle,
Barry Song
Cc: Rafael J. Wysocki, Andrea Righi, K Prateek Nayak, Len Brown,
ricardo.neri, linux-kernel, Ricardo Neri
Some topologies have scheduling domains that contain CPUs of asymmetric
capacity, grouped into two or more clusters of equal-capacity CPUs
sharing an L2 cache. When CONFIG_SCHED_CLUSTER is enabled, load must be
balanced across these resource-sharing clusters.
Do not clear SD_PREFER_SIBLING in the child domains to indicate to the
load balancer that it should spread load among cluster siblings.
Checks for capacity in update_sd_pick_busiest() prevent migrations from
high- to low-capacity CPUs if a candidate group is not overloaded.
An effect of keeping the SD_PREFER_SIBLING in domains with asymmetric
capacity is that low-capacity clusters with spare capacity can now help
overloaded higher-capacity groups. This was already the case for single-CPU
groups (see calculate_imbalance() for domains with SD_SHARE_LLC).
Once the overloading condition disappears, misfit load will still be used
to move high-utilization tasks to bigger CPUs if they have spare capacity.
Reviewed-by: Tim Chen <tim.c.chen@linux.intel.com>
Signed-off-by: Ricardo Neri <ricardo.neri-calderon@linux.intel.com>
---
Changes in v4:
* Added Reviewed-by tag from Tim. Thanks!
Changes in v3:
* Updated documentation of SD_PREFER_SIBLING.
* Expanded the patch description to explain the behavior when overloaded
groups are involved.
Changes in v2:
* Reworded the patch description for clarity.
* Kept parentheses around bitwise operators for clarity.
---
include/linux/sched/sd_flags.h | 3 ++-
kernel/sched/topology.c | 14 ++++++++++++--
2 files changed, 14 insertions(+), 3 deletions(-)
diff --git a/include/linux/sched/sd_flags.h b/include/linux/sched/sd_flags.h
index 42839cfa2778..42f74af83b8c 100644
--- a/include/linux/sched/sd_flags.h
+++ b/include/linux/sched/sd_flags.h
@@ -147,7 +147,8 @@ SD_FLAG(SD_ASYM_PACKING, SDF_NEEDS_GROUPS)
* Prefer to place tasks in a sibling domain
*
* Set up until domains start spanning NUMA nodes. Close to being a SHARED_CHILD
- * flag, but cleared below domains with SD_ASYM_CPUCAPACITY.
+ * flag, but cleared below domains with SD_ASYM_CPUCAPACITY if the domain does
+ * not have clusters of CPUs sharing cache.
*
* NEEDS_GROUPS: Load balancing flag.
*/
diff --git a/kernel/sched/topology.c b/kernel/sched/topology.c
index 622e2e01974c..f35203ed52c0 100644
--- a/kernel/sched/topology.c
+++ b/kernel/sched/topology.c
@@ -1995,8 +1995,18 @@ sd_init(struct sched_domain_topology_level *tl,
/*
* Convert topological properties into behaviour.
*/
- /* Don't attempt to spread across CPUs of different capacities. */
- if ((sd->flags & SD_ASYM_CPUCAPACITY) && sd->child)
+ /*
+ * Don't attempt to spread across CPUs of different capacities.
+ *
+ * If the domain has clusters of CPUs sharing L2 cache, keep the flag to
+ * spread tasks across clusters of identical capacity. Checks in
+ * update_sd_pick_busiest() prevent task migrations from high- to low-
+ * capacity CPUs for non-overloaded groups. Migrations to a lower-
+ * capacity CPU can happen if a higher-capacity group is overloaded and
+ * a low-capacity cluster has spare capacity.
+ */
+ if ((sd->flags & SD_ASYM_CPUCAPACITY) && sd->child &&
+ !(sd->child->flags & SD_CLUSTER))
sd->child->flags &= ~SD_PREFER_SIBLING;
if (sd->flags & SD_SHARE_CPUCAPACITY) {
--
2.43.0
^ permalink raw reply [flat|nested] 17+ messages in thread* Re: [PATCH v4 0/6] sched: Fix cluster scheduling in the presence of asymmetric capacity
2026-06-08 12:57 [PATCH v4 0/6] sched: Fix cluster scheduling in the presence of asymmetric capacity Ricardo Neri
` (5 preceding siblings ...)
2026-06-08 12:57 ` [PATCH v4 6/6] sched/topology: Do not clear SD_PREFER_SIBLING in domains with clusters Ricardo Neri
@ 2026-06-08 17:37 ` Christian Loehle
2026-06-09 3:19 ` Ricardo Neri
6 siblings, 1 reply; 17+ messages in thread
From: Christian Loehle @ 2026-06-08 17:37 UTC (permalink / raw)
To: Ricardo Neri, Ingo Molnar, Peter Zijlstra, Juri Lelli,
Vincent Guittot, Dietmar Eggemann, Steven Rostedt, Ben Segall,
Mel Gorman, Valentin Schneider, Tim C Chen, Chen Yu, Barry Song
Cc: Rafael J. Wysocki, Andrea Righi, K Prateek Nayak, Len Brown,
ricardo.neri, linux-kernel, Vincent Guittot
On 6/8/26 13:57, Ricardo Neri wrote:
> Hi,
>
> This is v4 of the series. The most important change in this version is a
> pre-work patch to fix a bug that surfaced after the SMT-aware asymmetric
> CPU capacity patchset from Andrea and Prateek [1] was applied. This led me
> to do more testing. Please read the changelog for details.
>
> Cluster scheduling aims to maximize performance by spreading load across
> clusters of CPUs that share mid-level resources [2]. It works well on
> uniform systems, but it breaks down on topologies with big and small
> cores arranged in clusters. As a result, it fails on several generations
> of Intel processors already shipped and upcoming.
>
> Consider the topology below of big (B) cores and clusters of small (s)
> cores.
> ------ ------
> | B | | B | ----------------- -----------------
> | | | | | s | s | s | s | | s | s | s | s |
> ------ ------ ----------------- -----------------
> | L2 | | L2 | | L2 | | L2 |
> -------------------------------------------------------
> | L3 |
> -------------------------------------------------------
>
> On a partially busy system (one with idle CPUs; busy CPUs have one task
> each), scheduling for asymmetric capacity ensures that misfit tasks land on
> the big CPUs. The remaining tasks, misfit or not, run on the small CPUs.
> When CONFIG_SCHED_CLUSTER is enabled, these remaining tasks are supposed to
> be evenly spread among the small-CPU clusters. Today, this does not
> happen.
>
> Several issues in the load balancer prevent a small CPU in one cluster
> from pulling tasks from another:
>
> a) update_sd_pick_busiest() may select a fully_busy group with higher
> per-CPU capacity as the busiest, preventing a subsequent fully_busy
> group of equal capacity from being correctly selected.
> b) Misfit-load statistics are used to identify tasks that would benefit
> from migrating to bigger CPUs. Accounting misfit load is pointless if
> the destination CPU is equally small, and it also blocks balancing
> between clusters.
> c) Due to b), groups that are truly has_spare or fully_busy get
> misclassified as misfit_task. update_sd_pick_busiest() then skips
> them, since a small destination CPU cannot help with misfit tasks.
> d) Once a busiest group has been identified, sched_balance_find_src_rq()
> will refuse to migrate tasks to CPUs of equal capacity, even when
> doing so is precisely what is required to balance small-CPU clusters.
> e) The SD_PREFER_SIBLING flag is missing from scheduling domains with
> asymmetric capacity, preventing the balancer from equalizing load
> across sibling small-core clusters.
>
> Together, these issues prevent cluster-level balancing on systems with
> asymmetric CPU capacity.
>
> This series addresses each problem and restores the intended behavior.
> Details, rationale, and code changes are explained in each patch.
>
> I tested these patches on Alder Lake, which has both SMT Pcores and
> clusters of Ecores. I tested with SMT both disabled and enabled. I also
> tested on Lunar Lake and Panther Lake, which have an Ecore cluster not
> connected to the L3 cache. I repeated the same experiment with
> CONFIG_SCHED_CLUSTER disabled. The load balancer behaves as expected.
>
> Link: https://lore.kernel.org/all/20260509180955.1840064-1-arighi@nvidia.com/ [1]
> Link: https://lore.kernel.org/r/20210924085104.44806-1-21cnbao@gmail.com/ [2]
>
> Changes in v4:
> - Patch 1 (pre-work): Fixed a bug that would block load balancing on SMT
> cores with more than one busy sibling.
> - Patch 2 (pre-work): Fixed a bug that would needlessly update
> sg_overloaded.
> - Patch 5: Reworked logic using a local variable for improved
> readability.
> - Added Reviewed-by tags from Chen Yu, Tim, and Vincent. Thanks!
> - Link to v3: https://lore.kernel.org/r/20260514-rneri-fix-cas-clusters-v3-0-0037869554bd@linux.intel.com
>
> Changes in v3:
> - Patch 3: Reverted the inverted runtime capacity check. The inverted
> form resulted in migrations to CPUs of slightly lower capacity. Guarded
> the check for architectural capacity with the sched_cluster_active
> static key.
> - Patch 4: Expanded the patch description to explain the behavior of
> overloaded groups and low-capacity clusters with spare capacity.
> - Added Reviewed-by tags from Christian. Thanks!
> - Link to v2: https://lore.kernel.org/r/20260429-rneri-fix-cas-clusters-v2-0-cd787de35cc6@linux.intel.com
>
> Changes in v2:
> - Patch 1: Rewrote patch description for clarity. Added a note
> clarifying that SD_ASYM_CPUCAPACITY and SMT are mutually
> exclusive. (Tim)
> - Patch 2: Fixed a bug where the capacity check inadvertently broke
> the mutual exclusion of the sched_reduced_capacity() path. Keep
> marking the root domain as overloaded when misfit tasks are present
> to allow bigger CPUs to help via newly idle balance. (sashiko)
> Fixed the description to state that capacity_greater() looks for
> differences of ~5% or more, not 20%. (Christian)
> - Patch 3: Use arch_scale_cpu_capacity() instead of capacity_of() to
> ignore runtime capacity variability. Inverted the capacity check.
> (Christian)
> - Patch 4: Reworded the patch description for clarity.
> - Link to v1: https://lore.kernel.org/r/20260330-rneri-fix-cas-clusters-v1-0-1e465b6fecb2@linux.intel.com/
>
> ---
> Ricardo Neri (6):
> sched/fair: Do not skip CPUs of similar capacity with busy SMT siblings
> sched/fair: Also gate overloaded status update for SD_ASYM_CPUCAPACITY
> sched/fair: Check CPU capacity before comparing group types during load balance
> sched/fair: Skip misfit load accounting when the destination CPU cannot help
> sched/fair: Allow load balancing between CPUs of identical capacity
> sched/topology: Do not clear SD_PREFER_SIBLING in domains with clusters
>
> include/linux/sched/sd_flags.h | 3 ++-
> kernel/sched/fair.c | 57 +++++++++++++++++++++++++++++++-----------
> kernel/sched/topology.c | 14 +++++++++--
> 3 files changed, 56 insertions(+), 18 deletions(-)
> ---
> base-commit: 83313bb25a6ace43b0cb5bde881213e6cfb3b046
> change-id: 20250620-rneri-fix-cas-clusters-bb4287d1e152
>
> Best regards,
Since I don't really have an arm64 machine that hits the described case just
right, I tested the series on a synthetic arm64 qemu topology with two
equal-capacity little clusters and one 1024 cluster.
The guest was booted with QEMU virt, 8 CPUs and a custom dtb. The resulting
topology is:
cluster0: CPUs 0-1, cpu_capacity=446
cluster1: CPUs 2-3, cpu_capacity=446
cluster2: CPUs 4-7, cpu_capacity=1024
The dtb describes the clusters with cpu-map. The test kernel was built with
CONFIG_SCHED_CLUSTER enabled.
I used an rt-app workload with 8 (nr_cpus) SCHED_OTHER tasks.
Each task used the same two phases:
"pinned": {
"loop": 100,
"run": 99000,
"timer": { "ref": "unique", "period": 100000 },
"cpus": [0, 1, 4, 5, 6, 7]
},
"open": {
"loop": 100,
"run": 99000,
"timer": { "ref": "unique", "period": 100000 },
"cpus": [0, 1, 2, 3, 4, 5, 6, 7]
}
The intent is to first force the workload onto cluster0 plus the big cluster,
leaving the second little cluster unused. Then the affinity mask is opened to
all CPUs. If load balancing across equal-capacity clusters works, CPUs 2-3
should receive a meaningful share of the work (instead of only occasional
migrations).
I counted rt-app sched_switch events per cluster in the open phase. The pass
condition was that cluster1_little receives at least 20% of open-phase rt-app
sched_switch events.
Results over three runs (for the open phases):
mainline:
run0: cluster0 5.7%, cluster1 5.7%, big 88.6% FAIL
run1: cluster0 5.5%, cluster1 6.2%, big 88.3% FAIL
run2: cluster0 4.3%, cluster1 4.7%, big 91.0% FAIL
with this series:
run0: cluster0 38.6%, cluster1 31.4%, big 30.1% PASS
run1: cluster0 33.2%, cluster1 60.6%, big 6.3% PASS
run2: cluster0 33.3%, cluster1 60.6%, big 6.1% PASS
(The pinned phase behaved as expected in all runs: there were no rt-app
sched_switch samples on CPUs 2-3 before the affinity mask was opened.)
For the series (patch 1/6 is a different setup, so maybe except for that)
Tested-by: Christian Loehle <christian.loehle@arm.com>
^ permalink raw reply [flat|nested] 17+ messages in thread* Re: [PATCH v4 0/6] sched: Fix cluster scheduling in the presence of asymmetric capacity
2026-06-08 17:37 ` [PATCH v4 0/6] sched: Fix cluster scheduling in the presence of asymmetric capacity Christian Loehle
@ 2026-06-09 3:19 ` Ricardo Neri
2026-06-09 20:09 ` Christian Loehle
0 siblings, 1 reply; 17+ messages in thread
From: Ricardo Neri @ 2026-06-09 3:19 UTC (permalink / raw)
To: Christian Loehle
Cc: Ingo Molnar, Peter Zijlstra, Juri Lelli, Vincent Guittot,
Dietmar Eggemann, Steven Rostedt, Ben Segall, Mel Gorman,
Valentin Schneider, Tim C Chen, Chen Yu, Barry Song,
Rafael J. Wysocki, Andrea Righi, K Prateek Nayak, Len Brown,
ricardo.neri, linux-kernel, Vincent Guittot
On Mon, Jun 08, 2026 at 06:37:41PM +0100, Christian Loehle wrote:
> On 6/8/26 13:57, Ricardo Neri wrote:
> > Hi,
> >
> > This is v4 of the series. The most important change in this version is a
> > pre-work patch to fix a bug that surfaced after the SMT-aware asymmetric
> > CPU capacity patchset from Andrea and Prateek [1] was applied. This led me
> > to do more testing. Please read the changelog for details.
> >
> > Cluster scheduling aims to maximize performance by spreading load across
> > clusters of CPUs that share mid-level resources [2]. It works well on
> > uniform systems, but it breaks down on topologies with big and small
> > cores arranged in clusters. As a result, it fails on several generations
> > of Intel processors already shipped and upcoming.
> >
> > Consider the topology below of big (B) cores and clusters of small (s)
> > cores.
> > ------ ------
> > | B | | B | ----------------- -----------------
> > | | | | | s | s | s | s | | s | s | s | s |
> > ------ ------ ----------------- -----------------
> > | L2 | | L2 | | L2 | | L2 |
> > -------------------------------------------------------
> > | L3 |
> > -------------------------------------------------------
> >
> > On a partially busy system (one with idle CPUs; busy CPUs have one task
> > each), scheduling for asymmetric capacity ensures that misfit tasks land on
> > the big CPUs. The remaining tasks, misfit or not, run on the small CPUs.
> > When CONFIG_SCHED_CLUSTER is enabled, these remaining tasks are supposed to
> > be evenly spread among the small-CPU clusters. Today, this does not
> > happen.
> >
> > Several issues in the load balancer prevent a small CPU in one cluster
> > from pulling tasks from another:
> >
> > a) update_sd_pick_busiest() may select a fully_busy group with higher
> > per-CPU capacity as the busiest, preventing a subsequent fully_busy
> > group of equal capacity from being correctly selected.
> > b) Misfit-load statistics are used to identify tasks that would benefit
> > from migrating to bigger CPUs. Accounting misfit load is pointless if
> > the destination CPU is equally small, and it also blocks balancing
> > between clusters.
> > c) Due to b), groups that are truly has_spare or fully_busy get
> > misclassified as misfit_task. update_sd_pick_busiest() then skips
> > them, since a small destination CPU cannot help with misfit tasks.
> > d) Once a busiest group has been identified, sched_balance_find_src_rq()
> > will refuse to migrate tasks to CPUs of equal capacity, even when
> > doing so is precisely what is required to balance small-CPU clusters.
> > e) The SD_PREFER_SIBLING flag is missing from scheduling domains with
> > asymmetric capacity, preventing the balancer from equalizing load
> > across sibling small-core clusters.
> >
> > Together, these issues prevent cluster-level balancing on systems with
> > asymmetric CPU capacity.
> >
> > This series addresses each problem and restores the intended behavior.
> > Details, rationale, and code changes are explained in each patch.
> >
> > I tested these patches on Alder Lake, which has both SMT Pcores and
> > clusters of Ecores. I tested with SMT both disabled and enabled. I also
> > tested on Lunar Lake and Panther Lake, which have an Ecore cluster not
> > connected to the L3 cache. I repeated the same experiment with
> > CONFIG_SCHED_CLUSTER disabled. The load balancer behaves as expected.
> >
> > Link: https://lore.kernel.org/all/20260509180955.1840064-1-arighi@nvidia.com/ [1]
> > Link: https://lore.kernel.org/r/20210924085104.44806-1-21cnbao@gmail.com/ [2]
> >
> > Changes in v4:
> > - Patch 1 (pre-work): Fixed a bug that would block load balancing on SMT
> > cores with more than one busy sibling.
> > - Patch 2 (pre-work): Fixed a bug that would needlessly update
> > sg_overloaded.
> > - Patch 5: Reworked logic using a local variable for improved
> > readability.
> > - Added Reviewed-by tags from Chen Yu, Tim, and Vincent. Thanks!
> > - Link to v3: https://lore.kernel.org/r/20260514-rneri-fix-cas-clusters-v3-0-0037869554bd@linux.intel.com
> >
> > Changes in v3:
> > - Patch 3: Reverted the inverted runtime capacity check. The inverted
> > form resulted in migrations to CPUs of slightly lower capacity. Guarded
> > the check for architectural capacity with the sched_cluster_active
> > static key.
> > - Patch 4: Expanded the patch description to explain the behavior of
> > overloaded groups and low-capacity clusters with spare capacity.
> > - Added Reviewed-by tags from Christian. Thanks!
> > - Link to v2: https://lore.kernel.org/r/20260429-rneri-fix-cas-clusters-v2-0-cd787de35cc6@linux.intel.com
> >
> > Changes in v2:
> > - Patch 1: Rewrote patch description for clarity. Added a note
> > clarifying that SD_ASYM_CPUCAPACITY and SMT are mutually
> > exclusive. (Tim)
> > - Patch 2: Fixed a bug where the capacity check inadvertently broke
> > the mutual exclusion of the sched_reduced_capacity() path. Keep
> > marking the root domain as overloaded when misfit tasks are present
> > to allow bigger CPUs to help via newly idle balance. (sashiko)
> > Fixed the description to state that capacity_greater() looks for
> > differences of ~5% or more, not 20%. (Christian)
> > - Patch 3: Use arch_scale_cpu_capacity() instead of capacity_of() to
> > ignore runtime capacity variability. Inverted the capacity check.
> > (Christian)
> > - Patch 4: Reworded the patch description for clarity.
> > - Link to v1: https://lore.kernel.org/r/20260330-rneri-fix-cas-clusters-v1-0-1e465b6fecb2@linux.intel.com/
> >
> > ---
> > Ricardo Neri (6):
> > sched/fair: Do not skip CPUs of similar capacity with busy SMT siblings
> > sched/fair: Also gate overloaded status update for SD_ASYM_CPUCAPACITY
> > sched/fair: Check CPU capacity before comparing group types during load balance
> > sched/fair: Skip misfit load accounting when the destination CPU cannot help
> > sched/fair: Allow load balancing between CPUs of identical capacity
> > sched/topology: Do not clear SD_PREFER_SIBLING in domains with clusters
> >
> > include/linux/sched/sd_flags.h | 3 ++-
> > kernel/sched/fair.c | 57 +++++++++++++++++++++++++++++++-----------
> > kernel/sched/topology.c | 14 +++++++++--
> > 3 files changed, 56 insertions(+), 18 deletions(-)
> > ---
> > base-commit: 83313bb25a6ace43b0cb5bde881213e6cfb3b046
> > change-id: 20250620-rneri-fix-cas-clusters-bb4287d1e152
> >
> > Best regards,
>
> Since I don't really have an arm64 machine that hits the described case just
> right, I tested the series on a synthetic arm64 qemu topology with two
> equal-capacity little clusters and one 1024 cluster.
>
> The guest was booted with QEMU virt, 8 CPUs and a custom dtb. The resulting
> topology is:
> cluster0: CPUs 0-1, cpu_capacity=446
> cluster1: CPUs 2-3, cpu_capacity=446
> cluster2: CPUs 4-7, cpu_capacity=1024
> The dtb describes the clusters with cpu-map. The test kernel was built with
> CONFIG_SCHED_CLUSTER enabled.
>
> I used an rt-app workload with 8 (nr_cpus) SCHED_OTHER tasks.
> Each task used the same two phases:
> "pinned": {
> "loop": 100,
> "run": 99000,
> "timer": { "ref": "unique", "period": 100000 },
> "cpus": [0, 1, 4, 5, 6, 7]
> },
> "open": {
> "loop": 100,
> "run": 99000,
> "timer": { "ref": "unique", "period": 100000 },
> "cpus": [0, 1, 2, 3, 4, 5, 6, 7]
> }
>
> The intent is to first force the workload onto cluster0 plus the big cluster,
> leaving the second little cluster unused. Then the affinity mask is opened to
> all CPUs. If load balancing across equal-capacity clusters works, CPUs 2-3
> should receive a meaningful share of the work (instead of only occasional
> migrations).
>
> I counted rt-app sched_switch events per cluster in the open phase. The pass
> condition was that cluster1_little receives at least 20% of open-phase rt-app
> sched_switch events.
>
> Results over three runs (for the open phases):
>
> mainline:
> run0: cluster0 5.7%, cluster1 5.7%, big 88.6% FAIL
> run1: cluster0 5.5%, cluster1 6.2%, big 88.3% FAIL
> run2: cluster0 4.3%, cluster1 4.7%, big 91.0% FAIL
>
> with this series:
> run0: cluster0 38.6%, cluster1 31.4%, big 30.1% PASS
> run1: cluster0 33.2%, cluster1 60.6%, big 6.3% PASS
> run2: cluster0 33.3%, cluster1 60.6%, big 6.1% PASS
>
> (The pinned phase behaved as expected in all runs: there were no rt-app
> sched_switch samples on CPUs 2-3 before the affinity mask was opened.)
>
> For the series (patch 1/6 is a different setup, so maybe except for that)
> Tested-by: Christian Loehle <christian.loehle@arm.com>
Many thanks for your tests! I have two questions: Do you see similar
results of you spawn less tasks than nr_cpus? Perhaps with 6 tasks? They
should continue to be evenly distributed on same-capacity clusters.
Also, are these high-utilization tasks? If yes, the high-capacity cluster
should be fully utilized before any tasks overflow to the lower-capacity
clusters.
^ permalink raw reply [flat|nested] 17+ messages in thread* Re: [PATCH v4 0/6] sched: Fix cluster scheduling in the presence of asymmetric capacity
2026-06-09 3:19 ` Ricardo Neri
@ 2026-06-09 20:09 ` Christian Loehle
2026-06-10 3:28 ` Ricardo Neri
0 siblings, 1 reply; 17+ messages in thread
From: Christian Loehle @ 2026-06-09 20:09 UTC (permalink / raw)
To: Ricardo Neri
Cc: Ingo Molnar, Peter Zijlstra, Juri Lelli, Vincent Guittot,
Dietmar Eggemann, Steven Rostedt, Ben Segall, Mel Gorman,
Valentin Schneider, Tim C Chen, Chen Yu, Barry Song,
Rafael J. Wysocki, Andrea Righi, K Prateek Nayak, Len Brown,
ricardo.neri, linux-kernel, Vincent Guittot
On 6/9/26 04:19, Ricardo Neri wrote:
> On Mon, Jun 08, 2026 at 06:37:41PM +0100, Christian Loehle wrote:
>> On 6/8/26 13:57, Ricardo Neri wrote:
>>> Hi,
>>>
>>> This is v4 of the series. The most important change in this version is a
>>> pre-work patch to fix a bug that surfaced after the SMT-aware asymmetric
>>> CPU capacity patchset from Andrea and Prateek [1] was applied. This led me
>>> to do more testing. Please read the changelog for details.
>>>
>>> Cluster scheduling aims to maximize performance by spreading load across
>>> clusters of CPUs that share mid-level resources [2]. It works well on
>>> uniform systems, but it breaks down on topologies with big and small
>>> cores arranged in clusters. As a result, it fails on several generations
>>> of Intel processors already shipped and upcoming.
>>>
>>> Consider the topology below of big (B) cores and clusters of small (s)
>>> cores.
>>> ------ ------
>>> | B | | B | ----------------- -----------------
>>> | | | | | s | s | s | s | | s | s | s | s |
>>> ------ ------ ----------------- -----------------
>>> | L2 | | L2 | | L2 | | L2 |
>>> -------------------------------------------------------
>>> | L3 |
>>> -------------------------------------------------------
>>>
>>> On a partially busy system (one with idle CPUs; busy CPUs have one task
>>> each), scheduling for asymmetric capacity ensures that misfit tasks land on
>>> the big CPUs. The remaining tasks, misfit or not, run on the small CPUs.
>>> When CONFIG_SCHED_CLUSTER is enabled, these remaining tasks are supposed to
>>> be evenly spread among the small-CPU clusters. Today, this does not
>>> happen.
>>>
>>> Several issues in the load balancer prevent a small CPU in one cluster
>>> from pulling tasks from another:
>>>
>>> a) update_sd_pick_busiest() may select a fully_busy group with higher
>>> per-CPU capacity as the busiest, preventing a subsequent fully_busy
>>> group of equal capacity from being correctly selected.
>>> b) Misfit-load statistics are used to identify tasks that would benefit
>>> from migrating to bigger CPUs. Accounting misfit load is pointless if
>>> the destination CPU is equally small, and it also blocks balancing
>>> between clusters.
>>> c) Due to b), groups that are truly has_spare or fully_busy get
>>> misclassified as misfit_task. update_sd_pick_busiest() then skips
>>> them, since a small destination CPU cannot help with misfit tasks.
>>> d) Once a busiest group has been identified, sched_balance_find_src_rq()
>>> will refuse to migrate tasks to CPUs of equal capacity, even when
>>> doing so is precisely what is required to balance small-CPU clusters.
>>> e) The SD_PREFER_SIBLING flag is missing from scheduling domains with
>>> asymmetric capacity, preventing the balancer from equalizing load
>>> across sibling small-core clusters.
>>>
>>> Together, these issues prevent cluster-level balancing on systems with
>>> asymmetric CPU capacity.
>>>
>>> This series addresses each problem and restores the intended behavior.
>>> Details, rationale, and code changes are explained in each patch.
>>>
>>> I tested these patches on Alder Lake, which has both SMT Pcores and
>>> clusters of Ecores. I tested with SMT both disabled and enabled. I also
>>> tested on Lunar Lake and Panther Lake, which have an Ecore cluster not
>>> connected to the L3 cache. I repeated the same experiment with
>>> CONFIG_SCHED_CLUSTER disabled. The load balancer behaves as expected.
>>>
>>> Link: https://lore.kernel.org/all/20260509180955.1840064-1-arighi@nvidia.com/ [1]
>>> Link: https://lore.kernel.org/r/20210924085104.44806-1-21cnbao@gmail.com/ [2]
>>>
>>> Changes in v4:
>>> - Patch 1 (pre-work): Fixed a bug that would block load balancing on SMT
>>> cores with more than one busy sibling.
>>> - Patch 2 (pre-work): Fixed a bug that would needlessly update
>>> sg_overloaded.
>>> - Patch 5: Reworked logic using a local variable for improved
>>> readability.
>>> - Added Reviewed-by tags from Chen Yu, Tim, and Vincent. Thanks!
>>> - Link to v3: https://lore.kernel.org/r/20260514-rneri-fix-cas-clusters-v3-0-0037869554bd@linux.intel.com
>>>
>>> Changes in v3:
>>> - Patch 3: Reverted the inverted runtime capacity check. The inverted
>>> form resulted in migrations to CPUs of slightly lower capacity. Guarded
>>> the check for architectural capacity with the sched_cluster_active
>>> static key.
>>> - Patch 4: Expanded the patch description to explain the behavior of
>>> overloaded groups and low-capacity clusters with spare capacity.
>>> - Added Reviewed-by tags from Christian. Thanks!
>>> - Link to v2: https://lore.kernel.org/r/20260429-rneri-fix-cas-clusters-v2-0-cd787de35cc6@linux.intel.com
>>>
>>> Changes in v2:
>>> - Patch 1: Rewrote patch description for clarity. Added a note
>>> clarifying that SD_ASYM_CPUCAPACITY and SMT are mutually
>>> exclusive. (Tim)
>>> - Patch 2: Fixed a bug where the capacity check inadvertently broke
>>> the mutual exclusion of the sched_reduced_capacity() path. Keep
>>> marking the root domain as overloaded when misfit tasks are present
>>> to allow bigger CPUs to help via newly idle balance. (sashiko)
>>> Fixed the description to state that capacity_greater() looks for
>>> differences of ~5% or more, not 20%. (Christian)
>>> - Patch 3: Use arch_scale_cpu_capacity() instead of capacity_of() to
>>> ignore runtime capacity variability. Inverted the capacity check.
>>> (Christian)
>>> - Patch 4: Reworded the patch description for clarity.
>>> - Link to v1: https://lore.kernel.org/r/20260330-rneri-fix-cas-clusters-v1-0-1e465b6fecb2@linux.intel.com/
>>>
>>> ---
>>> Ricardo Neri (6):
>>> sched/fair: Do not skip CPUs of similar capacity with busy SMT siblings
>>> sched/fair: Also gate overloaded status update for SD_ASYM_CPUCAPACITY
>>> sched/fair: Check CPU capacity before comparing group types during load balance
>>> sched/fair: Skip misfit load accounting when the destination CPU cannot help
>>> sched/fair: Allow load balancing between CPUs of identical capacity
>>> sched/topology: Do not clear SD_PREFER_SIBLING in domains with clusters
>>>
>>> include/linux/sched/sd_flags.h | 3 ++-
>>> kernel/sched/fair.c | 57 +++++++++++++++++++++++++++++++-----------
>>> kernel/sched/topology.c | 14 +++++++++--
>>> 3 files changed, 56 insertions(+), 18 deletions(-)
>>> ---
>>> base-commit: 83313bb25a6ace43b0cb5bde881213e6cfb3b046
>>> change-id: 20250620-rneri-fix-cas-clusters-bb4287d1e152
>>>
>>> Best regards,
>>
>> Since I don't really have an arm64 machine that hits the described case just
>> right, I tested the series on a synthetic arm64 qemu topology with two
>> equal-capacity little clusters and one 1024 cluster.
>>
>> The guest was booted with QEMU virt, 8 CPUs and a custom dtb. The resulting
>> topology is:
>> cluster0: CPUs 0-1, cpu_capacity=446
>> cluster1: CPUs 2-3, cpu_capacity=446
>> cluster2: CPUs 4-7, cpu_capacity=1024
>> The dtb describes the clusters with cpu-map. The test kernel was built with
>> CONFIG_SCHED_CLUSTER enabled.
>>
>> I used an rt-app workload with 8 (nr_cpus) SCHED_OTHER tasks.
>> Each task used the same two phases:
>> "pinned": {
>> "loop": 100,
>> "run": 99000,
>> "timer": { "ref": "unique", "period": 100000 },
>> "cpus": [0, 1, 4, 5, 6, 7]
>> },
>> "open": {
>> "loop": 100,
>> "run": 99000,
>> "timer": { "ref": "unique", "period": 100000 },
>> "cpus": [0, 1, 2, 3, 4, 5, 6, 7]
>> }
>>
>> The intent is to first force the workload onto cluster0 plus the big cluster,
>> leaving the second little cluster unused. Then the affinity mask is opened to
>> all CPUs. If load balancing across equal-capacity clusters works, CPUs 2-3
>> should receive a meaningful share of the work (instead of only occasional
>> migrations).
>>
>> I counted rt-app sched_switch events per cluster in the open phase. The pass
>> condition was that cluster1_little receives at least 20% of open-phase rt-app
>> sched_switch events.
>>
>> Results over three runs (for the open phases):
>>
>> mainline:
>> run0: cluster0 5.7%, cluster1 5.7%, big 88.6% FAIL
>> run1: cluster0 5.5%, cluster1 6.2%, big 88.3% FAIL
>> run2: cluster0 4.3%, cluster1 4.7%, big 91.0% FAIL
>>
>> with this series:
>> run0: cluster0 38.6%, cluster1 31.4%, big 30.1% PASS
>> run1: cluster0 33.2%, cluster1 60.6%, big 6.3% PASS
>> run2: cluster0 33.3%, cluster1 60.6%, big 6.1% PASS
>>
>> (The pinned phase behaved as expected in all runs: there were no rt-app
>> sched_switch samples on CPUs 2-3 before the affinity mask was opened.)
>>
>> For the series (patch 1/6 is a different setup, so maybe except for that)
>> Tested-by: Christian Loehle <christian.loehle@arm.com>
>
> Many thanks for your tests! I have two questions: Do you see similar
> results of you spawn less tasks than nr_cpus? Perhaps with 6 tasks? They
> should continue to be evenly distributed on same-capacity clusters.
with 6 tasks:
pinned phase first timestamp: 1850.75
open phase first timestamp: 1879.02
before_open sched_switch samples: 79
cpu0: 30
cpu1: 8
cpu2: 0
cpu3: 0
cpu4: 9
cpu5: 6
cpu6: 7
cpu7: 19
cluster0_little: 38 (48.1%)
cluster1_little: 0 (0.0%)
cluster2_big: 41 (51.9%)
after_open sched_switch samples: 899
cpu0: 235
cpu1: 176
cpu2: 225
cpu3: 218
cpu4: 9
cpu5: 18
cpu6: 8
cpu7: 10
cluster0_little: 411 (45.7%)
cluster1_little: 443 (49.3%)
cluster2_big: 45 (5.0%)
after_open sched_migrate_task destination clusters:
cluster0_little: 380
cluster1_little: 408
cluster2_big: 6
runtime evaluation:
before_open runtime: 169.337475s
cluster0_little: 57.503009s (34.0%)
cluster1_little: 0.000000s (0.0%)
cluster2_big: 111.834466s (66.0%)
after_open runtime: 191.293461s
cluster0_little: 27.926735s (14.6%)
cluster1_little: 33.729412s (17.6%)
cluster2_big: 129.637314s (67.8%)
after_open little-to-big rt-app migrations: 2
1910.977751 rtapp04-4 cpu0 -> cpu7
1911.011555 rtapp03-3 cpu2 -> cpu5
big-cluster rtapp_task:end count: 6
first: 1910.975772 last: 1913.225910
FWIW I also mirrored the pinned phase (so pinned to cluster1 first):
switch-count evaluation:
pinned phase first timestamp: 1469.11
open phase first timestamp: 1498.6
before_open sched_switch samples: 61
cpu0: 0
cpu1: 0
cpu2: 6
cpu3: 13
cpu4: 24
cpu5: 6
cpu6: 6
cpu7: 6
cluster0_little: 0 (0.0%)
cluster1_little: 19 (31.1%)
cluster2_big: 42 (68.9%)
after_open sched_switch samples: 883
cpu0: 221
cpu1: 178
cpu2: 226
cpu3: 219
cpu4: 10
cpu5: 7
cpu6: 11
cpu7: 11
cluster0_little: 399 (45.2%)
cluster1_little: 445 (50.4%)
cluster2_big: 39 (4.4%)
after_open sched_migrate_task destination clusters:
cluster0_little: 370
cluster1_little: 403
cluster2_big: 8
runtime evaluation:
before_open runtime: 171.855925s
cluster0_little: 0.000000s (0.0%)
cluster1_little: 58.754673s (34.2%)
cluster2_big: 113.101252s (65.8%)
after_open runtime: 189.467864s
cluster0_little: 27.279064s (14.4%)
cluster1_little: 31.392607s (16.6%)
cluster2_big: 130.796193s (69.0%)
after_open little-to-big rt-app migrations: 3
1529.002652 rtapp04-4 cpu0 -> cpu6
1529.002902 rtapp02-2 cpu3 -> cpu6
1529.778636 rtapp02-2 cpu0 -> cpu7
big-cluster rtapp_task:end count: 6
first: 1529.000465 last: 1533.242563
>
> Also, are these high-utilization tasks? If yes, the high-capacity cluster
> should be fully utilized before any tasks overflow to the lower-capacity
> clusters.
Yes, sorry I should've mentioned, the above rt-app tasks will use 99% of the
capacity of 1024 CPU, so the expected behavior is that all CPUs are used.
Once the 1024 CPUs finish (as they should finish in ~half the time), tasks
of any little cluster will be upmigrated to the 1024.
I did quickly check if that is the case, which it was, but that part is
definitely more than wonky on qemu (as the capacities are just in the dtb,
the CPUs are of course vCPUs which behave very noisily and with no correlation
to the capacity value).
^ permalink raw reply [flat|nested] 17+ messages in thread* Re: [PATCH v4 0/6] sched: Fix cluster scheduling in the presence of asymmetric capacity
2026-06-09 20:09 ` Christian Loehle
@ 2026-06-10 3:28 ` Ricardo Neri
0 siblings, 0 replies; 17+ messages in thread
From: Ricardo Neri @ 2026-06-10 3:28 UTC (permalink / raw)
To: Christian Loehle
Cc: Ingo Molnar, Peter Zijlstra, Juri Lelli, Vincent Guittot,
Dietmar Eggemann, Steven Rostedt, Ben Segall, Mel Gorman,
Valentin Schneider, Tim C Chen, Chen Yu, Barry Song,
Rafael J. Wysocki, Andrea Righi, K Prateek Nayak, Len Brown,
ricardo.neri, linux-kernel, Vincent Guittot
On Tue, Jun 09, 2026 at 09:09:25PM +0100, Christian Loehle wrote:
> On 6/9/26 04:19, Ricardo Neri wrote:
> > On Mon, Jun 08, 2026 at 06:37:41PM +0100, Christian Loehle wrote:
> >> On 6/8/26 13:57, Ricardo Neri wrote:
> >>> Hi,
> >>>
> >>> This is v4 of the series. The most important change in this version is a
> >>> pre-work patch to fix a bug that surfaced after the SMT-aware asymmetric
> >>> CPU capacity patchset from Andrea and Prateek [1] was applied. This led me
> >>> to do more testing. Please read the changelog for details.
> >>>
> >>> Cluster scheduling aims to maximize performance by spreading load across
> >>> clusters of CPUs that share mid-level resources [2]. It works well on
> >>> uniform systems, but it breaks down on topologies with big and small
> >>> cores arranged in clusters. As a result, it fails on several generations
> >>> of Intel processors already shipped and upcoming.
> >>>
> >>> Consider the topology below of big (B) cores and clusters of small (s)
> >>> cores.
> >>> ------ ------
> >>> | B | | B | ----------------- -----------------
> >>> | | | | | s | s | s | s | | s | s | s | s |
> >>> ------ ------ ----------------- -----------------
> >>> | L2 | | L2 | | L2 | | L2 |
> >>> -------------------------------------------------------
> >>> | L3 |
> >>> -------------------------------------------------------
> >>>
> >>> On a partially busy system (one with idle CPUs; busy CPUs have one task
> >>> each), scheduling for asymmetric capacity ensures that misfit tasks land on
> >>> the big CPUs. The remaining tasks, misfit or not, run on the small CPUs.
> >>> When CONFIG_SCHED_CLUSTER is enabled, these remaining tasks are supposed to
> >>> be evenly spread among the small-CPU clusters. Today, this does not
> >>> happen.
> >>>
> >>> Several issues in the load balancer prevent a small CPU in one cluster
> >>> from pulling tasks from another:
> >>>
> >>> a) update_sd_pick_busiest() may select a fully_busy group with higher
> >>> per-CPU capacity as the busiest, preventing a subsequent fully_busy
> >>> group of equal capacity from being correctly selected.
> >>> b) Misfit-load statistics are used to identify tasks that would benefit
> >>> from migrating to bigger CPUs. Accounting misfit load is pointless if
> >>> the destination CPU is equally small, and it also blocks balancing
> >>> between clusters.
> >>> c) Due to b), groups that are truly has_spare or fully_busy get
> >>> misclassified as misfit_task. update_sd_pick_busiest() then skips
> >>> them, since a small destination CPU cannot help with misfit tasks.
> >>> d) Once a busiest group has been identified, sched_balance_find_src_rq()
> >>> will refuse to migrate tasks to CPUs of equal capacity, even when
> >>> doing so is precisely what is required to balance small-CPU clusters.
> >>> e) The SD_PREFER_SIBLING flag is missing from scheduling domains with
> >>> asymmetric capacity, preventing the balancer from equalizing load
> >>> across sibling small-core clusters.
> >>>
> >>> Together, these issues prevent cluster-level balancing on systems with
> >>> asymmetric CPU capacity.
> >>>
> >>> This series addresses each problem and restores the intended behavior.
> >>> Details, rationale, and code changes are explained in each patch.
> >>>
> >>> I tested these patches on Alder Lake, which has both SMT Pcores and
> >>> clusters of Ecores. I tested with SMT both disabled and enabled. I also
> >>> tested on Lunar Lake and Panther Lake, which have an Ecore cluster not
> >>> connected to the L3 cache. I repeated the same experiment with
> >>> CONFIG_SCHED_CLUSTER disabled. The load balancer behaves as expected.
> >>>
> >>> Link: https://lore.kernel.org/all/20260509180955.1840064-1-arighi@nvidia.com/ [1]
> >>> Link: https://lore.kernel.org/r/20210924085104.44806-1-21cnbao@gmail.com/ [2]
> >>>
> >>> Changes in v4:
> >>> - Patch 1 (pre-work): Fixed a bug that would block load balancing on SMT
> >>> cores with more than one busy sibling.
> >>> - Patch 2 (pre-work): Fixed a bug that would needlessly update
> >>> sg_overloaded.
> >>> - Patch 5: Reworked logic using a local variable for improved
> >>> readability.
> >>> - Added Reviewed-by tags from Chen Yu, Tim, and Vincent. Thanks!
> >>> - Link to v3: https://lore.kernel.org/r/20260514-rneri-fix-cas-clusters-v3-0-0037869554bd@linux.intel.com
> >>>
> >>> Changes in v3:
> >>> - Patch 3: Reverted the inverted runtime capacity check. The inverted
> >>> form resulted in migrations to CPUs of slightly lower capacity. Guarded
> >>> the check for architectural capacity with the sched_cluster_active
> >>> static key.
> >>> - Patch 4: Expanded the patch description to explain the behavior of
> >>> overloaded groups and low-capacity clusters with spare capacity.
> >>> - Added Reviewed-by tags from Christian. Thanks!
> >>> - Link to v2: https://lore.kernel.org/r/20260429-rneri-fix-cas-clusters-v2-0-cd787de35cc6@linux.intel.com
> >>>
> >>> Changes in v2:
> >>> - Patch 1: Rewrote patch description for clarity. Added a note
> >>> clarifying that SD_ASYM_CPUCAPACITY and SMT are mutually
> >>> exclusive. (Tim)
> >>> - Patch 2: Fixed a bug where the capacity check inadvertently broke
> >>> the mutual exclusion of the sched_reduced_capacity() path. Keep
> >>> marking the root domain as overloaded when misfit tasks are present
> >>> to allow bigger CPUs to help via newly idle balance. (sashiko)
> >>> Fixed the description to state that capacity_greater() looks for
> >>> differences of ~5% or more, not 20%. (Christian)
> >>> - Patch 3: Use arch_scale_cpu_capacity() instead of capacity_of() to
> >>> ignore runtime capacity variability. Inverted the capacity check.
> >>> (Christian)
> >>> - Patch 4: Reworded the patch description for clarity.
> >>> - Link to v1: https://lore.kernel.org/r/20260330-rneri-fix-cas-clusters-v1-0-1e465b6fecb2@linux.intel.com/
> >>>
> >>> ---
> >>> Ricardo Neri (6):
> >>> sched/fair: Do not skip CPUs of similar capacity with busy SMT siblings
> >>> sched/fair: Also gate overloaded status update for SD_ASYM_CPUCAPACITY
> >>> sched/fair: Check CPU capacity before comparing group types during load balance
> >>> sched/fair: Skip misfit load accounting when the destination CPU cannot help
> >>> sched/fair: Allow load balancing between CPUs of identical capacity
> >>> sched/topology: Do not clear SD_PREFER_SIBLING in domains with clusters
> >>>
> >>> include/linux/sched/sd_flags.h | 3 ++-
> >>> kernel/sched/fair.c | 57 +++++++++++++++++++++++++++++++-----------
> >>> kernel/sched/topology.c | 14 +++++++++--
> >>> 3 files changed, 56 insertions(+), 18 deletions(-)
> >>> ---
> >>> base-commit: 83313bb25a6ace43b0cb5bde881213e6cfb3b046
> >>> change-id: 20250620-rneri-fix-cas-clusters-bb4287d1e152
> >>>
> >>> Best regards,
> >>
> >> Since I don't really have an arm64 machine that hits the described case just
> >> right, I tested the series on a synthetic arm64 qemu topology with two
> >> equal-capacity little clusters and one 1024 cluster.
> >>
> >> The guest was booted with QEMU virt, 8 CPUs and a custom dtb. The resulting
> >> topology is:
> >> cluster0: CPUs 0-1, cpu_capacity=446
> >> cluster1: CPUs 2-3, cpu_capacity=446
> >> cluster2: CPUs 4-7, cpu_capacity=1024
> >> The dtb describes the clusters with cpu-map. The test kernel was built with
> >> CONFIG_SCHED_CLUSTER enabled.
> >>
> >> I used an rt-app workload with 8 (nr_cpus) SCHED_OTHER tasks.
> >> Each task used the same two phases:
> >> "pinned": {
> >> "loop": 100,
> >> "run": 99000,
> >> "timer": { "ref": "unique", "period": 100000 },
> >> "cpus": [0, 1, 4, 5, 6, 7]
> >> },
> >> "open": {
> >> "loop": 100,
> >> "run": 99000,
> >> "timer": { "ref": "unique", "period": 100000 },
> >> "cpus": [0, 1, 2, 3, 4, 5, 6, 7]
> >> }
> >>
> >> The intent is to first force the workload onto cluster0 plus the big cluster,
> >> leaving the second little cluster unused. Then the affinity mask is opened to
> >> all CPUs. If load balancing across equal-capacity clusters works, CPUs 2-3
> >> should receive a meaningful share of the work (instead of only occasional
> >> migrations).
> >>
> >> I counted rt-app sched_switch events per cluster in the open phase. The pass
> >> condition was that cluster1_little receives at least 20% of open-phase rt-app
> >> sched_switch events.
> >>
> >> Results over three runs (for the open phases):
> >>
> >> mainline:
> >> run0: cluster0 5.7%, cluster1 5.7%, big 88.6% FAIL
> >> run1: cluster0 5.5%, cluster1 6.2%, big 88.3% FAIL
> >> run2: cluster0 4.3%, cluster1 4.7%, big 91.0% FAIL
> >>
> >> with this series:
> >> run0: cluster0 38.6%, cluster1 31.4%, big 30.1% PASS
> >> run1: cluster0 33.2%, cluster1 60.6%, big 6.3% PASS
> >> run2: cluster0 33.3%, cluster1 60.6%, big 6.1% PASS
> >>
> >> (The pinned phase behaved as expected in all runs: there were no rt-app
> >> sched_switch samples on CPUs 2-3 before the affinity mask was opened.)
> >>
> >> For the series (patch 1/6 is a different setup, so maybe except for that)
> >> Tested-by: Christian Loehle <christian.loehle@arm.com>
> >
> > Many thanks for your tests! I have two questions: Do you see similar
> > results of you spawn less tasks than nr_cpus? Perhaps with 6 tasks? They
> > should continue to be evenly distributed on same-capacity clusters.
>
> with 6 tasks:
>
> pinned phase first timestamp: 1850.75
> open phase first timestamp: 1879.02
>
> before_open sched_switch samples: 79
> cpu0: 30
> cpu1: 8
> cpu2: 0
> cpu3: 0
> cpu4: 9
> cpu5: 6
> cpu6: 7
> cpu7: 19
> cluster0_little: 38 (48.1%)
> cluster1_little: 0 (0.0%)
> cluster2_big: 41 (51.9%)
>
> after_open sched_switch samples: 899
> cpu0: 235
> cpu1: 176
> cpu2: 225
> cpu3: 218
> cpu4: 9
> cpu5: 18
> cpu6: 8
> cpu7: 10
> cluster0_little: 411 (45.7%)
> cluster1_little: 443 (49.3%)
> cluster2_big: 45 (5.0%)
>
> after_open sched_migrate_task destination clusters:
> cluster0_little: 380
> cluster1_little: 408
> cluster2_big: 6
>
>
> runtime evaluation:
> before_open runtime: 169.337475s
> cluster0_little: 57.503009s (34.0%)
> cluster1_little: 0.000000s (0.0%)
> cluster2_big: 111.834466s (66.0%)
>
> after_open runtime: 191.293461s
> cluster0_little: 27.926735s (14.6%)
> cluster1_little: 33.729412s (17.6%)
> cluster2_big: 129.637314s (67.8%)
>
> after_open little-to-big rt-app migrations: 2
> 1910.977751 rtapp04-4 cpu0 -> cpu7
> 1911.011555 rtapp03-3 cpu2 -> cpu5
> big-cluster rtapp_task:end count: 6
> first: 1910.975772 last: 1913.225910
>
>
> FWIW I also mirrored the pinned phase (so pinned to cluster1 first):
>
> switch-count evaluation:
> pinned phase first timestamp: 1469.11
> open phase first timestamp: 1498.6
>
> before_open sched_switch samples: 61
> cpu0: 0
> cpu1: 0
> cpu2: 6
> cpu3: 13
> cpu4: 24
> cpu5: 6
> cpu6: 6
> cpu7: 6
> cluster0_little: 0 (0.0%)
> cluster1_little: 19 (31.1%)
> cluster2_big: 42 (68.9%)
>
> after_open sched_switch samples: 883
> cpu0: 221
> cpu1: 178
> cpu2: 226
> cpu3: 219
> cpu4: 10
> cpu5: 7
> cpu6: 11
> cpu7: 11
> cluster0_little: 399 (45.2%)
> cluster1_little: 445 (50.4%)
> cluster2_big: 39 (4.4%)
>
> after_open sched_migrate_task destination clusters:
> cluster0_little: 370
> cluster1_little: 403
> cluster2_big: 8
>
> runtime evaluation:
> before_open runtime: 171.855925s
> cluster0_little: 0.000000s (0.0%)
> cluster1_little: 58.754673s (34.2%)
> cluster2_big: 113.101252s (65.8%)
>
> after_open runtime: 189.467864s
> cluster0_little: 27.279064s (14.4%)
> cluster1_little: 31.392607s (16.6%)
> cluster2_big: 130.796193s (69.0%)
>
> after_open little-to-big rt-app migrations: 3
> 1529.002652 rtapp04-4 cpu0 -> cpu6
> 1529.002902 rtapp02-2 cpu3 -> cpu6
> 1529.778636 rtapp02-2 cpu0 -> cpu7
> big-cluster rtapp_task:end count: 6
> first: 1529.000465 last: 1533.242563
Thanks for the experiment and the details! The patchset works as expected
AFAICS.
>
>
> >
> > Also, are these high-utilization tasks? If yes, the high-capacity cluster
> > should be fully utilized before any tasks overflow to the lower-capacity
> > clusters.
>
> Yes, sorry I should've mentioned, the above rt-app tasks will use 99% of the
> capacity of 1024 CPU, so the expected behavior is that all CPUs are used.
> Once the 1024 CPUs finish (as they should finish in ~half the time), tasks
> of any little cluster will be upmigrated to the 1024.
> I did quickly check if that is the case, which it was,
Great! then it seems I didn't break anything.
> but that part is
> definitely more than wonky on qemu (as the capacities are just in the dtb,
> the CPUs are of course vCPUs which behave very noisily and with no correlation
> to the capacity value).
Indeed! :)
^ permalink raw reply [flat|nested] 17+ messages in thread