mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* [RFC PATCH v3 0/4] sched/fair: introduce new scheduler group type group_parked
@ 2025-05-12 11:53 Tobias Huschle
  2025-05-12 11:53 ` [RFC PATCH v3 1/4] " Tobias Huschle
                   ` (3 more replies)
  0 siblings, 4 replies; 6+ messages in thread
From: Tobias Huschle @ 2025-05-12 11:53 UTC (permalink / raw)
  To: linux-kernel
  Cc: mingo, peterz, juri.lelli, vincent.guittot, dietmar.eggemann,
	rostedt, bsegall, mgorman, vschneid, sshegde

Introduces parked_cpu concept. When a CPU is marked as parked, 
it is expected that nothing meaningful runs on it. To achieve 
this use a new group type called group_parked in the load 
balance path.

See cover letter of v2 for extensive description.

Adding an usecase and performance metrics:

The core goal is to allow Linux systems running as guests under a
hypervisor to run more efficiently. Virtualization usually implies
that the total amount of virtual CPUs creates an overcommitment on
the actually existing physical CPUs on the host.

==== Scenario and workload ==========================================

Therefore, the following scenario is used:
- KVM host with 10 cores, SMT 2, yielding 20 CPUs
- 8 KVM guests with 6 vCPUs each, no SMT, yielding 48 vCPUs in total

The following workload is used:
The guests communicate pair-wise with one another via distinct Linux
bridges, so we get 4 bridges, with 2 guests connected respectively.
Each pair runs an uperf benchmark with one guest sending 200 bytes to
the other guest and receiving 30000 bytes on return. This is done
by 50 parallel workers per pair. All running simulatinously for 400s.

==== Comparison 1 ===================================================

1. no guest vCPUs are parked
   This implies that 48 vCPUs are used, overcommiting the 20 actually
   available host CPUs.
2. guest vCPUs 2-5 are parked, means only 0-1 are used
   In this case, only 16 vCPUs are explicitly used, leaving 4 host
   CPUs available for virtualization overhead

Results:
Setup 2 provides a throughput improvement of ~24%.

==== Comparison 2 ===================================================

In addition to the uperf workload, each guest now runs 2 stress-ng
workers: stress-ng --cpu 2 --cpu-load 99 --cpu-method matrixprod
These 2 stress-ng workers are meant to consume the full CPU
entitlement of each guest.

Results:
Setup 2 provides a throughput improvement of ~50%. 

As an additional metric, the bogo/ops reported by stress-ng can be
noted where setup 1 outperforms setup 2 by 23%, which is expected, 
as stress-ng will pick up all available computation power left 
untouched by uperf. So, a better performing uperf consumes more
CPU runtime, taking it away from stress-ng.

This yields a trade-off between improving interrupt/lock-using
workload like uperf and penalizing purely CPU focussed workload
like stress-ng. With a 50% improvement on the uperf side, but only
23% regression on the stress-ng side, it is probably possible to
find a sweet spot.

==== Notes ==========================================================

This is of course only an initial sniff test. Additional
configurations will need to be tested, but the initial runs look
promising, in the sense that it is possible to find performance 
improvements by passing information about the availability of CPU 
resources in the host. 
In the presented runs, all values where set statically and not
dynamically modified.

The number of usable CPU resources should be seen as a part of the
topology that the system is perceiving from the underlying layer.
The host running on that underlying layer has to determine, that 
certain CPUs are currently not usable for the guest. 
The guest would receive this information through architecture
specific means, as it should perceive, that it is interacting with
actual hardware rathen than being virtualized.

In the case where the host observes that all of its resources are
consumed by the guests, it can pass the necessary information such
that the guests can start parking CPUs. If the host observes, that
the overall pressure on the resources is relieved, it can instruct
the guests that it is safe to unpark CPUs again.

==== Open questions =================================================

There are a couple of issues and corner cases which need further
considerations:
- dl:         Deadline scheduling is not covered yet. There is
              probably only little overlap in systems that would make
              use of parked CPUs and systems running deadline 
              scheduling.
- ext:        Probably affected as well. Needs some conceptional
              thoughts first.
- raciness:   Right now, there are no synchronization efforts. It needs
              to be considered whether those might be necessary or if
              it is alright that the parked-state of a CPU might change
              during load-balancing.
- taskset:    If a task is pinned to CPUs that are all parked, the 
              pinning is discarded (similar to CPU hotplug). Thoughts
              need to be spent on how to properly notify the user.
- reporting:  Tools like lsdasd and debugfs should represent the parked 
              state of CPUs.
- interrupts: Interrupts should be disabled on parked CPUs as well,
              most likely responsibility of an implementing arch.

