Re: [PATCH] sched/fair: Let sync wakeups target the waker's core

Madadi Vineeth Reddy <[email protected]>
Newsgroups org.kernel.vger.linux-kernel
Message-ID <[email protected]>
On 05/08/26 09:00, K Prateek Nayak wrote:
> Hello Vineeth,
> 
> On 8/4/2026 5:43 PM, Madadi Vineeth Reddy wrote:
>> Hello Prateek,
>>
>> On 04/08/26 10:19, K Prateek Nayak wrote:
>>> Hello Vineeth,
>>>
>>> On 8/1/2026 9:25 AM, Madadi Vineeth Reddy wrote:
>>>> -static int select_idle_core(struct task_struct *p, int core, struct cpumask *cpus, int *idle_cpu)
>>>> +static int select_idle_core(struct task_struct *p, int core, struct cpumask *cpus,
>>>> +				int *idle_cpu, int sync_cpu)
>>>>  {
>>>>  	bool idle = true;
>>>>  	int cpu;
>>>>  
>>>>  	for_each_cpu(cpu, cpu_smt_mask(core)) {
>>>> -		if (!available_idle_cpu(cpu)) {
>>>> +		bool sync_waker = (cpu == sync_cpu);
>>>> +
>>>> +		/*
>>>> +		 * @sync_cpu, if set, is running a waker that is about to
>>>> +		 * block with nothing else runnable behind it. Treat it as
>>>> +		 * idle so this core stays an idle-core candidate: placing
>>>> +		 * the wakee on a sibling keeps the cache sharing that
>>>> +		 * stacking on the waker's rq would get, without serialising
>>>> +		 * the wakee behind the waker's remaining work.
>>>> +		 */
>>>> +		if (!available_idle_cpu(cpu) && !sync_waker) {
>>>
>>> If I'm not wrong, all you want to make is the sync_waker appear idle and
>>> then see if you can then consider that core as idle core or not right?
>>>
>>
>> Correct.
>>
>>> Why can't this be done in select_idle_sibling() extending that early
>>> check for (!has_idle_core && cpus_share_cache(prev, target)) condition
>>> and then initializing "idle_cpu" in select_idle_cpu() accordingly?
>>>
>>> Something along the lines of:
>>>
>>>   (Only build tested)
>>>
>>> diff --git a/kernel/sched/fair.c b/kernel/sched/fair.c
>>> index df8c9c2c7918..dd62bceb3838 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);
>>>  
>>> @@ -8661,10 +8660,11 @@ static int select_idle_smt(struct task_struct *p, struct sched_domain *sd, int t
>>>   * comparing the average scan cost (tracked in sd->avg_scan_cost) against the
>>>   * average idle time for this rq (as found in rq->avg_idle).
>>>   */
>>> -static int select_idle_cpu(struct task_struct *p, struct sched_domain *sd, bool has_idle_core, int target)
>>> +static int select_idle_cpu(struct task_struct *p, struct sched_domain *sd, bool has_idle_core,
>>> +			   int target, int idle_cpu)
>>>  {
>>>  	struct cpumask *cpus = this_cpu_cpumask_var_ptr(select_rq_mask);
>>> -	int i, cpu, idle_cpu = -1, nr = INT_MAX;
>>> +	int i, cpu, nr = INT_MAX;
>>>  
>>>  	if (sched_feat(SIS_UTIL) && sd->shared) {
>>>  		/*
>>> @@ -8928,7 +8928,7 @@ 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_cpu)
>>>  {
>>>  	bool has_idle_core = false;
>>>  	struct sched_domain *sd;
>>> @@ -9028,16 +9028,19 @@ static int select_idle_sibling(struct task_struct *p, int prev, int target)
>>>  		return target;
>>>  
>>>  	if (sched_smt_active()) {
>>> +		int cpu = ((unsigned)sync_cpu < nr_cpumask_bits) ? sync_cpu : prev;
>>> +
>>>  		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)
>>> +		if (sync_cpu == target || (!has_idle_core && cpus_share_cache(prev, target))) {
>>> +			i = select_idle_smt(p, sd, cpu);
>>> +
>>> +			if (!has_idle_core && ((unsigned int)i < nr_cpumask_bits))
>>>  				return i;
>>>  		}
>>>  	}
>>>  
>>> -	i = select_idle_cpu(p, sd, has_idle_core, target);
>>> +	i = select_idle_cpu(p, sd, has_idle_core, target, i);
>>
>> `i` which is passed could be garbage value if we don't enter sched_smt_active block.
>>
>>>  	if ((unsigned)i < nr_cpumask_bits)
>>>  		return i;
>>>  
>>
>> This is different from what I wanted to achieve in a couple of ways.
>>
>> - Calling `select_idle_smt()` in sync case, would only give an idle CPU in that core but doesn't
>> test if that core is idle. That would be lost.
>> - You return `i` only when `!has_idle_core`, but I wanted to return waker core given that rest of the siblings
>> in that waker core are idle even though there are other idle cores present in the LLC.
>>
>> I agree that this could be done in `select_idle_sibling` but with a helper function.
>> Something like below (build tested)
>>
>> diff --git a/kernel/sched/fair.c b/kernel/sched/fair.c
>> index df8c9c2c7918..448af3c4b183 100644
>> --- a/kernel/sched/fair.c
>> +++ b/kernel/sched/fair.c
>> @@ -1301,7 +1301,7 @@ 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 int select_idle_sibling(struct task_struct *p, int prev_cpu, int cpu, bool sync_core);
> 
> Is this forward declaration necessary? I think you can remove it
> entirely.

Right. This can be removed as definition is above the only caller.

> 
>>  static unsigned long task_h_load(struct task_struct *p);
>>  static unsigned long capacity_of(int cpu);
>>  
>> @@ -8656,6 +8656,27 @@ static int select_idle_smt(struct task_struct *p, struct sched_domain *sd, int t
>>  	return -1;
>>  }
>>  
>> +static int select_idle_sync_core(struct task_struct *p, struct sched_domain *sd,
>> +				 int target)
>> +{
>> +	int cpu, idle_sibling = -1;
>> +
>> +	for_each_cpu(cpu, cpu_smt_mask(target)) {
>> +		if (cpu == target)
>> +			continue;
>> +
>> +		if (!available_idle_cpu(cpu))
>> +			return -1;
> 
> Reads a lot like select_idle_smt(). Perhaps you can pass "has_idle_core"
> hint to the same and return idle CPU early if !has_idle_core or otherwise
> return the first idle sibling if core is idle.
> 
> That way even sync_core case is handled by the same function. You can
> gate the call with (sync_core || !has_idle_core)

Agreed. When has_idle_core is clear the LLC scan is not going to find a
fully idle core anyway, so an idle sibling on the waker's core is at least
as good.

> 
>> +
>> +		if (idle_sibling == -1 &&
>> +		    cpumask_test_cpu(cpu, sched_domain_span(sd)) &&
>> +		    cpumask_test_cpu(cpu, p->cpus_ptr))
>> +			idle_sibling = cpu;
>> +	}
>> +
>> +	return idle_sibling;
>> +}
>> +
>>  /*
>>   * Scan the LLC domain for idle CPUs; this is dynamically regulated by
>>   * comparing the average scan cost (tracked in sd->avg_scan_cost) against the
>> @@ -8928,7 +8949,7 @@ 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, bool sync_core)
>>  {
>>  	bool has_idle_core = false;
>>  	struct sched_domain *sd;
>> @@ -9035,6 +9056,12 @@ static int select_idle_sibling(struct task_struct *p, int prev, int target)
>>  			if ((unsigned int)i < nr_cpumask_bits)
>>  				return i;
>>  		}
>> +
>> +		if (sync_core) {
>> +			i = select_idle_sync_core(p, sd, target);
>> +			if ((unsigned int)i < nr_cpumask_bits)
>> +				return i;
>> +		}
>>  	}
>>  
>>  	i = select_idle_cpu(p, sd, has_idle_core, target);
>> @@ -9733,8 +9760,16 @@ select_task_rq_fair(struct task_struct *p, int prev_cpu, int wake_flags)
>>  		return sched_balance_find_dst_cpu(sd, p, cpu, prev_cpu, sd_flag);
>>  
>>  	/* Fast path */
>> -	if (wake_flags & WF_TTWU)
>> -		return select_idle_sibling(p, prev_cpu, new_cpu);
>> +	if (wake_flags & WF_TTWU) {
>> +		bool sync_core = false;
>> +		if (want_affine && sync && new_cpu == cpu) {
>> +			struct rq *rq = cpu_rq(cpu);
>> +
>> +			sync_core = (rq->nr_running - cfs_h_nr_delayed(rq)) == 1;
> 
> Instead of computing this twice on wake_affine path, you can have a task
> flag like sched_task_hot that you just set in wake_affine_idle() before
> it returns early from the sync branch.
> 
> You can clear it up top in select_task_rq_fair() and check it
> select_idle_sibling() to conditionally call select_idle_sync_core().
> 

Fair point, will include this in the next version.

>> +		}
>> +
>> +		return select_idle_sibling(p, prev_cpu, new_cpu, sync_core);
>> +	}
>>  
>>  	return new_cpu;
>>  }
>>
>> This should also solve the issue raised by Zhan Xusheng and first target waker core given
>> that it is idle by giving exception to waker cpu.
>>
>> Thoughts?
> 
> Let me give it a spin. I'll report back if I see anything unexpected.

Thank you. Will post a new version with these set of changes and include numbers from Power10
box.

Thanks,
Vineeth

>
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.