summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChen Yu <yu.c.chen@intel.com>2026-05-13 23:39:13 +0300
committerPeter Zijlstra <peterz@infradead.org>2026-05-18 22:33:14 +0300
commitdeee5e27d5b608323c04dc99979e55f944016a13 (patch)
tree6bdd229d92be85a9ce8947b036ec3bee11113bf3
parenta2b4cf39d9d333bfeb9262dbaafe3d24d405a5c0 (diff)
downloadlinux-deee5e27d5b608323c04dc99979e55f944016a13.tar.xz
sched/cache: Disable cache aware scheduling for processes with high thread counts
A performance regression was observed by Prateek when running hackbench with many threads per process (high fd count). To avoid this, processes with a large number of active threads are excluded from cache-aware scheduling. With sched_cache enabled, record the number of active threads in each process during the periodic task_cache_work(). While iterating over CPUs, if the currently running task belongs to the same process as the task that launched task_cache_work(), increment the active thread count. If the number of active threads within the process exceeds the number of Cores (divided by the SMT number) in the LLC, do not enable cache-aware scheduling. However, on systems with a smaller number of CPUs within 1 LLC, like Power10/Power11 with SMT4 and an LLC size of 4, this check effectively disables cache-aware scheduling for any process. One possible solution suggested by Peter is to use an LLC-mask instead of a single LLC value for preference. Once there are a 'few' LLCs as preference, this constraint becomes a little easier. It could be an enhancement in the future. For users who wish to perform task aggregation regardless, a debugfs knob is provided for tuning in a subsequent change. Suggested-by: K Prateek Nayak <kprateek.nayak@amd.com> Suggested-by: Aaron Lu <ziqianlu@bytedance.com> Signed-off-by: Chen Yu <yu.c.chen@intel.com> Co-developed-by: Tim Chen <tim.c.chen@linux.intel.com> Signed-off-by: Tim Chen <tim.c.chen@linux.intel.com> Signed-off-by: Peter Zijlstra (Intel) <peterz@infradead.org> Tested-by: Tingyin Duan <tingyin.duan@gmail.com> Link: https://patch.msgid.link/d076cd21a8e6c6341d1e2d927e118db770ebb650.1778703694.git.tim.c.chen@linux.intel.com
-rw-r--r--include/linux/sched.h1
-rw-r--r--kernel/sched/fair.c48
2 files changed, 44 insertions, 5 deletions
diff --git a/include/linux/sched.h b/include/linux/sched.h
index 6d883f109ba3..6701911eaaf7 100644
--- a/include/linux/sched.h
+++ b/include/linux/sched.h
@@ -2423,6 +2423,7 @@ struct sched_cache_stat {
struct sched_cache_time __percpu *pcpu_sched;
raw_spinlock_t lock;
unsigned long epoch;
+ u64 nr_running_avg;
unsigned long next_scan;
int cpu;
} ____cacheline_aligned_in_smp;
diff --git a/kernel/sched/fair.c b/kernel/sched/fair.c
index a759ea669d74..808f614fc2d2 100644
--- a/kernel/sched/fair.c
+++ b/kernel/sched/fair.c
@@ -1384,6 +1384,12 @@ static int llc_id(int cpu)
return per_cpu(sd_llc_id, cpu);
}
+static bool invalid_llc_nr(struct mm_struct *mm, int cpu)
+{
+ return !fits_capacity((mm->sc_stat.nr_running_avg * cpu_smt_num_threads),
+ per_cpu(sd_llc_size, cpu));
+}
+
static void account_llc_enqueue(struct rq *rq, struct task_struct *p)
{
struct sched_domain *sd;
@@ -1452,7 +1458,7 @@ void mm_init_sched(struct mm_struct *mm,
mm->sc_stat.epoch = epoch;
mm->sc_stat.cpu = -1;
mm->sc_stat.next_scan = jiffies;
-
+ mm->sc_stat.nr_running_avg = 0;
/*
* The update to mm->sc_stat should not be reordered
* before initialization to mm's other fields, in case
@@ -1574,7 +1580,8 @@ void account_mm_sched(struct rq *rq, struct task_struct *p, s64 delta_exec)
* If this process hasn't hit task_cache_work() for a while invalidate
* its preferred state.
*/
- if (epoch - READ_ONCE(mm->sc_stat.epoch) > EPOCH_LLC_AFFINITY_TIMEOUT) {
+ if (epoch - READ_ONCE(mm->sc_stat.epoch) > EPOCH_LLC_AFFINITY_TIMEOUT ||
+ invalid_llc_nr(mm, cpu_of(rq))) {
if (mm->sc_stat.cpu != -1)
mm->sc_stat.cpu = -1;
}
@@ -1660,14 +1667,32 @@ out:
cpumask_copy(cpus, cpu_online_mask);
}
+static inline void update_avg_scale(u64 *avg, u64 sample)
+{
+ int factor = per_cpu(sd_llc_size, raw_smp_processor_id());
+ s64 diff = sample - *avg;
+ u32 divisor;
+
+ /*
+ * Scale the divisor based on the number of CPUs contained
+ * in the LLC. This scaling ensures smaller LLC domains use
+ * a smaller divisor to achieve more precise sensitivity to
+ * changes in nr_running, while larger LLC domains are capped
+ * at a maximum divisor of 8 which is the default smoothing
+ * factor of EWMA in update_avg().
+ */
+ divisor = clamp_t(u32, (factor >> 2), 2, 8);
+ *avg += div64_s64(diff, divisor);
+}
+
static void task_cache_work(struct callback_head *work)
{
unsigned long next_scan, now = jiffies;
- struct task_struct *p = current;
+ struct task_struct *p = current, *cur;
+ int cpu, m_a_cpu = -1, nr_running = 0;
+ unsigned long curr_m_a_occ = 0;
struct mm_struct *mm = p->mm;
unsigned long m_a_occ = 0;
- unsigned long curr_m_a_occ = 0;
- int cpu, m_a_cpu = -1;
cpumask_var_t cpus;
WARN_ON_ONCE(work != &p->cache_work);
@@ -1711,6 +1736,11 @@ static void task_cache_work(struct callback_head *work)
m_occ = occ;
m_cpu = i;
}
+
+ cur = rcu_dereference_all(cpu_rq(i)->curr);
+ if (cur && !(cur->flags & (PF_EXITING | PF_KTHREAD)) &&
+ cur->mm == mm)
+ nr_running++;
}
/*
@@ -1754,6 +1784,7 @@ static void task_cache_work(struct callback_head *work)
mm->sc_stat.cpu = m_a_cpu;
}
+ update_avg_scale(&mm->sc_stat.nr_running_avg, nr_running);
free_cpumask_var(cpus);
}
@@ -10294,6 +10325,13 @@ static enum llc_mig can_migrate_llc_task(int src_cpu, int dst_cpu,
if (cpu < 0 || cpus_share_cache(src_cpu, dst_cpu))
return mig_unrestricted;
+ /* skip cache aware load balance for too many threads */
+ if (invalid_llc_nr(mm, dst_cpu)) {
+ if (mm->sc_stat.cpu != -1)
+ mm->sc_stat.cpu = -1;
+ return mig_unrestricted;
+ }
+
if (cpus_share_cache(dst_cpu, cpu))
to_pref = true;
else if (cpus_share_cache(src_cpu, cpu))