=====================================================================

Changes to v2
- provide usecase and performance measurements
- add support for realtime scheduler
  The adjustments work fine for all kinds of real time threads.
  Only those which are running at 100% CPU utilization are never
  interrupted and therefore never rescheduled. This is an
  limitation for now although scenarios that would profit from
  having parked CPUs would probably not run such uninterrupted
  real time processes anyway.
- use h_nr_queued instead of nr_running
- remove unnecessary arch_cpu_parked check
- do not touch idle load balancer, it seems to be unnecessary to 
  explicitly run it, the idea could be reconsidered later

Patches apply to tip:sched/core

The s390 patch serves as a simplified implementation example.
Tobias Huschle (4):
  sched/fair: introduce new scheduler group type group_parked
  sched/rt: add support for parked CPUs
  sched/fair: adapt scheduler group weight and capacity for parked CPUs
  s390/topology: Add initial implementation for selection of parked CPUs

 arch/s390/include/asm/smp.h    |  2 +
 arch/s390/kernel/smp.c         |  5 ++
 include/linux/sched/topology.h | 19 +++++++
 kernel/sched/core.c            | 13 ++++-
 kernel/sched/fair.c            | 95 +++++++++++++++++++++++++++++-----
 kernel/sched/rt.c              | 25 +++++++--
 kernel/sched/syscalls.c        |  3 ++
 7 files changed, 142 insertions(+), 20 deletions(-)

-- 
2.34.1


^ permalink raw reply	[flat|nested] 6+ messages in thread

* [RFC PATCH v3 1/4] sched/fair: introduce new scheduler group type group_parked
  2025-05-12 11:53 [RFC PATCH v3 0/4] sched/fair: introduce new scheduler group type group_parked Tobias Huschle
