mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
From: Madadi Vineeth Reddy <vineethr@linux.ibm.com>
To: K Prateek Nayak <kprateek.nayak@amd.com>
Cc: "Shubhang Kaushik (Ampere)" <sh@gentwo.org>,
	Ingo Molnar <mingo@redhat.com>,
	Peter Zijlstra <peterz@infradead.org>,
	Juri Lelli <juri.lelli@redhat.com>,
	Vincent Guittot <vincent.guittot@linaro.org>,
	Dietmar Eggemann <dietmar.eggemann@arm.com>,
	Steven Rostedt <rostedt@goodmis.org>,
	Ben Segall <bsegall@google.com>, Mel Gorman <mgorman@suse.de>,
	Valentin Schneider <vschneid@redhat.com>,
	Christian Loehle <christian.loehle@arm.com>,
	"Christoph Lameter (Ampere)" <cl@gentwo.org>,
	Shubhang Kaushik <shubhang@os.amperecomputing.com>,
	linux-kernel@vger.kernel.org,
	Madadi Vineeth Reddy <vineethr@linux.ibm.com>
Subject: Re: [PATCH v3] sched/fair: Prefer waker CPU for non-SMT reciprocal sync wakeups
Date: Sat, 1 Aug 2026 09:33:27 +0530	[thread overview]
Message-ID: <c67a04d6-ff1b-47d3-95ff-52bb8c291811@linux.ibm.com> (raw)
In-Reply-To: <f3d5530f-3811-42af-8c34-c40cf314deed@amd.com>

Hi Prateek,

