Re: [PATCH 1/2] sched/fair: Honor asymmetric SMT priority in idle selection

From: Vincent Guittot

Date: Fri Sep 18 2026 - 12:59:32 EST


On Thu, 17 Sept 2026 at 16:08, Andrea Righi <arighi@xxxxxxxxxx> wrote:
>
> POWER7 uses SD_ASYM_PACKING at the shared-capacity SMT level to order
> hardware threads, and NVIDIA Olympus benefits from the same policy. Idle
> CPU selection does not consult that order, so a task can wake on an
> arbitrary sibling and remain there until load balancing corrects the
> placement. On these systems, that initial choice can prevent the core
> from entering its preferred lower-thread resource mode and cause a large
> and persistent performance loss.
>
> When idle selection finds an available CPU in an SMT core, choose the
> highest-priority available sibling. On SMT2 Olympus this only changes
> selection on fully idle cores. A partially idle core has only one
> available CPU. On wider SMT systems such as POWER7, it also fills
> available siblings in priority order while the core is partially busy.
>
> Apply the preference to idle-core and idle-CPU scans,
> asymmetric-capacity scans, target, previous, recently-used CPU fast
> paths and the slow path. Inspect the lowest scheduling domain directly,
> but require both CPUs to share its span because isolcpus can split
> hardware siblings across scheduling domains.
>
> Keep physical-core capacity selection independent from SMT sibling
> ordering. SD_ASYM_CPUCAPACITY first selects among cores with different
> maximum capacities, then SD_ASYM_PACKING selects the preferred available
> sibling inside the chosen core, whose siblings continue to share equal
> capacity.
>
> Reviewed-by: Srikar Dronamraju <srikar@xxxxxxxxxxxxx>
> Reviewed-by: K Prateek Nayak <kprateek.nayak@xxxxxxx>
> Tested-by: K Prateek Nayak <kprateek.nayak@xxxxxxx>
> Signed-off-by: Andrea Righi <arighi@xxxxxxxxxx>

Reviewed-by: Vincent Guittot <vincent.guittot@xxxxxxxxxx>