@ 2025-05-12 11:53 ` Tobias Huschle
  2025-05-12 11:53 ` [RFC PATCH v3 2/4] sched/rt: add support for parked CPUs Tobias Huschle
                   ` (2 subsequent siblings)
  3 siblings, 0 replies; 6+ messages in thread
From: Tobias Huschle @ 2025-05-12 11:53 UTC (permalink / raw)
  To: linux-kernel
  Cc: mingo, peterz, juri.lelli, vincent.guittot, dietmar.eggemann,
	rostedt, bsegall, mgorman, vschneid, sshegde

A parked CPU is considered to be flagged as unsuitable to process
workload at the moment, but might be become usable anytime. Depending on
the necessity for additional computation power and/or available capacity
of the underlying hardware.

A scheduler group is considered to be parked, if there are tasks queued
on parked CPUs and there are no idle CPUs, i.e. all non parked CPUs are
busy or there are only parked CPUs. A scheduler group with parked tasks
can be considered to not be parked, if it has idle CPUs which can pick
up the parked tasks. A parked scheduler group is considered to be busier
than another if it runs more tasks on parked CPUs than another parked
scheduler group.

A parked CPU must keep its scheduler tick (or have it re-enabled if
necessary) in order to make sure that parked CPUs which only run a
single task which does not give up its runtime voluntarily is still
evacuated as it would otherwise go into NO_HZ.

The status of the underlying hardware must be considered to be
architecture dependent. Therefore the check whether a CPU is parked is
architecture specific. For architectures not relying on this feature,
the check is mostly a NOP.

This is more efficient and non-disruptive compared to CPU hotplug in
environments where such changes can be necessary on a frequent basis.

Signed-off-by: Tobias Huschle <huschle@linux.ibm.com>
---
 include/linux/sched/topology.h | 19 +++++++++
 kernel/sched/core.c            | 13 +++++-
 kernel/sched/fair.c            | 77 +++++++++++++++++++++++++++++-----
 kernel/sched/syscalls.c        |  3 ++
 4 files changed, 101 insertions(+), 11 deletions(-)

diff --git a/include/linux/sched/topology.h b/include/linux/sched/topology.h
index 7b4301b7235f..6baf51d45e85 100644
--- a/include/linux/sched/topology.h
+++ b/include/linux/sched/topology.h
@@ -251,6 +251,25 @@ unsigned long arch_scale_cpu_capacity(int cpu)
 }
 #endif
 
+#ifndef arch_cpu_parked
+/**
+ * arch_cpu_parked - Check if a given CPU is currently parked.
+ *
+ * A parked CPU cannot run any kind of workload since underlying
+ * physical CPU should not be used at the moment .
+ *
+ * @cpu: the CPU in question.
+ *
+ * By default assume CPU is not parked
+ *
+ * Return: Parked state of CPU
+ */
+static __always_inline bool arch_cpu_parked(int cpu)
+{
+	return false;
+}
+#endif
+
 #ifndef arch_scale_hw_pressure
 static __always_inline
 unsigned long arch_scale_hw_pressure(int cpu)
diff --git a/kernel/sched/core.c b/kernel/sched/core.c
index c81cf642dba0..90efc322a81e 100644
--- a/kernel/sched/core.c
+++ b/kernel/sched/core.c
@@ -1358,6 +1358,9 @@ bool sched_can_stop_tick(struct rq *rq)
 	if (rq->cfs.h_nr_queued > 1)
 		return false;
 
+	if (rq->cfs.h_nr_queued > 0 && arch_cpu_parked(cpu_of(rq)))
+		return false;
+
 	/*
 	 * If there is one task and it has CFS runtime bandwidth constraints
 	 * and it's on the cpu now we don't want to stop the tick.
@@ -2449,7 +2452,7 @@ static inline bool is_cpu_allowed(struct task_struct *p, int cpu)
 
 	/* Non kernel threads are not allowed during either online or offline. */
 	if (!(p->flags & PF_KTHREAD))
-		return cpu_active(cpu);
+		return !arch_cpu_parked(cpu) && cpu_active(cpu);
 
 	/* KTHREAD_IS_PER_CPU is always allowed. */
 	if (kthread_is_per_cpu(p))
@@ -2459,6 +2462,10 @@ static inline bool is_cpu_allowed(struct task_struct *p, int cpu)
 	if (cpu_dying(cpu))
 		return false;
 
+	/* CPU should be avoided at the moment */
+	if (arch_cpu_parked(cpu))
+		return false;
+
 	/* But are allowed during online. */
 	return cpu_online(cpu);
 }
@@ -3929,6 +3936,10 @@ static inline bool ttwu_queue_cond(struct task_struct *p, int cpu)
 	if (!scx_allow_ttwu_queue(p))
 		return false;
 
+	/* The task should not be queued onto a parked CPU. */
+	if (arch_cpu_parked(cpu))
+		return false;
+
 	/*
 	 * Do not complicate things with the async wake_list while the CPU is
 	 * in hotplug state.
diff --git a/kernel/sched/fair.c b/kernel/sched/fair.c
index 0fb9bf995a47..ee8ccee69774 100644
--- a/kernel/sched/fair.c
+++ b/kernel/sched/fair.c
@@ -6884,6 +6884,8 @@ static int sched_idle_rq(struct rq *rq)
 #ifdef CONFIG_SMP
 static int sched_idle_cpu(int cpu)
 {
+	if (arch_cpu_parked(cpu))
+		return 0;
 	return sched_idle_rq(cpu_rq(cpu));
 }
 #endif
@@ -7414,6 +7416,9 @@ static int wake_affine(struct sched_domain *sd, struct task_struct *p,
 {
 	int target = nr_cpumask_bits;
 
+	if (arch_cpu_parked(target))
+		return prev_cpu;
+
 	if (sched_feat(WA_IDLE))
 		target = wake_affine_idle(this_cpu, prev_cpu, sync);
 
@@ -9204,7 +9209,12 @@ enum group_type {
 	 * The CPU is overloaded and can't provide expected CPU cycles to all
 	 * tasks.
 	 */
-	group_overloaded
+	group_overloaded,
+	/*
+	 * The CPU should be avoided as it can't provide expected CPU cycles
+	 * even for small amounts of workload.
+	 */
+	group_parked
 };
 
 enum migration_type {
@@ -9923,6 +9933,7 @@ struct sg_lb_stats {
 	unsigned long group_runnable;		/* Total runnable time over the CPUs of the group */
 	unsigned int sum_nr_running;		/* Nr of all tasks running in the group */
 	unsigned int sum_h_nr_running;		/* Nr of CFS tasks running in the group */
+	unsigned int sum_nr_parked;
 	unsigned int idle_cpus;                 /* Nr of idle CPUs         in the group */
 	unsigned int group_weight;
 	enum group_type group_type;
@@ -10180,6 +10191,9 @@ group_type group_classify(unsigned int imbalance_pct,
 			  struct sched_group *group,
 			  struct sg_lb_stats *sgs)
 {
+	if (sgs->sum_nr_parked && !sgs->idle_cpus)
+		return group_parked;
+
 	if (group_is_overloaded(imbalance_pct, sgs))
 		return group_overloaded;
 
@@ -10375,6 +10389,8 @@ static inline void update_sg_lb_stats(struct lb_env *env,
 		if (cpu_overutilized(i))
 			*sg_overutilized = 1;
 
+		sgs->sum_nr_parked += arch_cpu_parked(i) * rq->cfs.h_nr_queued;
+
 		/*
 		 * No need to call idle_cpu() if nr_running is not 0
 		 */
@@ -10480,6 +10496,8 @@ static bool update_sd_pick_busiest(struct lb_env *env,
 	 */
 
 	switch (sgs->group_type) {
+	case group_parked:
+		return sgs->sum_nr_parked > busiest->sum_nr_parked;
 	case group_overloaded:
 		/* Select the overloaded group with highest avg_load. */
 		return sgs->avg_load > busiest->avg_load;
@@ -10643,6 +10661,9 @@ static int idle_cpu_without(int cpu, struct task_struct *p)
 {
 	struct rq *rq = cpu_rq(cpu);
 
+	if (arch_cpu_parked(cpu))
+		return 0;
+
 	if (rq->curr != rq->idle && rq->curr != p)
 		return 0;
 
@@ -10691,6 +10712,8 @@ static inline void update_sg_wakeup_stats(struct sched_domain *sd,
 		nr_running = rq->nr_running - local;
 		sgs->sum_nr_running += nr_running;
 
+		sgs->sum_nr_parked += arch_cpu_parked(i) * rq->cfs.h_nr_queued;
+
 		/*
 		 * No need to call idle_cpu_without() if nr_running is not 0
 		 */
@@ -10738,6 +10761,8 @@ static bool update_pick_idlest(struct sched_group *idlest,
 	 */
 
 	switch (sgs->group_type) {
+	case group_parked:
+		return false;
 	case group_overloaded:
 	case group_fully_busy:
 		/* Select the group with lowest avg_load. */
@@ -10788,7 +10813,7 @@ sched_balance_find_dst_group(struct sched_domain *sd, struct task_struct *p, int
 	unsigned long imbalance;
 	struct sg_lb_stats idlest_sgs = {
 			.avg_load = UINT_MAX,
-			.group_type = group_overloaded,
+			.group_type = group_parked,
 	};
 
 	do {
@@ -10846,6 +10871,8 @@ sched_balance_find_dst_group(struct sched_domain *sd, struct task_struct *p, int
 		return idlest;
 
 	switch (local_sgs.group_type) {
+	case group_parked:
+		return idlest;
 	case group_overloaded:
 	case group_fully_busy:
 
@@ -11097,6 +11124,12 @@ static inline void calculate_imbalance(struct lb_env *env, struct sd_lb_stats *s
 	local = &sds->local_stat;
 	busiest = &sds->busiest_stat;
 
+	if (busiest->group_type == group_parked) {
+		env->migration_type = migrate_task;
+		env->imbalance = busiest->sum_nr_parked;
+		return;
+	}
+
 	if (busiest->group_type == group_misfit_task) {
 		if (env->sd->flags & SD_ASYM_CPUCAPACITY) {
 			/* Set imbalance to allow misfit tasks to be balanced. */
@@ -11265,13 +11298,14 @@ static inline void calculate_imbalance(struct lb_env *env, struct sd_lb_stats *s
 /*
  * Decision matrix according to the local and busiest group type:
  *
- * busiest \ local has_spare fully_busy misfit asym imbalanced overloaded
- * has_spare        nr_idle   balanced   N/A    N/A  balanced   balanced
- * fully_busy       nr_idle   nr_idle    N/A    N/A  balanced   balanced
- * misfit_task      force     N/A        N/A    N/A  N/A        N/A
- * asym_packing     force     force      N/A    N/A  force      force
- * imbalanced       force     force      N/A    N/A  force      force
- * overloaded       force     force      N/A    N/A  force      avg_load
+ * busiest \ local has_spare fully_busy misfit asym imbalanced overloaded parked
+ * has_spare        nr_idle   balanced   N/A    N/A  balanced   balanced  balanced
+ * fully_busy       nr_idle   nr_idle    N/A    N/A  balanced   balanced  balanced
+ * misfit_task      force     N/A        N/A    N/A  N/A        N/A       N/A
+ * asym_packing     force     force      N/A    N/A  force      force     balanced
+ * imbalanced       force     force      N/A    N/A  force      force     balanced
+ * overloaded       force     force      N/A    N/A  force      avg_load  balanced
+ * parked           force     force      N/A    N/A  force      force     balanced
  *
  * N/A :      Not Applicable because already filtered while updating
  *            statistics.
@@ -11310,6 +11344,13 @@ static struct sched_group *sched_balance_find_src_group(struct lb_env *env)
 		goto out_balanced;
 
 	busiest = &sds.busiest_stat;
+	local = &sds.local_stat;
+
+	if (local->group_type == group_parked)
+		goto out_balanced;
+
+	if (busiest->group_type == group_parked)
+		goto force_balance;
 
 	/* Misfit tasks should be dealt with regardless of the avg load */
 	if (busiest->group_type == group_misfit_task)
@@ -11331,7 +11372,6 @@ static struct sched_group *sched_balance_find_src_group(struct lb_env *env)
 	if (busiest->group_type == group_imbalanced)
 		goto force_balance;
 
-	local = &sds.local_stat;
 	/*
 	 * If the local group is busier than the selected busiest group
 	 * don't try and pull any tasks.
@@ -11444,6 +11484,9 @@ static struct rq *sched_balance_find_src_rq(struct lb_env *env,
 		enum fbq_type rt;
 
 		rq = cpu_rq(i);
+		if (arch_cpu_parked(i) && rq->cfs.h_nr_queued)
+			return rq;
+
 		rt = fbq_classify_rq(rq);
 
 		/*
@@ -11614,6 +11657,9 @@ static int need_active_balance(struct lb_env *env)
 {
 	struct sched_domain *sd = env->sd;
 
+	if (arch_cpu_parked(env->src_cpu) && cpu_rq(env->src_cpu)->cfs.h_nr_queued)
+		return 1;
+
 	if (asym_active_balance(env))
 		return 1;
 
@@ -11647,6 +11693,14 @@ static int should_we_balance(struct lb_env *env)
 	struct sched_group *sg = env->sd->groups;
 	int cpu, idle_smt = -1;
 
+	if (arch_cpu_parked(env->dst_cpu))
+		return 0;
+
+	for_each_cpu(cpu, sched_domain_span(env->sd)) {
+		if (arch_cpu_parked(cpu) && cpu_rq(cpu)->cfs.h_nr_queued)
+			return 1;
+	}
+
 	/*
 	 * Ensure the balancing environment is consistent; can happen
 	 * when the softirq triggers 'during' hotplug.
@@ -12788,6 +12842,9 @@ static int sched_balance_newidle(struct rq *this_rq, struct rq_flags *rf)
 
 	update_misfit_status(NULL, this_rq);
 
+	if (arch_cpu_parked(this_cpu))
+		return 0;
+
 	/*
 	 * There is a task waiting to run. No need to search for one.
 	 * Return 0; the task will be enqueued when switching to idle.
diff --git a/kernel/sched/syscalls.c b/kernel/sched/syscalls.c
index c326de1344fb..4e559d8775da 100644
--- a/kernel/sched/syscalls.c
+++ b/kernel/sched/syscalls.c
@@ -214,6 +214,9 @@ int idle_cpu(int cpu)
 		return 0;
 #endif
 
+	if (arch_cpu_parked(cpu))
+		return 0;
+
 	return 1;
 }
 
-- 
2.34.1


^ permalink raw reply	[flat|nested] 6+ messages in thread

* [RFC PATCH v3 2/4] sched/rt: add support for parked CPUs
  2025-05-12 11:53 [RFC PATCH v3 0/4] sched/fair: introduce new scheduler group type group_parked Tobias Huschle
  2025-05-12 11:53 ` [RFC PATCH v3 1/4] " Tobias Huschle