On 30/07/26 11:57, K Prateek Nayak wrote:
> Hello Shubhang,
> 
> On 7/28/2026 5:28 AM, Shubhang Kaushik (Ampere) wrote:
>> Pipe-style ping-pong workloads can be dominated by handoff cost. In
>> such cases, placing the wakee on an idle CPU can be slower than keeping
>> the pair on the same runqueue.
>>
>> Use the existing last_wakee and wake_wide() state to identify narrow
>> reciprocal WF_SYNC wakeups:
>>
>> A wakes B
>> B wakes A
>> A wakes B
>> ...
>>
>> When the wake-affine domain allows SD_WAKE_AFFINE, prefer the waker CPU
>> for these narrow reciprocal handoffs on non-SMT systems. Do so only when
>> the waker CPU has no other runnable fair task and the wakee fits there on
>> asymmetric-capacity systems.
>>
>> SMT systems, and wakeups that do not match this pattern, continue through
>> the existing wake_affine() and select_idle_sibling() path.
>>
>> Signed-off-by: Shubhang Kaushik (Ampere) <sh@gentwo.org>
>> ---
>> Tested on 80-core non-SMT Ampere Altra: perf bench sched pipe -l 1000000
>> improved by about 30%, averaged over 40 runs. Hackbench, schbench and
>> SPECjBB showed no material regression.
>>
>> Baseline: v7.2-rc5
>> ---
>> Changes in v3:
>>   - Limit the direct waker-CPU preference to !sched_smt_active(); SMT
>>     systems continue through the existing wake_affine() and
>>     select_idle_sibling() path.
> 
> Building on top of Chris' suggestion on v2 for systems with SMT, we can
> push that check further down into select_idle_sibling() and can take a
> call at the point where we know what test_idle_core() returns.
> 
> This is what I tried out on top of tip:sched/core:
> 
>   (Lightly tested on a SMT-2 system)
> 
> diff --git a/kernel/sched/fair.c b/kernel/sched/fair.c
> index df8c9c2c7918..5821cbd930ae 100644
> --- a/kernel/sched/fair.c
> +++ b/kernel/sched/fair.c
> @@ -1301,7 +1301,6 @@ static bool update_deadline(struct cfs_rq *cfs_rq, struct sched_entity *se)
>  
>  #include "pelt.h"
>  
> -static int select_idle_sibling(struct task_struct *p, int prev_cpu, int cpu);
>  static unsigned long task_h_load(struct task_struct *p);
>  static unsigned long capacity_of(int cpu);
>  
> @@ -8636,7 +8635,7 @@ static int select_idle_core(struct task_struct *p, int core, struct cpumask *cpu
>  /*
>   * Scan the local SMT mask for idle CPUs.
>   */
> -static int select_idle_smt(struct task_struct *p, struct sched_domain *sd, int target)
> +static int select_idle_smt(struct task_struct *p, struct root_domain *rd, int target)
>  {
>  	int cpu;
>  
> @@ -8644,10 +8643,13 @@ static int select_idle_smt(struct task_struct *p, struct sched_domain *sd, int t
>  		if (cpu == target)
>  			continue;
>  		/*
> -		 * Check if the CPU is in the LLC scheduling domain of @target.
> -		 * Due to isolcpus, there is no guarantee that all the siblings are in the domain.
> +		 * Check if the CPU is in the scheduling domain of @target.
> +		 * Due to isolcpus, there is no guarantee that all the
> +		 * siblings are in the domain.
>  		 */
> -		if (!cpumask_test_cpu(cpu, sched_domain_span(sd)))
> +		if (!cpumask_test_cpu(cpu, rd->span))
> +			continue;
> +		if (sched_asym_cpucap_active() && !task_fits_cpu(p, cpu))
>  			continue;
>  		if (choose_idle_cpu(cpu, p))
>  			return cpu;
> @@ -8928,12 +8930,12 @@ static inline bool asym_fits_cpu(unsigned long util,
>  /*
>   * Try and locate an idle core/thread in the LLC cache domain.
>   */
> -static int select_idle_sibling(struct task_struct *p, int prev, int target)
> +static int select_idle_sibling(struct task_struct *p, int prev, int target, int sync)
>  {
>  	bool has_idle_core = false;
>  	struct sched_domain *sd;
>  	unsigned long task_util, util_min, util_max;
> -	int i, recent_used_cpu, prev_aff = -1;
> +	int i, this_cpu, recent_used_cpu, prev_aff = -1;
>  
>  	/*
>  	 * On asymmetric system, update task utilization because we will check
> @@ -8977,9 +8979,10 @@ static int select_idle_sibling(struct task_struct *p, int prev, int target)
>  	 * essentially a sync wakeup. An obvious example of this
>  	 * pattern is IO completions.
>  	 */
> +	this_cpu = smp_processor_id();
>  	if (is_per_cpu_kthread(current) &&
>  	    in_task() &&
> -	    prev == smp_processor_id() &&
> +	    prev == this_cpu &&
>  	    this_rq()->nr_running <= 1 &&
>  	    asym_fits_cpu(task_util, util_min, util_max, prev)) {
>  		return prev;
> @@ -9003,6 +9006,32 @@ static int select_idle_sibling(struct task_struct *p, int prev, int target)
>  		recent_used_cpu = -1;
>  	}
>  
> +	has_idle_core = sched_smt_active() && test_idle_cores(target);
> +
> +	if (!has_idle_core) {
> +		struct rq *target_rq = cpu_rq(target);
> +
> +		/* Prefer an idle thread on same core where data is hot. */
> +		if (sched_smt_active() && cpus_share_cache(prev, target)) {
> +			i = select_idle_smt(p, target_rq->rd, prev);
> +			if ((unsigned int)i < nr_cpumask_bits)
> +				return i;
> +		}
> +
> +		/*
> +		 * Tasks are likely a sync wakeup pair that passed WA_IDLE.
> +		 * Prefer to temporarily stack them on the same CPU since the
> +		 * waker is likely to go away soon and there are no idle cores.
> +		 */
> +		if (sync &&
> +		    in_task() &&
> +		    target == this_cpu &&
> +		    p->last_wakee == current &&
> +		    (target_rq->nr_running - cfs_h_nr_delayed(target_rq)) <= 1 &&
> +		    asym_fits_cpu(task_util, util_min, util_max, target))
> +			return target;
> +	}
> +
>  	/*
>  	 * For asymmetric CPU capacity systems, our domain of interest is
>  	 * sd_asym_cpucapacity rather than sd_llc.
> @@ -9027,16 +9056,6 @@ static int select_idle_sibling(struct task_struct *p, int prev, int target)
>  	if (!sd)
>  		return target;
>  
> -	if (sched_smt_active()) {
> -		has_idle_core = test_idle_cores(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;
> -		}
> -	}
> -
>  	i = select_idle_cpu(p, sd, has_idle_core, target);
>  	if ((unsigned)i < nr_cpumask_bits)
>  		return i;
> @@ -9734,7 +9753,7 @@ 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);
> +		return select_idle_sibling(p, prev_cpu, new_cpu, sync);
>  
>  	return new_cpu;
>  }

I have been looking at the same problem from the SMT side which I mentioned in v2
of this patch:
https://lore.kernel.org/all/60a584c5-25ac-4077-a725-a2f9ee74318d@linux.ibm.com/

Posted a patch for it today:
https://lore.kernel.org/lkml/20260801035532.260625-1-vineethr@linux.ibm.com/

It lets the waker's CPU count as idle inside select_idle_core(), so the
waker's core stays an idle-core candidate and the wakee lands on one of
its sibling threads. On a sync wakeup the waker's core already holds the
data, so this keeps the cache sharing.

Thanks,
Vineeth

> ---
> 
> I'm currently seeing a ~10% improvement for the workload you mentioned
> (perf bench sched pipe -l 1000000) on average. I haven't tried anything
> else yet but would love to know your thoughts.
> 
> I'm using rq->rd->span to know the CPUs covered by the cpuset instead of
> sched_domain_span(sd_llc) in select_idle_smt() to make it work for
> sched_asym_cpucap_active() + sched_smt_active() where some cores may
> have more than one CPUs and the LLC is defined at core boundary.
> 
> Basically I wanted to avoid this ugly:
> 
>     sd = rcu_dereference_all(per_cpu((sched_asym_cpucap_active()) ? sd_asym : sd_llc, target));
> 
>     if (!sd)
>         goto skip;
> 
> pattern and rq->rd->span seemed just fine since it doesn't need a null
> check and gives the desired boundary.
> 
> Could you please check if the improvements still persist on your system
> with the check pushed down into select_idle_sibling(). Thank you.
> 
> 
>>   - Drop the redundant affinity check; want_affine already verifies the
>>     waker CPU is allowed.
>>   - Use a plain p->last_wakee read instead of READ_ONCE().
>>   - Rebase and refresh testing on v7.2-rc5.
>>
>> Link to v2: https://lore.kernel.org/r/20260722-b4-sched-sync-wakeup-v2-1-f1164560b24b@gentwo.org
>>
>> Changes in v2:
>>   - Move the reciprocal handoff preference under the existing
>>     SD_WAKE_AFFINE domain check.
>>   - Drop futex from the changelog motivation.
>>   - Refresh perf bench sched pipe results after rebasing.
>>
>> Link to v1: https://lore.kernel.org/r/20260721-b4-sched-sync-wakeup-v1-1-dc94f184e27f@gentwo.org
>> ---
>>  kernel/sched/fair.c | 25 +++++++++++++++++++++++++
>>  1 file changed, 25 insertions(+)
>>
>> diff --git a/kernel/sched/fair.c b/kernel/sched/fair.c
>> index d78467ec6ee1343050fcc2794dafb38ade3599e5..e61062d20da772d29da6f5f377a150b4b5128619 100644
>> --- a/kernel/sched/fair.c
>> +++ b/kernel/sched/fair.c
>> @@ -8794,6 +8794,26 @@ static inline bool asym_fits_cpu(unsigned long util,
>>  	return true;
>>  }
>>  
>> +/*
>> + * For reciprocal WF_SYNC handoffs, prefer the waker CPU when it has no
>> + * other runnable fair task.
>> + */
>> +static bool prefer_sync_pair_cpu(struct task_struct *p, int cpu)
>> +{
>> +	struct rq *rq = cpu_rq(cpu);
>> +
>> +	if ((rq->nr_running - cfs_h_nr_delayed(rq)) != 1)
>> +		return false;
>> +
>> +	if (sched_asym_cpucap_active()) {
>> +		sync_entity_load_avg(&p->se);
>> +		if (!task_fits_cpu(p, cpu))
>> +			return false;
>> +	}
>> +
>> +	return true;
>> +}
>> +
>>  /*
>>   * Try and locate an idle core/thread in the LLC cache domain.
>>   */
>> @@ -9579,6 +9599,11 @@ select_task_rq_fair(struct task_struct *p, int prev_cpu, int wake_flags)
>>  		 */
>>  		if (want_affine && (tmp->flags & SD_WAKE_AFFINE) &&
>>  		    cpumask_test_cpu(prev_cpu, sched_domain_span(tmp))) {
>> +			if (sync && !sched_smt_active() &&
> 
> For the record, without !sched_smt_active(), the runtime for
> "perf bench sched pipe -l 1000000" almost doubles in my case but
> looks like that condition might overall be good with a bunch of
> defensive checks on SMT systems too.
> 
>> +			    p->last_wakee == current &&
>> +			    prefer_sync_pair_cpu(p, cpu))
>> +				return cpu;
>> +
>>  			if (cpu != prev_cpu)
>>  				new_cpu = wake_affine(tmp, p, cpu, prev_cpu, sync);
>>  
>>
>> ---
>> base-commit: f5098b6bae761e346ebcd9da7f95622c04733cff
>> change-id: 20260721-b4-sched-sync-wakeup-04d40cbeb1da
>>
>> Best regards,
> 


  parent reply	other threads:[~2026-08-01  4:04 UTC|newest]

Thread overview: 6+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-07-27 23:58 Shubhang Kaushik (Ampere)
2026-07-30  6:27 ` K Prateek Nayak
2026-07-31  7:22   ` Shubhang
2026-08-01  4:03   ` Madadi Vineeth Reddy [this message]
2026-08-03 14:05 ` Shrikanth Hegde
2026-08-04  0:10   ` Shubhang

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=c67a04d6-ff1b-47d3-95ff-52bb8c291811@linux.ibm.com \
    --to=vineethr@linux.ibm.com \
    --cc=bsegall@google.com \
    --cc=christian.loehle@arm.com \
    --cc=cl@gentwo.org \
    --cc=dietmar.eggemann@arm.com \
    --cc=juri.lelli@redhat.com \
    --cc=kprateek.nayak@amd.com \
    --cc=linux-kernel@vger.kernel.org \
    --cc=mgorman@suse.de \
    --cc=mingo@redhat.com \
    --cc=peterz@infradead.org \
    --cc=rostedt@goodmis.org \
    --cc=sh@gentwo.org \
    --cc=shubhang@os.amperecomputing.com \
    --cc=vincent.guittot@linaro.org \
    --cc=vschneid@redhat.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
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®