> ---
> kernel/sched/fair.c | 85 ++++++++++++++++++++++++++++++++++++---------
> 1 file changed, 68 insertions(+), 17 deletions(-)
>
> diff --git a/kernel/sched/fair.c b/kernel/sched/fair.c
> index 4d0b94465d19e..8e6dc3a657cca 100644
> --- a/kernel/sched/fair.c
> +++ b/kernel/sched/fair.c
> @@ -8598,6 +8598,35 @@ static inline bool test_idle_cores(int cpu)
> return false;
> }
>
> +/*
> + * Redirect a CPU to a higher-priority available sibling in its SMT domain,
> + * subject to task affinity.
> + */
> +static inline int select_idle_smt_cpu(struct task_struct *p, int cpu)
> +{
> + struct sched_domain *sd;
> + int best = cpu;
> + int sibling;
> +
> + if (!sched_smt_active())
> + return cpu;
> +
> + sd = rcu_dereference_all(cpu_rq(cpu)->sd);
> + if (!sd || !(sd->flags & SD_SHARE_CPUCAPACITY) ||
> + !(sd->flags & SD_ASYM_PACKING))
> + return cpu;
> +
> + for_each_cpu_and(sibling, sched_domain_span(sd), p->cpus_ptr) {
> + if (sibling == best || !choose_idle_cpu(sibling, p))
> + continue;
> +
> + if (sched_asym_prefer(sibling, best))
> + best = sibling;
> + }
> +
> + return best;
> +}
> +
> /*
> * Scans the local SMT mask to see if the entire core is idle, and records this
> * information in sd_balance_shared->has_idle_cores.
> @@ -8982,7 +9011,7 @@ static int select_idle_sibling(struct task_struct *p, int prev, int target)
>
> if (choose_idle_cpu(target, p) &&
> asym_fits_cpu(task_util, util_min, util_max, target))
> - return target;
> + goto select_smt_priority;
>
> /*
> * If the previous CPU is cache affine and idle, don't be stupid:
> @@ -8992,8 +9021,10 @@ static int select_idle_sibling(struct task_struct *p, int prev, int target)
> asym_fits_cpu(task_util, util_min, util_max, prev)) {
>
> if (!static_branch_unlikely(&sched_cluster_active) ||
> - cpus_share_resources(prev, target))
> - return prev;
> + cpus_share_resources(prev, target)) {
> + target = prev;
> + goto select_smt_priority;
> + }
>
> prev_aff = prev;
> }
> @@ -9011,7 +9042,8 @@ static int select_idle_sibling(struct task_struct *p, int prev, int target)
> prev == smp_processor_id() &&
> this_rq()->nr_running <= 1 &&
> asym_fits_cpu(task_util, util_min, util_max, prev)) {
> - return prev;
> + target = prev;
> + goto select_smt_priority;
> }
>
> /* Check a recently used CPU as a potential idle candidate: */
> @@ -9025,8 +9057,10 @@ static int select_idle_sibling(struct task_struct *p, int prev, int target)
> asym_fits_cpu(task_util, util_min, util_max, recent_used_cpu)) {
>
> if (!static_branch_unlikely(&sched_cluster_active) ||
> - cpus_share_resources(recent_used_cpu, target))
> - return recent_used_cpu;
> + cpus_share_resources(recent_used_cpu, target)) {
> + target = recent_used_cpu;
> + goto select_smt_priority;
> + }
>
> } else {
> recent_used_cpu = -1;
> @@ -9048,7 +9082,11 @@ static int select_idle_sibling(struct task_struct *p, int prev, int target)
> */
> if (sd) {
> i = select_idle_capacity(p, sd, target);
> - return ((unsigned)i < nr_cpumask_bits) ? i : target;
> + if ((unsigned int)i < nr_cpumask_bits) {
> + target = i;
> + goto select_smt_priority;
> + }
> + return target;
> }
> }
>
> @@ -9061,14 +9099,18 @@ static int select_idle_sibling(struct task_struct *p, int prev, int target)
>
> if (!has_idle_core && cpus_share_cache(prev, target)) {
> i = select_idle_smt(p, sd, prev);
> - if ((unsigned int)i < nr_cpumask_bits)
> - return i;
> + if ((unsigned int)i < nr_cpumask_bits) {
> + target = i;
> + goto select_smt_priority;
> + }
> }
> }
>
> i = select_idle_cpu(p, sd, has_idle_core, target);
> - if ((unsigned)i < nr_cpumask_bits)
> - return i;
> + if ((unsigned int)i < nr_cpumask_bits) {
> + target = i;
> + goto select_smt_priority;
> + }
>
> /*
> * For cluster machines which have lower sharing cache like L2 or
> @@ -9076,12 +9118,19 @@ static int select_idle_sibling(struct task_struct *p, int prev, int target)
> * first. But prev_cpu or recent_used_cpu may also be a good candidate,
> * use them if possible when no idle CPU found in select_idle_cpu().
> */
> - if ((unsigned int)prev_aff < nr_cpumask_bits)
> - return prev_aff;
> - if ((unsigned int)recent_used_cpu < nr_cpumask_bits)
> - return recent_used_cpu;
> + if ((unsigned int)prev_aff < nr_cpumask_bits) {
> + target = prev_aff;
> + goto select_smt_priority;
> + }
> + if ((unsigned int)recent_used_cpu < nr_cpumask_bits) {
> + target = recent_used_cpu;
> + goto select_smt_priority;
> + }
>
> return target;
> +
> +select_smt_priority:
> + return select_idle_smt_cpu(p, target);
> }
>
> /**
> @@ -9758,8 +9807,10 @@ select_task_rq_fair(struct task_struct *p, int prev_cpu, int wake_flags)
> }
>
> /* Slow path */
> - if (unlikely(sd))
> - return sched_balance_find_dst_cpu(sd, p, cpu, prev_cpu, sd_flag);
> + if (unlikely(sd)) {
> + new_cpu = sched_balance_find_dst_cpu(sd, p, cpu, prev_cpu, sd_flag);
> + return select_idle_smt_cpu(p, new_cpu);
> + }
>
> /* Fast path */
> if (wake_flags & WF_TTWU)
> --
> 2.55.0
>