@ 2025-05-12 11:53 ` Tobias Huschle
  2025-05-12 15:16   ` Tobias Huschle
  2025-05-12 11:53 ` [RFC PATCH v3 3/4] sched/fair: adapt scheduler group weight and capacity " Tobias Huschle
  2025-05-12 11:53 ` [RFC PATCH v3 4/4] s390/topology: Add initial implementation for selection of " Tobias Huschle
  3 siblings, 1 reply; 6+ messages in thread
From: Tobias Huschle @ 2025-05-12 11:53 UTC (permalink / raw)
  To: linux-kernel
  Cc: mingo, peterz, juri.lelli, vincent.guittot, dietmar.eggemann,
	rostedt, bsegall, mgorman, vschneid, sshegde

Realtime tasks must also react to the parked states of CPUs. Tasks will
be treated as if the parked CPUs have no free capacity to work on them.

A dynamic change in the parked state of CPUs is handled correctly if
realtime tasks do not consume 100% CPU time, without any interruption.
If a realtime tasks runs without interruption, it will never enter the
load balancing code and will therefore remain on a CPU, even if the CPU
becomes classified as parked. Any value below 100% causes the task to
be migrated off a CPU which has just been classified as parked.

Signed-off-by: Tobias Huschle <huschle@linux.ibm.com>
---
 kernel/sched/rt.c | 25 ++++++++++++++++++++-----
 1 file changed, 20 insertions(+), 5 deletions(-)

diff --git a/kernel/sched/rt.c b/kernel/sched/rt.c
index fa03ec3ed56a..595d760304fb 100644
--- a/kernel/sched/rt.c
+++ b/kernel/sched/rt.c
@@ -460,6 +460,9 @@ static inline bool rt_task_fits_capacity(struct task_struct *p, int cpu)
 	unsigned int max_cap;
 	unsigned int cpu_cap;
 
+	if (arch_cpu_parked(cpu))
+		return false;
+
 	/* Only heterogeneous systems can benefit from this check */
 	if (!sched_asym_cpucap_active())
 		return true;
@@ -474,6 +477,9 @@ static inline bool rt_task_fits_capacity(struct task_struct *p, int cpu)
 #else
 static inline bool rt_task_fits_capacity(struct task_struct *p, int cpu)
 {
+	if (arch_cpu_parked(cpu))
+		return false;
+
 	return true;
 }
 #endif
@@ -1799,6 +1805,8 @@ static int find_lowest_rq(struct task_struct *task)
 	int this_cpu = smp_processor_id();
 	int cpu      = task_cpu(task);
 	int ret;
+	int parked_cpu = 0;
+	int tmp_cpu;
 
 	/* Make sure the mask is initialized first */
 	if (unlikely(!lowest_mask))
@@ -1807,11 +1815,18 @@ static int find_lowest_rq(struct task_struct *task)
 	if (task->nr_cpus_allowed == 1)
 		return -1; /* No other targets possible */
 
+	for_each_cpu(tmp_cpu, cpu_online_mask) {
+		if (arch_cpu_parked(tmp_cpu)) {
+			parked_cpu = tmp_cpu;
+			break;
+		}
+	}
+
 	/*
 	 * If we're on asym system ensure we consider the different capacities
 	 * of the CPUs when searching for the lowest_mask.
 	 */
-	if (sched_asym_cpucap_active()) {
+	if (sched_asym_cpucap_active() || parked_cpu > -1) {
 
 		ret = cpupri_find_fitness(&task_rq(task)->rd->cpupri,
 					  task, lowest_mask,
@@ -1833,14 +1848,14 @@ static int find_lowest_rq(struct task_struct *task)
 	 * We prioritize the last CPU that the task executed on since
 	 * it is most likely cache-hot in that location.
 	 */
-	if (cpumask_test_cpu(cpu, lowest_mask))
+	if (cpumask_test_cpu(cpu, lowest_mask) && !arch_cpu_parked(cpu))
 		return cpu;
 
 	/*
 	 * Otherwise, we consult the sched_domains span maps to figure
 	 * out which CPU is logically closest to our hot cache data.
 	 */
-	if (!cpumask_test_cpu(this_cpu, lowest_mask))
+	if (!cpumask_test_cpu(this_cpu, lowest_mask) || arch_cpu_parked(this_cpu))
 		this_cpu = -1; /* Skip this_cpu opt if not among lowest */
 
 	rcu_read_lock();
@@ -1860,7 +1875,7 @@ static int find_lowest_rq(struct task_struct *task)
 
 			best_cpu = cpumask_any_and_distribute(lowest_mask,
 							      sched_domain_span(sd));
-			if (best_cpu < nr_cpu_ids) {
+			if (best_cpu < nr_cpu_ids && !arch_cpu_parked(best_cpu)) {
 				rcu_read_unlock();
 				return best_cpu;
 			}
@@ -1877,7 +1892,7 @@ static int find_lowest_rq(struct task_struct *task)
 		return this_cpu;
 
 	cpu = cpumask_any_distribute(lowest_mask);
-	if (cpu < nr_cpu_ids)
+	if (cpu < nr_cpu_ids && !arch_cpu_parked(cpu))
 		return cpu;
 
 	return -1;
-- 
2.34.1


^ permalink raw reply	[flat|nested] 6+ messages in thread

* [RFC PATCH v3 3/4] sched/fair: adapt scheduler group weight and capacity for parked CPUs
  2025-05-12 11:53 [RFC PATCH v3 0/4] sched/fair: introduce new scheduler group type group_parked Tobias Huschle
  2025-05-12 11:53 ` [RFC PATCH v3 1/4] " Tobias Huschle
  2025-05-12 11:53 ` [RFC PATCH v3 2/4] sched/rt: add support for parked CPUs Tobias Huschle
@ 2025-05-12 11:53 ` Tobias Huschle
  2025-05-12 11:53 ` [RFC PATCH v3 4/4] s390/topology: Add initial implementation for selection of " Tobias Huschle
  3 siblings, 0 replies; 6+ messages in thread
From: Tobias Huschle @ 2025-05-12 11:53 UTC (permalink / raw)
  To: linux-kernel
  Cc: mingo, peterz, juri.lelli, vincent.guittot, dietmar.eggemann,
	rostedt, bsegall, mgorman, vschneid, sshegde

Parked CPUs should not be considered to be available for computation.
This implies, that they should also not contribute to the overall weight
of scheduler groups, as a large group of parked CPUs should not attempt
to process any tasks, hence, a small group of non-parked CPUs should be
considered to have a larger weight.
The same consideration holds true for the CPU capacities of such groups.
A group of parked CPUs should not be considered to have any capacity.

Signed-off-by: Tobias Huschle <huschle@linux.ibm.com>
---
 kernel/sched/fair.c | 18 ++++++++++++++----
 1 file changed, 14 insertions(+), 4 deletions(-)

diff --git a/kernel/sched/fair.c b/kernel/sched/fair.c
index ee8ccee69774..d3161e928746 100644
--- a/kernel/sched/fair.c
+++ b/kernel/sched/fair.c
@@ -9934,6 +9934,8 @@ struct sg_lb_stats {
 	unsigned int sum_nr_running;		/* Nr of all tasks running in the group */
 	unsigned int sum_h_nr_running;		/* Nr of CFS tasks running in the group */
 	unsigned int sum_nr_parked;
+	unsigned int parked_cpus;
+	unsigned int parked_capacity;
 	unsigned int idle_cpus;                 /* Nr of idle CPUs         in the group */
 	unsigned int group_weight;
 	enum group_type group_type;
@@ -10390,6 +10392,8 @@ static inline void update_sg_lb_stats(struct lb_env *env,
 			*sg_overutilized = 1;
 
 		sgs->sum_nr_parked += arch_cpu_parked(i) * rq->cfs.h_nr_queued;
+		sgs->parked_capacity += arch_cpu_parked(i) * capacity_of(i);
+		sgs->parked_cpus += arch_cpu_parked(i);
 
 		/*
 		 * No need to call idle_cpu() if nr_running is not 0
@@ -10427,9 +10431,11 @@ static inline void update_sg_lb_stats(struct lb_env *env,
 		}
 	}
 
-	sgs->group_capacity = group->sgc->capacity;
+	sgs->group_capacity = group->sgc->capacity - sgs->parked_capacity;
+	if (!sgs->group_capacity)
+		sgs->group_capacity = 1;
 
-	sgs->group_weight = group->group_weight;
+	sgs->group_weight = group->group_weight - sgs->parked_cpus;
 
 	/* Check if dst CPU is idle and preferred to this group */
 	if (!local_group && env->idle && sgs->sum_h_nr_running &&
@@ -10713,6 +10719,8 @@ static inline void update_sg_wakeup_stats(struct sched_domain *sd,
 		sgs->sum_nr_running += nr_running;
 
 		sgs->sum_nr_parked += arch_cpu_parked(i) * rq->cfs.h_nr_queued;
+		sgs->parked_capacity += arch_cpu_parked(i) * capacity_of(i);
+		sgs->parked_cpus += arch_cpu_parked(i);
 
 		/*
 		 * No need to call idle_cpu_without() if nr_running is not 0
@@ -10728,9 +10736,11 @@ static inline void update_sg_wakeup_stats(struct sched_domain *sd,
 
 	}
 
-	sgs->group_capacity = group->sgc->capacity;
+	sgs->group_capacity = group->sgc->capacity - sgs->parked_capacity;
+	if (!sgs->group_capacity)
+		sgs->group_capacity = 1;
 
-	sgs->group_weight = group->group_weight;
+	sgs->group_weight = group->group_weight - sgs->parked_cpus;
 
 	sgs->group_type = group_classify(sd->imbalance_pct, group, sgs);
 
-- 
2.34.1


^ permalink raw reply	[flat|nested] 6+ messages in thread

* [RFC PATCH v3 4/4] s390/topology: Add initial implementation for selection of parked CPUs
  2025-05-12 11:53 [RFC PATCH v3 0/4] sched/fair: introduce new scheduler group type group_parked Tobias Huschle
                   ` (2 preceding siblings ...)
  2025-05-12 11:53 ` [RFC PATCH v3 3/4] sched/fair: adapt scheduler group weight and capacity " Tobias Huschle
@ 2025-05-12 11:53 ` Tobias Huschle
  3 siblings, 0 replies; 6+ messages in thread
From: Tobias Huschle @ 2025-05-12 11:53 UTC (permalink / raw)
  To: linux-kernel
  Cc: mingo, peterz, juri.lelli, vincent.guittot, dietmar.eggemann,
	rostedt, bsegall, mgorman, vschneid, sshegde

At first, vertical low CPUs will be parked generally. This will later
be adjusted by making the parked state dependent on the overall
utilization on the underlying hypervisor.

Vertical lows are always bound to the highest CPU IDs. This implies that
the three types of vertically polarized CPUs are always clustered by ID.
This has the following implications:
- There might be scheduler domains consisting of only vertical highs
- There might be scheduler domains consisting of only vertical lows

Signed-off-by: Tobias Huschle <huschle@linux.ibm.com>
---
 arch/s390/include/asm/smp.h | 2 ++
 arch/s390/kernel/smp.c      | 5 +++++
 2 files changed, 7 insertions(+)

diff --git a/arch/s390/include/asm/smp.h b/arch/s390/include/asm/smp.h
index 03f4d01664f8..93754c354803 100644
--- a/arch/s390/include/asm/smp.h
+++ b/arch/s390/include/asm/smp.h
@@ -31,6 +31,7 @@ static __always_inline unsigned int raw_smp_processor_id(void)
 }
 
 #define arch_scale_cpu_capacity smp_cpu_get_capacity
+#define arch_cpu_parked smp_cpu_parked
 
 extern struct mutex smp_cpu_state_mutex;
 extern unsigned int smp_cpu_mt_shift;
@@ -56,6 +57,7 @@ extern int smp_cpu_get_polarization(int cpu);
 extern void smp_cpu_set_capacity(int cpu, unsigned long val);
 extern void smp_set_core_capacity(int cpu, unsigned long val);
 extern unsigned long smp_cpu_get_capacity(int cpu);
+extern bool smp_cpu_parked(int cpu);
 extern int smp_cpu_get_cpu_address(int cpu);
 extern void smp_fill_possible_mask(void);
 extern void smp_detect_cpus(void);
diff --git a/arch/s390/kernel/smp.c b/arch/s390/kernel/smp.c
index 63f41dfaba85..6f6b2e90366d 100644
--- a/arch/s390/kernel/smp.c
+++ b/arch/s390/kernel/smp.c
@@ -680,6 +680,11 @@ void smp_set_core_capacity(int cpu, unsigned long val)
 		smp_cpu_set_capacity(i, val);
 }
 
+bool smp_cpu_parked(int cpu)
+{
+	return smp_cpu_get_polarization(cpu) == POLARIZATION_VL;
+}
+
 int smp_cpu_get_cpu_address(int cpu)
 {
 	return per_cpu(pcpu_devices, cpu).address;
-- 
2.34.1


^ permalink raw reply	[flat|nested] 6+ messages in thread

* Re: [RFC PATCH v3 2/4] sched/rt: add support for parked CPUs
  2025-05-12 11:53 ` [RFC PATCH v3 2/4] sched/rt: add support for parked CPUs Tobias Huschle
@ 2025-05-12 15:16   ` Tobias Huschle
  0 siblings, 0 replies; 6+ messages in thread
From: Tobias Huschle @ 2025-05-12 15:16 UTC (permalink / raw)
  To: linux-kernel
  Cc: mingo, peterz, juri.lelli, vincent.guittot, dietmar.eggemann,
	rostedt, bsegall, mgorman, vschneid, sshegde



On 12/05/2025 13:53, Tobias Huschle wrote:
> Realtime tasks must also react to the parked states of CPUs. Tasks will
> be treated as if the parked CPUs have no free capacity to work on them.
> 
> A dynamic change in the parked state of CPUs is handled correctly if
> realtime tasks do not consume 100% CPU time, without any interruption.
> If a realtime tasks runs without interruption, it will never enter the
> load balancing code and will therefore remain on a CPU, even if the CPU
> becomes classified as parked. Any value below 100% causes the task to
> be migrated off a CPU which has just been classified as parked.
> 
> Signed-off-by: Tobias Huschle <huschle@linux.ibm.com>

The content of this patch was proposed by Shrikanth, so special thanks 
to him!

<...>


^ permalink raw reply	[flat|nested] 6+ messages in thread

end of thread, other threads:[~2025-05-12 15:40 UTC | newest]

Thread overview: 6+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2025-05-12 11:53 [RFC PATCH v3 0/4] sched/fair: introduce new scheduler group type group_parked Tobias Huschle
2025-05-12 11:53 ` [RFC PATCH v3 1/4] " Tobias Huschle
2025-05-12 11:53 ` [RFC PATCH v3 2/4] sched/rt: add support for parked CPUs Tobias Huschle
2025-05-12 15:16   ` Tobias Huschle
2025-05-12 11:53 ` [RFC PATCH v3 3/4] sched/fair: adapt scheduler group weight and capacity " Tobias Huschle
2025-05-12 11:53 ` [RFC PATCH v3 4/4] s390/topology: Add initial implementation for selection of " Tobias Huschle

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®