Skip to contentSkip to conversation

uclamp sum aggregation

PATCH In review Hongyan Xia 3 participants 15 messages Mar 4, 2025 – Mar 10, 2025

Subsystems (2)
lore
Share & tools
lore
#0 lore raw

v2 cover uclamp sum aggregation

This series gives an alternative implementation that addresses some of the problems in uclamp max aggregation. Sum aggregation mostly gives:

1. Simplicity. Sum aggregation implements uclamp with less than half of code than max aggregation.
2. Effectiveness. Sum aggregation shows better uclamp effectiveness, either in benchmark scores or sometimes in energy efficiency.

The key idea of sum aggregation is fairly simple. Each task has a util_avg_bias, which is obtained by:

    util_avg_bias = clamp(util_avg, uclamp_min, uclamp_max) - util_avg;

If a CPU has N tasks, p1, p2, p3... pN, then we sum the biases up and obtain a rq total bias:

    rq_bias = util_avg_bias1 + util_avg_bias2... + util_avg_biasN;

Then we use the biased rq utilization rq_util + rq_bias to select OPP and to schedule tasks.

PATCH BREAKDOWN:

Patch 1/6 reverts a patch that accommodate uclamp_max tasks under max aggregation. This patch is not needed and creates other problems for sum aggregation. It is discussed elsewhere that this patch will be improved and there may not be the need to revert it in the future.

Patch 2, 3 and 4 implement sum aggregation.

Patch 5 and 6 remove max aggregation.

Patch 7 applies PELT decay on negative util_avg_bias. This improves energy efficiency and task placement, but is not strictly necessary.

Patch 8 addresses sum aggregation under-utilization problem.

TESTING:

Two notebooks are shared at

https://nbviewer.org/github/honxia02/notebooks/blob/aac12d9becae2b2fe4690cbb672439fd884ede30/whitebox/max.ipynb
https://nbviewer.org/github/honxia02/notebooks/blob/aac12d9becae2b2fe4690cbb672439fd884ede30/whitebox/sum-offset.ipynb

The experiments done in notebooks are on Arm Juno r2 board. CPU0-3 are little cores with capacity of 383. CPU4-5 are big cores. The rt-app profiles used for these experiments are included in the notebooks.

Scenario 1: Scheduling 4 tasks with UCLAMP_MAX at 110.

The scheduling decisions are plotted in Out[11]. Both max and sum aggregation understand the UCLAMP_MAX hint and schedule all 4 tasks on the little cluster. Max aggregation sometimes schedule 2 tasks on 1 CPU, and this is the reason why sum aggregation reverts the 1st commit.

Scenario 2: Scheduling 4 tasks with UCLAMP_MIN and UCLAMP_MAX at a value slightly above the capacity of the little CPU.

Results are in Out[17]. The purpose is to use UCLAMP_MIN to place tasks on the big core. Both max and sum aggregation handle this correctly.

Scenario 3: Task A is a task with a small utilization pinned to CPU4. Task B is an always-running task pinned to CPU5, but UCLAMP_MAX capped at 300. After a while, task A is then pinned to CPU5, joining B.

Results are in Out[23]. Max aggregation sees a frequency spike at 873.64s. When zoomed in, one can see square-wave-like utilization values because of A periodically going to sleep. When A wakes up, its default UCLAMP_MAX of 1024 will uncap B and reach the highest CPU frequency. When A sleeps, B's UCLAMP_MAX will be in effect and will reduce rq utilization. This happens repeatedly, hence the square wave. In contrast, sum aggregation sees a normal increase in utilization when A joins B, without any square-wave behavior.

Scenario 4: 4 always-running tasks with UCLAMP_MAX of 110 pinned to the little PD (CPU0-3). 4 same tasks pinned to the big PD (CPU4-5). After a while, remove the CPU pinning of the 4 tasks on the big PD.

Results are in Out[29]. After unpinning, max aggregation moves all 8 tasks to the little cluster, but schedules 5 tasks on CPU0 and 1 each on CPU1-3. In contrast, sum aggregation schedules 2 on each little CPU after unpinning, which is the desired balanced task placement.

EVALUATION:

We backport patches to GKI kernel v6.1 on Pixel 9 and run Android benchmarks.

Speedometer:

We run Speedometer 2.1 on Chrome v131 to test ADPF/uclamp effectiveness. Because sum aggregation does not circumvent the 25% OPP margin, we reduce uclamp values to 80% to be fair.

|   score   | score |   %    | CPU power % |
|    max    | 192.4 |        |             |
|  sum_0.8  | 230.8 | +19.96 |   +31.54    |
| sum_tuned | 201.8 |  +4.89 |    -0.41    |

We see a consistant higher score and higher average power consumption. Note that a higher score also means a reduction in run-time, total energy increase for sum_0.8 is only 9.65%.

We then reduce uclamp values so that power consumption is roughly the same. If we do so, then sum aggregation achieves slightly better scores, shown in the sum_tuned row.

UIBench:

|   score   | jank percentage |   %    | CPU power (mW) |   %   |
|    max    |     0.115%      |        |     158.1      |       |
|  sum_0.8  |     0.129%      | +11.96 |     154.9      | -4.19 |

UIBench on Pixel 9 by default already has a low enough jank percentage. Moving to sum aggregation gives slightly higher jank percentage and lower power consumption.

---
Changed in v2:
- Completely separate uclamp component from PELT and util_est.
- Separate util_est_uclamp into an individual patch.
- Address the under-utilization problem.
- Update Python notebooks to reflect the latest sched/tip.

Hongyan Xia (8):
Revert "sched/uclamp: Set max_spare_cap_cpu even if max_spare_cap is

    0"

sched/uclamp: Track a new util_avg_bias signal sched/uclamp: Add util_est_uclamp
sched/fair: Use util biases for utilization and frequency sched/uclamp: Remove all uclamp bucket logic sched/uclamp: Simplify uclamp_eff_value() sched/uclamp: Propagate negative bias sched/uclamp: Solve under-utilization problem

 include/linux/sched.h            |   8 +-
 init/Kconfig                     |  32 ---
 kernel/sched/core.c              | 308 ++--------------------
 kernel/sched/cpufreq_schedutil.c |   6 +-
 kernel/sched/debug.c             |   2 +-
 kernel/sched/fair.c              | 430 ++++++++++++++++---------------
 kernel/sched/pelt.c              |  62 +++++
 kernel/sched/rt.c                |   4 -
 kernel/sched/sched.h             | 132 +++-------
 kernel/sched/syscalls.c          |   2 +
 10 files changed, 341 insertions(+), 645 deletions(-)
Signature · 1 line
2.34.1
#1 lore raw

PATCH v2 1/8 Revert "sched/uclamp: Set max_spare_cap_cpu even if max_spare_cap is 0"

From: Hongyan Xia <[email protected]>

That commit creates further problems because 0 spare capacity can be either a real indication that the CPU is maxed out, or the CPU is UCLAMP_MAX throttled, but we end up giving all of them a chance which can results in bogus energy calculations. It also tends to schedule tasks on the same CPU and requires load balancing patches. Sum aggregation solves these problems and this patch is not needed.

This reverts commit 6b00a40147653c8ea748e8f4396510f252763364.

---
 kernel/sched/fair.c | 11 ++++++-----
 1 file changed, 6 insertions(+), 5 deletions(-)
kernel/sched/fair.c +6 -5
index 857808da23d8..71fc86eafbd9 100644
@@ -8417,10 +8417,11 @@ static int find_energy_efficient_cpu(struct task_struct *p, int prev_cpu)
 	for (; pd; pd = pd->next) {
 		unsigned long util_min = p_util_min, util_max = p_util_max;
 		unsigned long cpu_cap, cpu_actual_cap, util;
-		long prev_spare_cap = -1, max_spare_cap = -1;
+		unsigned long cur_delta, max_spare_cap = 0;
 		unsigned long rq_util_min, rq_util_max;
-		unsigned long cur_delta, base_energy;
+		unsigned long prev_spare_cap = 0;
 		int max_spare_cap_cpu = -1;
+		unsigned long base_energy;
 		int fits, max_fits = -1;
 
 		cpumask_and(cpus, perf_domain_span(pd), cpu_online_mask);
@@ -8482,7 +8483,7 @@ static int find_energy_efficient_cpu(struct task_struct *p, int prev_cpu)
 				prev_spare_cap = cpu_cap;
 				prev_fits = fits;
 			} else if ((fits > max_fits) ||
-				   ((fits == max_fits) && ((long)cpu_cap > max_spare_cap))) {
+				   ((fits == max_fits) && (cpu_cap > max_spare_cap))) {
 				/*
 				 * Find the CPU with the maximum spare capacity
 				 * among the remaining CPUs in the performance
@@ -8494,7 +8495,7 @@ static int find_energy_efficient_cpu(struct task_struct *p, int prev_cpu)
 			}
 		}
 
-		if (max_spare_cap_cpu < 0 && prev_spare_cap < 0)
+		if (max_spare_cap_cpu < 0 && prev_spare_cap == 0)
 			continue;
 
 		eenv_pd_busy_time(&eenv, cpus, p);
@@ -8502,7 +8503,7 @@ static int find_energy_efficient_cpu(struct task_struct *p, int prev_cpu)
 		base_energy = compute_energy(&eenv, pd, cpus, p, -1);
 
 		/* Evaluate the energy impact of using prev_cpu. */
-		if (prev_spare_cap > -1) {
+		if (prev_spare_cap > 0) {
 			prev_delta = compute_energy(&eenv, pd, cpus, p,
 						    prev_cpu);
 			/* CPU utilization has changed */
Signature · 1 line
2.34.1
#2 lore raw

PATCH v2 2/8 sched/uclamp: Track a new util_avg_bias signal

In reply to Hongyan Xia

Add a util_avg_bias signal in sched_avg, which is obtained by:

util_avg_bias = clamp(util_avg, uclamp_min, uclamp_max) - util_avg

The task utilization after considering uclamp is;

util_avg_uclamp = util_avg + util_avg_bias

We then sum up all biases on the same rq and use the total bias to bias the rq utilization. This is the core idea of uclamp sum aggregation. The rq utilization will be

rq_util_avg_uclamp = rq_util_avg + total_util_avg_bias

---
 include/linux/sched.h |  3 ++-
 kernel/sched/debug.c  |  2 +-
 kernel/sched/fair.c   | 33 +++++++++++++++++++++++++++++++++
 kernel/sched/pelt.c   | 37 +++++++++++++++++++++++++++++++++++++
 kernel/sched/sched.h  | 24 ++++++++++++++++++++++++
 5 files changed, 97 insertions(+), 2 deletions(-)
include/linux/sched.h +2 -1
index 9632e3318e0d..1f3b06aa024d 100644
@@ -487,7 +487,8 @@ struct sched_avg {
 	u32				period_contrib;
 	unsigned long			load_avg;
 	unsigned long			runnable_avg;
-	unsigned long			util_avg;
+	unsigned int			util_avg;
+	int				util_avg_bias;
 	unsigned int			util_est;
 } ____cacheline_aligned;
 
kernel/sched/debug.c +1 -1
index ef047add7f9e..264ee83958b5 100644
@@ -853,7 +853,7 @@ void print_cfs_rq(struct seq_file *m, int cpu, struct cfs_rq *cfs_rq)
 			cfs_rq->avg.load_avg);
 	SEQ_printf(m, "  .%-30s: %lu\n", "runnable_avg",
 			cfs_rq->avg.runnable_avg);
-	SEQ_printf(m, "  .%-30s: %lu\n", "util_avg",
+	SEQ_printf(m, "  .%-30s: %u\n", "util_avg",
 			cfs_rq->avg.util_avg);
 	SEQ_printf(m, "  .%-30s: %u\n", "util_est",
 			cfs_rq->avg.util_est);
kernel/sched/fair.c +33 -0
index 71fc86eafbd9..438755f55624 100644
@@ -1131,6 +1131,7 @@ void post_init_entity_util_avg(struct task_struct *p)
 	}
 
 	sa->runnable_avg = sa->util_avg;
+	sa->util_avg_bias = 0;
 }
 
 #else /* !CONFIG_SMP */
@@ -4852,6 +4853,32 @@ static inline unsigned long task_util_est(struct task_struct *p)
 	return max(task_util(p), _task_util_est(p));
 }
 
+#ifdef CONFIG_UCLAMP_TASK
+static inline long task_util_bias(struct task_struct *p)
+{
+	return READ_ONCE(p->se.avg.util_avg_bias);
+}
+
+static inline unsigned long task_util_uclamp(struct task_struct *p)
+{
+	long ret = task_util(p);
+
+	ret += task_util_bias(p);
+
+	return max(ret, 0L);
+}
+#else
+static inline long task_util_bias(struct task_struct *p)
+{
+	return 0;
+}
+
+static inline unsigned long task_util_uclamp(struct task_struct *p)
+{
+	return task_util(p);
+}
+#endif
+
 static inline void util_est_enqueue(struct cfs_rq *cfs_rq,
 				    struct task_struct *p)
 {
@@ -7027,6 +7054,10 @@ enqueue_task_fair(struct rq *rq, struct task_struct *p, int flags)
 
 	/* At this point se is NULL and we are at root level*/
 	add_nr_running(rq, 1);
+	util_bias_enqueue(rq, p);
+	/* XXX: We should skip the update above and only do it once here. */
+	if (task_util_bias(p) > 0)
+		cpufreq_update_util(rq, 0);
 
 	/*
 	 * Since new tasks are assigned an initial util_avg equal to
@@ -7150,6 +7181,8 @@ static int dequeue_entities(struct rq *rq, struct sched_entity *se, int flags)
 	}
 
 	sub_nr_running(rq, h_nr_queued);
+	if (p)
+		util_bias_dequeue(rq, p);
 
 	if (rq_h_nr_queued && !rq->cfs.h_nr_queued)
 		dl_server_stop(&rq->fair_server);
kernel/sched/pelt.c +37 -0
index 7a8534a2deff..f38abe6f0b8b 100644
@@ -266,6 +266,39 @@ ___update_load_avg(struct sched_avg *sa, unsigned long load)
 	WRITE_ONCE(sa->util_avg, sa->util_sum / divider);
 }
 
+#ifdef CONFIG_UCLAMP_TASK
+/* avg must belong to the queue this se is on. */
+static void util_bias_update(struct task_struct *p)
+{
+	unsigned int util, uclamp_min, uclamp_max;
+	struct rq *rq;
+	int old, new;
+
+	util = READ_ONCE(p->se.avg.util_avg);
+	uclamp_min = uclamp_eff_value(p, UCLAMP_MIN);
+	uclamp_max = uclamp_eff_value(p, UCLAMP_MAX);
+	/*
+	 * uclamp_max at the max value means there is no uclamp_max, and should
+	 * not have any clamping effect at all here.
+	 */
+	if (uclamp_max == SCHED_CAPACITY_SCALE)
+		uclamp_max = UINT_MAX;
+	old = READ_ONCE(p->se.avg.util_avg_bias);
+	new = (int)clamp(util, uclamp_min, uclamp_max) - (int)util;
+
+	WRITE_ONCE(p->se.avg.util_avg_bias, new);
+	if (!p->se.on_rq)
+		return;
+	rq = task_rq(p);
+	WRITE_ONCE(rq->cfs.avg.util_avg_bias,
+		   READ_ONCE(rq->cfs.avg.util_avg_bias) + new - old);
+}
+#else /* !CONFIG_UCLAMP_TASK */
+static void util_bias_update(struct task_struct *p)
+{
+}
+#endif
+
 /*
  * sched_entity:
  *
@@ -296,6 +329,8 @@ int __update_load_avg_blocked_se(u64 now, struct sched_entity *se)
 {
 	if (___update_load_sum(now, &se->avg, 0, 0, 0)) {
 		___update_load_avg(&se->avg, se_weight(se));
+		if (entity_is_task(se))
+			util_bias_update(task_of(se));
 		trace_pelt_se_tp(se);
 		return 1;
 	}
@@ -310,6 +345,8 @@ int __update_load_avg_se(u64 now, struct cfs_rq *cfs_rq, struct sched_entity *se
 
 		___update_load_avg(&se->avg, se_weight(se));
 		cfs_se_util_change(&se->avg);
+		if (entity_is_task(se))
+			util_bias_update(task_of(se));
 		trace_pelt_se_tp(se);
 		return 1;
 	}
kernel/sched/sched.h +24 -0
index ab16d3d0e51c..74363bc74e23 100644
@@ -3468,6 +3468,22 @@ uclamp_se_set(struct uclamp_se *uc_se, unsigned int value, bool user_defined)
 	uc_se->user_defined = user_defined;
 }
 
+static inline void util_bias_enqueue(struct rq *rq, struct task_struct *p)
+{
+	int rq_val = READ_ONCE(rq->cfs.avg.util_avg_bias);
+	int p_val = READ_ONCE(p->se.avg.util_avg_bias);
+
+	WRITE_ONCE(rq->cfs.avg.util_avg_bias, rq_val + p_val);
+}
+
+static inline void util_bias_dequeue(struct rq *rq, struct task_struct *p)
+{
+	int rq_val = READ_ONCE(rq->cfs.avg.util_avg_bias);
+	int p_val = READ_ONCE(p->se.avg.util_avg_bias);
+
+	WRITE_ONCE(rq->cfs.avg.util_avg_bias, rq_val - p_val);
+}
+
 #else /* !CONFIG_UCLAMP_TASK: */
 
 static inline unsigned long
@@ -3505,6 +3521,14 @@ static inline bool uclamp_rq_is_idle(struct rq *rq)
 	return false;
 }
 
+static inline void util_bias_enqueue(struct rq *rq, struct task_struct *p)
+{
+}
+
+static inline void util_bias_dequeue(struct rq *rq, struct task_struct *p)
+{
+}
+
 #endif /* !CONFIG_UCLAMP_TASK */
 
 #ifdef CONFIG_HAVE_SCHED_AVG_IRQ
Signature · 1 line
2.34.1
#3 lore raw

PATCH v2 3/8 sched/uclamp: Add util_est_uclamp

In reply to Hongyan Xia

The new util_est_uclamp is essentially clamp(util_est, min, max) and follows how util_est operates.

---
 include/linux/sched.h |  1 +
 kernel/sched/fair.c   | 30 ++++++++++++++++++++++++++++++
 2 files changed, 31 insertions(+)
include/linux/sched.h +1 -0
index 1f3b06aa024d..a4bdfa1d6be1 100644
@@ -490,6 +490,7 @@ struct sched_avg {
 	unsigned int			util_avg;
 	int				util_avg_bias;
 	unsigned int			util_est;
+	unsigned int			util_est_uclamp;
 } ____cacheline_aligned;
 
 /*
kernel/sched/fair.c +30 -0
index 438755f55624..e9aa93f99a4e 100644
@@ -4867,6 +4867,16 @@ static inline unsigned long task_util_uclamp(struct task_struct *p)
 
 	return max(ret, 0L);
 }
+
+static inline unsigned long _task_util_est_uclamp(struct task_struct *p)
+{
+	return READ_ONCE(p->se.avg.util_est_uclamp);
+}
+
+static inline unsigned long task_util_est_uclamp(struct task_struct *p)
+{
+	return max(task_util_uclamp(p), _task_util_est_uclamp(p));
+}
 #else
 static inline long task_util_bias(struct task_struct *p)
 {
@@ -4877,6 +4887,16 @@ static inline unsigned long task_util_uclamp(struct task_struct *p)
 {
 	return task_util(p);
 }
+
+static inline unsigned long _task_util_est_uclamp(struct task_struct *p)
+{
+	return _task_util_est(p);
+}
+
+static inline unsigned long task_util_est_uclamp(struct task_struct *p)
+{
+	return task_util_est(p);
+}
 #endif
 
 static inline void util_est_enqueue(struct cfs_rq *cfs_rq,
@@ -4891,6 +4911,9 @@ static inline void util_est_enqueue(struct cfs_rq *cfs_rq,
 	enqueued  = cfs_rq->avg.util_est;
 	enqueued += _task_util_est(p);
 	WRITE_ONCE(cfs_rq->avg.util_est, enqueued);
+	enqueued  = cfs_rq->avg.util_est_uclamp;
+	enqueued += _task_util_est_uclamp(p);
+	WRITE_ONCE(cfs_rq->avg.util_est_uclamp, enqueued);
 
 	trace_sched_util_est_cfs_tp(cfs_rq);
 }
@@ -4907,6 +4930,9 @@ static inline void util_est_dequeue(struct cfs_rq *cfs_rq,
 	enqueued  = cfs_rq->avg.util_est;
 	enqueued -= min_t(unsigned int, enqueued, _task_util_est(p));
 	WRITE_ONCE(cfs_rq->avg.util_est, enqueued);
+	enqueued  = cfs_rq->avg.util_est_uclamp;
+	enqueued -= _task_util_est_uclamp(p);
+	WRITE_ONCE(cfs_rq->avg.util_est_uclamp, enqueued);
 
 	trace_sched_util_est_cfs_tp(cfs_rq);
 }
@@ -4994,6 +5020,10 @@ static inline void util_est_update(struct cfs_rq *cfs_rq,
 	ewma  -= last_ewma_diff;
 	ewma >>= UTIL_EST_WEIGHT_SHIFT;
 done:
+	WRITE_ONCE(p->se.avg.util_est_uclamp,
+		   clamp(ewma,
+			 (unsigned int)uclamp_eff_value(p, UCLAMP_MIN),
+			 (unsigned int)uclamp_eff_value(p, UCLAMP_MAX)));
 	ewma |= UTIL_AVG_UNCHANGED;
 	WRITE_ONCE(p->se.avg.util_est, ewma);
 
Signature · 1 line
2.34.1
#4 lore raw

PATCH v2 4/8 sched/fair: Use util biases for utilization and frequency

In reply to Hongyan Xia

Use the new util_avg_bias for task and runqueue utilization. We also maintain separate util_est and util_est_uclamp signals.

Now that we have the uclamp sum aggregated CFS util value, we do not need to consult uclamp buckets to know how the frequency should be clamped. We simply look at the aggregated top level rq->cfs.avg.util_avg
+ rq->cfs.avg.util_avg_bias and rq->cfs.avg.util_est_uclamp to know what frequency to choose and how to place tasks.

---
 kernel/sched/cpufreq_schedutil.c |   6 +-
 kernel/sched/fair.c              | 296 +++++++++++--------------------
 kernel/sched/sched.h             |  19 +-
 3 files changed, 101 insertions(+), 220 deletions(-)
kernel/sched/cpufreq_schedutil.c +1 -5
index 1a19d69b91ed..7b1616b139f6 100644
@@ -200,7 +200,7 @@ static void sugov_get_util(struct sugov_cpu *sg_cpu, unsigned long boost)
 	unsigned long min, max, util = scx_cpuperf_target(sg_cpu->cpu);
 
 	if (!scx_switched_all())
-		util += cpu_util_cfs_boost(sg_cpu->cpu);
+		util += cpu_util_cfs_boost_uclamp(sg_cpu->cpu);
 	util = effective_cpu_util(sg_cpu->cpu, util, &min, &max);
 	util = max(util, boost);
 	sg_cpu->bw_min = min;
@@ -340,10 +340,6 @@ static bool sugov_hold_freq(struct sugov_cpu *sg_cpu)
 	if (scx_switched_all())
 		return false;
 
-	/* if capped by uclamp_max, always update to be in compliance */
-	if (uclamp_rq_is_capped(cpu_rq(sg_cpu->cpu)))
-		return false;
-
 	/*
 	 * Maintain the frequency if the CPU has not been idle recently, as
 	 * reduction is likely to be premature.
kernel/sched/fair.c +99 -197

478 lines not on this page · raw message

kernel/sched/sched.h +1 -18
index 74363bc74e23..b50e3d6e79c4 100644
@@ -3379,7 +3379,7 @@ static inline unsigned long cpu_util_dl(struct rq *rq)
 
 
 extern unsigned long cpu_util_cfs(int cpu);
-extern unsigned long cpu_util_cfs_boost(int cpu);
+extern unsigned long cpu_util_cfs_boost_uclamp(int cpu);
 
 static inline unsigned long cpu_util_rt(struct rq *rq)
 {
@@ -3411,21 +3411,6 @@ static inline bool uclamp_rq_is_idle(struct rq *rq)
 	return rq->uclamp_flags & UCLAMP_FLAG_IDLE;
 }
 
-/* Is the rq being capped/throttled by uclamp_max? */
-static inline bool uclamp_rq_is_capped(struct rq *rq)
-{
-	unsigned long rq_util;
-	unsigned long max_util;
-
-	if (!static_branch_likely(&sched_uclamp_used))
-		return false;
-
-	rq_util = cpu_util_cfs(cpu_of(rq)) + cpu_util_rt(rq);
-	max_util = READ_ONCE(rq->uclamp[UCLAMP_MAX].value);
-
-	return max_util != SCHED_CAPACITY_SCALE && rq_util >= max_util;
-}
-
 /*
  * When uclamp is compiled in, the aggregation at rq level is 'turned off'
  * by default in the fast path and only gets turned on once userspace performs
@@ -3495,8 +3480,6 @@ uclamp_eff_value(struct task_struct *p, enum uclamp_id clamp_id)
 	return SCHED_CAPACITY_SCALE;
 }
 
-static inline bool uclamp_rq_is_capped(struct rq *rq) { return false; }
-
 static inline bool uclamp_is_used(void)
 {
 	return false;
Signature · 1 line
2.34.1
#5 lore raw

PATCH v2 5/8 sched/uclamp: Remove all uclamp bucket logic

In reply to Hongyan Xia

Also rewrite uclamp_update_active() so that the effective uclamp values are updated every time we change task group properties, change system defaults or a request is issued from userspace.

This also signnificantly reduces uclamp overhead because we no longer need to compute effective uclamp values and manipulate buckets every time a task is enqueued or dequeued (in uclamp_rq_{inc/dec}()).

TODO: Rewrite documentation to match the new logic.

---
 include/linux/sched.h   |   4 -
 init/Kconfig            |  32 -----
 kernel/sched/core.c     | 301 ++--------------------------------------
 kernel/sched/fair.c     |   4 -
 kernel/sched/rt.c       |   4 -
 kernel/sched/sched.h    |  95 +------------
 kernel/sched/syscalls.c |   2 +
 7 files changed, 20 insertions(+), 422 deletions(-)
include/linux/sched.h +0 -4
index a4bdfa1d6be1..012df2f612d4 100644
@@ -722,9 +722,6 @@ struct sched_dl_entity {
 };
 
 #ifdef CONFIG_UCLAMP_TASK
-/* Number of utilization clamp buckets (shorter alias) */
-#define UCLAMP_BUCKETS CONFIG_UCLAMP_BUCKETS_COUNT
-
 /*
  * Utilization clamp for a scheduling entity
  * @value:		clamp value "assigned" to a se
@@ -750,7 +747,6 @@ struct sched_dl_entity {
  */
 struct uclamp_se {
 	unsigned int value		: bits_per(SCHED_CAPACITY_SCALE);
-	unsigned int bucket_id		: bits_per(UCLAMP_BUCKETS);
 	unsigned int active		: 1;
 	unsigned int user_defined	: 1;
 };
init/Kconfig +0 -32
index d0d021b3fa3b..6d6d2eaa2963 100644
@@ -843,38 +843,6 @@ config UCLAMP_TASK
 	  enforce or grant any specific bandwidth for tasks.
 
 	  If in doubt, say N.
-
-config UCLAMP_BUCKETS_COUNT
-	int "Number of supported utilization clamp buckets"
-	range 5 20
-	default 5
-	depends on UCLAMP_TASK
-	help
-	  Defines the number of clamp buckets to use. The range of each bucket
-	  will be SCHED_CAPACITY_SCALE/UCLAMP_BUCKETS_COUNT. The higher the
-	  number of clamp buckets the finer their granularity and the higher
-	  the precision of clamping aggregation and tracking at run-time.
-
-	  For example, with the minimum configuration value we will have 5
-	  clamp buckets tracking 20% utilization each. A 25% boosted tasks will
-	  be refcounted in the [20..39]% bucket and will set the bucket clamp
-	  effective value to 25%.
-	  If a second 30% boosted task should be co-scheduled on the same CPU,
-	  that task will be refcounted in the same bucket of the first task and
-	  it will boost the bucket clamp effective value to 30%.
-	  The clamp effective value of a bucket is reset to its nominal value
-	  (20% in the example above) when there are no more tasks refcounted in
-	  that bucket.
-
-	  An additional boost/capping margin can be added to some tasks. In the
-	  example above the 25% task will be boosted to 30% until it exits the
-	  CPU. If that should be considered not acceptable on certain systems,
-	  it's always possible to reduce the margin by increasing the number of
-	  clamp buckets to trade off used memory for run-time tracking
-	  precision.
-
-	  If in doubt, use the default value.
-
 endmenu
 
 #
kernel/sched/core.c +15 -286

384 lines not on this page · raw message

kernel/sched/fair.c +0 -4
index 3d91dbd19a85..68e7b1ac7a57 100644
@@ -13626,10 +13626,6 @@ DEFINE_SCHED_CLASS(fair) = {
 #ifdef CONFIG_SCHED_CORE
 	.task_is_throttled	= task_is_throttled_fair,
 #endif
-
-#ifdef CONFIG_UCLAMP_TASK
-	.uclamp_enabled		= 1,
-#endif
 };
 
 #ifdef CONFIG_SCHED_DEBUG
kernel/sched/rt.c +0 -4
index 4b8e33c615b1..4cd8d3e06eeb 100644
@@ -2649,10 +2649,6 @@ DEFINE_SCHED_CLASS(rt) = {
 #ifdef CONFIG_SCHED_CORE
 	.task_is_throttled	= task_is_throttled_rt,
 #endif
-
-#ifdef CONFIG_UCLAMP_TASK
-	.uclamp_enabled		= 1,
-#endif
 };
 
 #ifdef CONFIG_RT_GROUP_SCHED
kernel/sched/sched.h +3 -92

138 lines not on this page · raw message

kernel/sched/syscalls.c +2 -0
index 9f40348f1dc7..24af915f8d18 100644
@@ -437,6 +437,8 @@ static void __setscheduler_uclamp(struct task_struct *p,
 		uclamp_se_set(&p->uclamp_req[UCLAMP_MAX],
 			      attr->sched_util_max, true);
 	}
+
+	uclamp_update_active_nolock(p);
 }
 
 #else /* !CONFIG_UCLAMP_TASK: */
Signature · 1 line
2.34.1
#6 lore raw

PATCH v2 6/8 sched/uclamp: Simplify uclamp_eff_value()

In reply to Hongyan Xia

The commit

sched: Remove all uclamp bucket logic

removes uclamp_rq_{inc/dec}() functions, so now p->uclamp contains the correct values all the time after a uclamp_update_active() call, and there's no need to toggle the boolean active after an update. As a result, this function is fairly simple now and can live as a static inline function.

---
 kernel/sched/core.c  | 13 ++++---------
 kernel/sched/sched.h | 10 +++++++++-
 2 files changed, 13 insertions(+), 10 deletions(-)
kernel/sched/core.c +4 -9
index 85c69ca7abaa..f3825e36ae85 100644
@@ -1569,20 +1569,14 @@ uclamp_eff_get(struct task_struct *p, enum uclamp_id clamp_id)
 	return uc_req;
 }
 
-unsigned long uclamp_eff_value(struct task_struct *p, enum uclamp_id clamp_id)
-{
-	if (!uclamp_is_used() || !p->uclamp[clamp_id].active)
-		return uclamp_none(clamp_id);
-
-	return p->uclamp[clamp_id].value;
-}
-
 void uclamp_update_active_nolock(struct task_struct *p)
 {
 	enum uclamp_id clamp_id;
 
-	for_each_clamp_id(clamp_id)
+	for_each_clamp_id(clamp_id) {
 		p->uclamp[clamp_id] = uclamp_eff_get(p, clamp_id);
+		p->uclamp[clamp_id].active = 1;
+	}
 }
 
 static inline void
@@ -1737,6 +1731,7 @@ static void uclamp_fork(struct task_struct *p)
 		for_each_clamp_id(clamp_id) {
 			uclamp_se_set(&p->uclamp_req[clamp_id],
 				      uclamp_none(clamp_id), false);
+			p->uclamp[clamp_id].active = 0;
 		}
 	}
 
kernel/sched/sched.h +9 -1
index cfeaefcec8b6..f4a82e6cc029 100644
@@ -3343,7 +3343,6 @@ static inline bool update_other_load_avgs(struct rq *rq) { return false; }
 
 #ifdef CONFIG_UCLAMP_TASK
 
-unsigned long uclamp_eff_value(struct task_struct *p, enum uclamp_id clamp_id);
 void uclamp_update_active_nolock(struct task_struct *p);
 
 /*
@@ -3372,6 +3371,15 @@ static inline unsigned int uclamp_none(enum uclamp_id clamp_id)
 	return SCHED_CAPACITY_SCALE;
 }
 
+static inline unsigned long
+uclamp_eff_value(struct task_struct *p, enum uclamp_id clamp_id)
+{
+	if (uclamp_is_used() && p->uclamp[clamp_id].active)
+		return p->uclamp[clamp_id].value;
+
+	return uclamp_none(clamp_id);
+}
+
 static inline void
 uclamp_se_set(struct uclamp_se *uc_se, unsigned int value, bool user_defined)
 {
Signature · 1 line
2.34.1
#7 lore raw

PATCH v2 7/8 sched/uclamp: Propagate negative bias

In reply to Hongyan Xia

Negative bias is interesting, because dequeuing such a task will actually increase utilization.

Solve by applying PELT decay to negative biases as well. This in fact can be implemented easily with some math tricks.

---
 kernel/sched/fair.c  | 46 +++++++++++++++++++++++++++++++++++++++++++-
 kernel/sched/sched.h |  4 ++++
 2 files changed, 49 insertions(+), 1 deletion(-)
kernel/sched/fair.c +45 -1
index 68e7b1ac7a57..944953b90297 100644
@@ -4886,6 +4886,48 @@ static inline unsigned long root_cfs_util_uclamp(struct rq *rq)
 
 	return max(ret, 0L);
 }
+
+/*
+ * Negative biases are tricky. If we remove them right away then dequeuing a
+ * uclamp_max task has the interesting effect that dequeuing results in a higher
+ * rq utilization. Solve this by applying PELT decay to the bias itself.
+ *
+ * Keeping track of a PELT-decayed negative bias is extra overhead. However, we
+ * observe this interesting math property, where y is the decay factor and p is
+ * the number of periods elapsed:
+ *
+ *	util_new = util_old * y^p - neg_bias * y^p
+ *		 = (util_old - neg_bias) * y^p
+ *
+ * Therefore, we simply subtract the negative bias from util_avg the moment we
+ * dequeue, then the PELT signal itself is the total of util_avg and the decayed
+ * negative bias, and we no longer need to track the decayed bias separately.
+ */
+static void propagate_negative_bias(struct task_struct *p)
+{
+	if (task_util_bias(p) < 0 && !task_on_rq_migrating(p)) {
+		unsigned long neg_bias = -task_util_bias(p);
+		struct sched_entity *se = &p->se;
+
+		p->se.avg.util_avg_bias = 0;
+
+		for_each_sched_entity(se) {
+			struct sched_avg *sa = &se->avg;
+			u32 divider = get_pelt_divider(sa);
+
+			sub_positive(&sa->util_avg, neg_bias);
+			sub_positive(&sa->util_sum, neg_bias * divider);
+			sa->util_sum = max_t(u32, sa->util_sum,
+					     sa->util_avg * PELT_MIN_DIVIDER);
+			sa = &cfs_rq_of(se)->avg;
+			divider = get_pelt_divider(sa);
+			sub_positive(&sa->util_avg, neg_bias);
+			sub_positive(&sa->util_sum, neg_bias * divider);
+			sa->util_sum = max_t(u32, sa->util_sum,
+					     sa->util_avg * PELT_MIN_DIVIDER);
+		}
+	}
+}
 #else
 static inline long task_util_bias(struct task_struct *p)
 {
@@ -7114,8 +7156,10 @@ static int dequeue_entities(struct rq *rq, struct sched_entity *se, int flags)
 	}
 
 	sub_nr_running(rq, h_nr_queued);
-	if (p)
+	if (p) {
 		util_bias_dequeue(rq, p);
+		propagate_negative_bias(p);
+	}
 
 	if (rq_h_nr_queued && !rq->cfs.h_nr_queued)
 		dl_server_stop(&rq->fair_server);
kernel/sched/sched.h +4 -0
index f4a82e6cc029..654eede62979 100644
@@ -3431,6 +3431,10 @@ static inline void util_bias_dequeue(struct rq *rq, struct task_struct *p)
 {
 }
 
+static inline void propagate_negative_bias(struct task_struct *p)
+{
+}
+
 #endif /* !CONFIG_UCLAMP_TASK */
 
 #ifdef CONFIG_HAVE_SCHED_AVG_IRQ
Signature · 1 line
2.34.1
#8 lore raw

PATCH v2 8/8 sched/uclamp: Solve under-utilization problem

In reply to Hongyan Xia

With sum aggregation, a heavily uclamp_max-throttled task may throttle the whole rq, resulting in low OPP.

For example, two tasks having the same priority and both tasks are always-running tasks. One task has no uclamp values but the other has uclamp_max of 1. Then, under sum aggregation, the CPU will run at 512 + 1 = 513 OPP, which means the task without uclamp_max only gets 513 / 2 = 256 utilization, even though the CPU still can run faster.

With this patch, we do not throttle a uclamp_max too hard such that it impacts other tasks. This is done by tracking the highest uclamp_factor and any uclamp_max tasks cannot throttle more than this factor allows.

---
 kernel/sched/fair.c  | 12 ++++++++++++
 kernel/sched/pelt.c  | 33 +++++++++++++++++++++++++++++----
 kernel/sched/sched.h |  2 ++
 3 files changed, 43 insertions(+), 4 deletions(-)
kernel/sched/fair.c +12 -0
index 944953b90297..966ca63da3fa 100644
@@ -7159,6 +7159,18 @@ static int dequeue_entities(struct rq *rq, struct sched_entity *se, int flags)
 	if (p) {
 		util_bias_dequeue(rq, p);
 		propagate_negative_bias(p);
+		if (p->pid == rq->max_uclamp_factor_pid) {
+			/*
+			 * If the task with the highest uclamp_factor gets
+			 * dequeued, the correct thing to do is to set pid and
+			 * factor to the second highest. However, the overhead
+			 * isn't really necessary because the second highest
+			 * will set these fields the next time it gets updated
+			 * anyway.
+			 */
+			rq->max_uclamp_factor_pid = -1;
+			rq->max_uclamp_factor = 0;
+		}
 	}
 
 	if (rq_h_nr_queued && !rq->cfs.h_nr_queued)
kernel/sched/pelt.c +29 -4
index f38abe6f0b8b..e96ca045af2e 100644
@@ -271,8 +271,8 @@ ___update_load_avg(struct sched_avg *sa, unsigned long load)
 static void util_bias_update(struct task_struct *p)
 {
 	unsigned int util, uclamp_min, uclamp_max;
-	struct rq *rq;
-	int old, new;
+	struct rq *rq = task_rq(p);
+	int old, new, clamped_util, prio = p->prio - MAX_RT_PRIO;
 
 	util = READ_ONCE(p->se.avg.util_avg);
 	uclamp_min = uclamp_eff_value(p, UCLAMP_MIN);
@@ -284,12 +284,37 @@ static void util_bias_update(struct task_struct *p)
 	if (uclamp_max == SCHED_CAPACITY_SCALE)
 		uclamp_max = UINT_MAX;
 	old = READ_ONCE(p->se.avg.util_avg_bias);
-	new = (int)clamp(util, uclamp_min, uclamp_max) - (int)util;
+	clamped_util = (int)clamp(util, uclamp_min, uclamp_max);
+	if (p->se.on_rq && prio >= 0) {
+		/* We only do this for fair class priorities. */
+		u64 uclamp_factor = sched_prio_to_wmult[prio];
+
+		/* This has to be a 64-bit multiplication. */
+		uclamp_factor *= clamped_util;
+		if (rq->max_uclamp_factor_pid == p->pid) {
+			rq->max_uclamp_factor = uclamp_factor;
+		} else if (uclamp_factor > rq->max_uclamp_factor) {
+			rq->max_uclamp_factor = uclamp_factor;
+			rq->max_uclamp_factor_pid = p->pid;
+		} else {
+			u32 weight = sched_prio_to_weight[prio];
+
+			/*
+			 * We cannot throttle too much if some other task is
+			 * running at high utilization. We should prioritize
+			 * giving that task enough utilization and respect
+			 * task priority, before enforcing uclamp_max.
+			 */
+			uclamp_max = max(uclamp_max,
+				(rq->max_uclamp_factor * weight) >> 32);
+			clamped_util = (int)clamp(util, uclamp_min, uclamp_max);
+		}
+	}
+	new = clamped_util - (int)util;
 
 	WRITE_ONCE(p->se.avg.util_avg_bias, new);
 	if (!p->se.on_rq)
 		return;
-	rq = task_rq(p);
 	WRITE_ONCE(rq->cfs.avg.util_avg_bias,
 		   READ_ONCE(rq->cfs.avg.util_avg_bias) + new - old);
 }
kernel/sched/sched.h +2 -0
index 654eede62979..0dc90208ad73 100644
@@ -1086,6 +1086,8 @@ struct rq {
 	u64			nr_switches;
 
 #ifdef CONFIG_UCLAMP_TASK
+	u64			max_uclamp_factor;
+	pid_t			max_uclamp_factor_pid;
 #endif
 
 	struct cfs_rq		cfs;
Signature · 1 line
2.34.1
#9 lore raw
In reply to Hongyan Xia

Hi Hongyan,

Quoted 125 lines · Hongyan Xia

This series gives an alternative implementation that addresses some of the problems in uclamp max aggregation. Sum aggregation mostly gives:

1. Simplicity. Sum aggregation implements uclamp with less than half of code than max aggregation.
2. Effectiveness. Sum aggregation shows better uclamp effectiveness, either in benchmark scores or sometimes in energy efficiency.

The key idea of sum aggregation is fairly simple. Each task has a util_avg_bias, which is obtained by:

    util_avg_bias = clamp(util_avg, uclamp_min, uclamp_max) - util_avg;

If a CPU has N tasks, p1, p2, p3... pN, then we sum the biases up and obtain a rq total bias:

    rq_bias = util_avg_bias1 + util_avg_bias2... + util_avg_biasN;

Then we use the biased rq utilization rq_util + rq_bias to select OPP and to schedule tasks.

PATCH BREAKDOWN:

Patch 1/6 reverts a patch that accommodate uclamp_max tasks under max aggregation. This patch is not needed and creates other problems for sum aggregation. It is discussed elsewhere that this patch will be improved and there may not be the need to revert it in the future.

Patch 2, 3 and 4 implement sum aggregation.

Patch 5 and 6 remove max aggregation.

Patch 7 applies PELT decay on negative util_avg_bias. This improves energy efficiency and task placement, but is not strictly necessary.

Patch 8 addresses sum aggregation under-utilization problem.

TESTING:

Two notebooks are shared at

https://nbviewer.org/github/honxia02/notebooks/blob/aac12d9becae2b2fe4690cbb672439fd884ede30/whitebox/max.ipynb
https://nbviewer.org/github/honxia02/notebooks/blob/aac12d9becae2b2fe4690cbb672439fd884ede30/whitebox/sum-offset.ipynb

The experiments done in notebooks are on Arm Juno r2 board. CPU0-3 are little cores with capacity of 383. CPU4-5 are big cores. The rt-app profiles used for these experiments are included in the notebooks.

Scenario 1: Scheduling 4 tasks with UCLAMP_MAX at 110.

The scheduling decisions are plotted in Out[11]. Both max and sum aggregation understand the UCLAMP_MAX hint and schedule all 4 tasks on the little cluster. Max aggregation sometimes schedule 2 tasks on 1 CPU, and this is the reason why sum aggregation reverts the 1st commit.

Scenario 2: Scheduling 4 tasks with UCLAMP_MIN and UCLAMP_MAX at a value slightly above the capacity of the little CPU.

Results are in Out[17]. The purpose is to use UCLAMP_MIN to place tasks on the big core. Both max and sum aggregation handle this correctly.

Scenario 3: Task A is a task with a small utilization pinned to CPU4. Task B is an always-running task pinned to CPU5, but UCLAMP_MAX capped at 300. After a while, task A is then pinned to CPU5, joining B.

Results are in Out[23]. Max aggregation sees a frequency spike at 873.64s. When zoomed in, one can see square-wave-like utilization values because of A periodically going to sleep. When A wakes up, its default UCLAMP_MAX of 1024 will uncap B and reach the highest CPU frequency. When A sleeps, B's UCLAMP_MAX will be in effect and will reduce rq utilization. This happens repeatedly, hence the square wave. In contrast, sum aggregation sees a normal increase in utilization when A joins B, without any square-wave behavior.

Scenario 4: 4 always-running tasks with UCLAMP_MAX of 110 pinned to the little PD (CPU0-3). 4 same tasks pinned to the big PD (CPU4-5). After a while, remove the CPU pinning of the 4 tasks on the big PD.

Results are in Out[29]. After unpinning, max aggregation moves all 8 tasks to the little cluster, but schedules 5 tasks on CPU0 and 1 each on CPU1-3. In contrast, sum aggregation schedules 2 on each little CPU after unpinning, which is the desired balanced task placement.

EVALUATION:

We backport patches to GKI kernel v6.1 on Pixel 9 and run Android benchmarks.

Speedometer:

We run Speedometer 2.1 on Chrome v131 to test ADPF/uclamp effectiveness. Because sum aggregation does not circumvent the 25% OPP margin, we reduce uclamp values to 80% to be fair.

|   score   | score |   %    | CPU power % |
|    max    | 192.4 |        |             |
|  sum_0.8  | 230.8 | +19.96 |   +31.54    |
| sum_tuned | 201.8 |  +4.89 |    -0.41    |

We see a consistant higher score and higher average power consumption. Note that a higher score also means a reduction in run-time, total energy increase for sum_0.8 is only 9.65%.

We then reduce uclamp values so that power consumption is roughly the same. If we do so, then sum aggregation achieves slightly better scores, shown in the sum_tuned row.

UIBench:

|   score   | jank percentage |   %    | CPU power (mW) |   %   |
|    max    |     0.115%      |        |     158.1      |       |
|  sum_0.8  |     0.129%      | +11.96 |     154.9      | -4.19 |

UIBench on Pixel 9 by default already has a low enough jank percentage. Moving to sum aggregation gives slightly higher jank percentage and lower power consumption.

---
Changed in v2:
- Completely separate uclamp component from PELT and util_est.
- Separate util_est_uclamp into an individual patch.
- Address the under-utilization problem.
- Update Python notebooks to reflect the latest sched/tip.

Quoted 7 lines

Hongyan Xia (8):
Revert "sched/uclamp: Set max_spare_cap_cpu even if max_spare_cap is

    0"

sched/uclamp: Track a new util_avg_bias signal sched/uclamp: Add util_est_uclamp
sched/fair: Use util biases for utilization and frequency sched/uclamp: Remove all uclamp bucket logic

I’ve recently been looking into the issue with uclamp and delayed-dequeue, and I found that uclamp_rq_inc should be placed before enqueue_task, which led to a patch.
Before sending the patch, I came across your series of patches. I haven’t fully understood your patch yet, but it seems like uclamp_rq_inc is no longer needed.
Do you think the patch below is still necessary?

--->

Subject: [PATCH] sched/uclamp: Update the rq's uclamp before enqueue task

When task's uclamp is set, we hope that the CPU frequency can increase as quickly as possible when the task is enqueued. Because the cpu frequency updating happens during the enqueue_task(), so the rq's uclamp needs to be updated before the task is enqueued. For sched-delayed tasks, the rq uclamp should only be updated when they are enqueued upon being awakened.

---
 kernel/sched/core.c | 14 ++++++--------
 1 file changed, 6 insertions(+), 8 deletions(-)
kernel/sched/core.c +0 -0
index 67189907214d..b07e78910221 100644
@@ -1747,7 +1747,7 @@ static inline void uclamp_rq_dec_id(struct rq

*rq, struct task_struct *p,

        }

}

-static inline void uclamp_rq_inc(struct rq *rq, struct task_struct *p) +static inline void uclamp_rq_inc(struct rq *rq, struct task_struct *p, int flags)

 {
        enum uclamp_id clamp_id;

@@ -1763,7 +1763,8 @@ static inline void uclamp_rq_inc(struct rq *rq, struct task_struct *p)

        if (unlikely(!p->sched_class->uclamp_enabled))
                return;
-       if (p->se.sched_delayed)
+       /* Only inc the delayed task which is being woken up. */
+       if (p->se.sched_delayed && !(flags & ENQUEUE_DELAYED))
                return;

        for_each_clamp_id(clamp_id)

@@ -2031,7 +2032,7 @@ static void __init init_uclamp(void)
}

#else /* !CONFIG_UCLAMP_TASK */
-static inline void uclamp_rq_inc(struct rq *rq, struct task_struct *p) { } +static inline void uclamp_rq_inc(struct rq *rq, struct task_struct *p, int flags) { }
static inline void uclamp_rq_dec(struct rq *rq, struct task_struct *p) { } static inline void uclamp_fork(struct task_struct *p) { } static inline void uclamp_post_fork(struct task_struct *p) { }
@@ -2067,12 +2068,9 @@ void enqueue_task(struct rq *rq, struct task_struct *p, int flags)
if (!(flags & ENQUEUE_NOCLOCK))
update_rq_clock(rq);

+ uclamp_rq_inc(rq, p, flags);
+

        p->sched_class->enqueue_task(rq, p, flags);
-       /*
-        * Must be after ->enqueue_task() because ENQUEUE_DELAYED can clear
-        * ->sched_delayed.
-        */

- uclamp_rq_inc(rq, p);

        psi_enqueue(p, flags);

--

Thanks!

BR
---

Quoted 20 lines

sched/uclamp: Simplify uclamp_eff_value()
sched/uclamp: Propagate negative bias
sched/uclamp: Solve under-utilization problem

 include/linux/sched.h            |   8 +-
 init/Kconfig                     |  32 ---
 kernel/sched/core.c              | 308 ++--------------------
 kernel/sched/cpufreq_schedutil.c |   6 +-
 kernel/sched/debug.c             |   2 +-
 kernel/sched/fair.c              | 430 ++++++++++++++++---------------
 kernel/sched/pelt.c              |  62 +++++
 kernel/sched/rt.c                |   4 -
 kernel/sched/sched.h             | 132 +++-------
 kernel/sched/syscalls.c          |   2 +
 10 files changed, 341 insertions(+), 645 deletions(-)

--
2.34.1

#10 lore raw

Hi Xuewen,

Quoted 136 lines · Xuewen Yan

Hi Hongyan,

Quoted 125 lines · Hongyan Xia

This series gives an alternative implementation that addresses some of the problems in uclamp max aggregation. Sum aggregation mostly gives:

1. Simplicity. Sum aggregation implements uclamp with less than half of code than max aggregation.
2. Effectiveness. Sum aggregation shows better uclamp effectiveness, either in benchmark scores or sometimes in energy efficiency.

The key idea of sum aggregation is fairly simple. Each task has a util_avg_bias, which is obtained by:

     util_avg_bias = clamp(util_avg, uclamp_min, uclamp_max) - util_avg;

If a CPU has N tasks, p1, p2, p3... pN, then we sum the biases up and obtain a rq total bias:

     rq_bias = util_avg_bias1 + util_avg_bias2... + util_avg_biasN;

Then we use the biased rq utilization rq_util + rq_bias to select OPP and to schedule tasks.

PATCH BREAKDOWN:

Patch 1/6 reverts a patch that accommodate uclamp_max tasks under max aggregation. This patch is not needed and creates other problems for sum aggregation. It is discussed elsewhere that this patch will be improved and there may not be the need to revert it in the future.

Patch 2, 3 and 4 implement sum aggregation.

Patch 5 and 6 remove max aggregation.

Patch 7 applies PELT decay on negative util_avg_bias. This improves energy efficiency and task placement, but is not strictly necessary.

Patch 8 addresses sum aggregation under-utilization problem.

TESTING:

Two notebooks are shared at

https://nbviewer.org/github/honxia02/notebooks/blob/aac12d9becae2b2fe4690cbb672439fd884ede30/whitebox/max.ipynb
https://nbviewer.org/github/honxia02/notebooks/blob/aac12d9becae2b2fe4690cbb672439fd884ede30/whitebox/sum-offset.ipynb

The experiments done in notebooks are on Arm Juno r2 board. CPU0-3 are little cores with capacity of 383. CPU4-5 are big cores. The rt-app profiles used for these experiments are included in the notebooks.

Scenario 1: Scheduling 4 tasks with UCLAMP_MAX at 110.

The scheduling decisions are plotted in Out[11]. Both max and sum aggregation understand the UCLAMP_MAX hint and schedule all 4 tasks on the little cluster. Max aggregation sometimes schedule 2 tasks on 1 CPU, and this is the reason why sum aggregation reverts the 1st commit.

Scenario 2: Scheduling 4 tasks with UCLAMP_MIN and UCLAMP_MAX at a value slightly above the capacity of the little CPU.

Results are in Out[17]. The purpose is to use UCLAMP_MIN to place tasks on the big core. Both max and sum aggregation handle this correctly.

Scenario 3: Task A is a task with a small utilization pinned to CPU4. Task B is an always-running task pinned to CPU5, but UCLAMP_MAX capped at 300. After a while, task A is then pinned to CPU5, joining B.

Results are in Out[23]. Max aggregation sees a frequency spike at 873.64s. When zoomed in, one can see square-wave-like utilization values because of A periodically going to sleep. When A wakes up, its default UCLAMP_MAX of 1024 will uncap B and reach the highest CPU frequency. When A sleeps, B's UCLAMP_MAX will be in effect and will reduce rq utilization. This happens repeatedly, hence the square wave. In contrast, sum aggregation sees a normal increase in utilization when A joins B, without any square-wave behavior.

Scenario 4: 4 always-running tasks with UCLAMP_MAX of 110 pinned to the little PD (CPU0-3). 4 same tasks pinned to the big PD (CPU4-5). After a while, remove the CPU pinning of the 4 tasks on the big PD.

Results are in Out[29]. After unpinning, max aggregation moves all 8 tasks to the little cluster, but schedules 5 tasks on CPU0 and 1 each on CPU1-3. In contrast, sum aggregation schedules 2 on each little CPU after unpinning, which is the desired balanced task placement.

EVALUATION:

We backport patches to GKI kernel v6.1 on Pixel 9 and run Android benchmarks.

Speedometer:

We run Speedometer 2.1 on Chrome v131 to test ADPF/uclamp effectiveness. Because sum aggregation does not circumvent the 25% OPP margin, we reduce uclamp values to 80% to be fair.

|   score   | score |   %    | CPU power % |
|    max    | 192.4 |        |             |
|  sum_0.8  | 230.8 | +19.96 |   +31.54    |
| sum_tuned | 201.8 |  +4.89 |    -0.41    |

We see a consistant higher score and higher average power consumption. Note that a higher score also means a reduction in run-time, total energy increase for sum_0.8 is only 9.65%.

We then reduce uclamp values so that power consumption is roughly the same. If we do so, then sum aggregation achieves slightly better scores, shown in the sum_tuned row.

UIBench:

|   score   | jank percentage |   %    | CPU power (mW) |   %   |
|    max    |     0.115%      |        |     158.1      |       |
|  sum_0.8  |     0.129%      | +11.96 |     154.9      | -4.19 |

UIBench on Pixel 9 by default already has a low enough jank percentage. Moving to sum aggregation gives slightly higher jank percentage and lower power consumption.

---
Changed in v2:
- Completely separate uclamp component from PELT and util_est.
- Separate util_est_uclamp into an individual patch.
- Address the under-utilization problem.
- Update Python notebooks to reflect the latest sched/tip.

Quoted 7 lines

Hongyan Xia (8):
Revert "sched/uclamp: Set max_spare_cap_cpu even if max_spare_cap is

     0"

sched/uclamp: Track a new util_avg_bias signal sched/uclamp: Add util_est_uclamp
sched/fair: Use util biases for utilization and frequency sched/uclamp: Remove all uclamp bucket logic

Quoted 8 lines

I’ve recently been looking into the issue with uclamp and delayed-dequeue, and I found that uclamp_rq_inc should be placed before enqueue_task, which led to a patch.
Before sending the patch, I came across your series of patches. I haven’t fully understood your patch yet, but it seems like uclamp_rq_inc is no longer needed.
Do you think the patch below is still necessary?

I posted a fix of the issue you mentioned days ago here

https://lore.kernel.org/lkml/84441660bef0a5e67fd09dc3787178d0276dad31.1740664400.git.hongyan.xia2@arm.com/

I think we found the same issue, but our approaches are different. I fear that as more complexity goes into each sched_class like delayed dequeue, it's better to just let the sched_class handle how uclamp is enqueued and dequeued within itself rather than leaking into core.c. Would be nice if you could take a look at my fix.

Your patch is definitely necessary. The thing with this uclamp sum aggregation series is that there are still debates around it and it might take a while before everything is settled. So, at the moment we should view this series and the uclamp enqueue fix as separate things.

Quoted 95 lines

--->

Subject: [PATCH] sched/uclamp: Update the rq's uclamp before enqueue task

When task's uclamp is set, we hope that the CPU frequency can increase as quickly as possible when the task is enqueued. Because the cpu frequency updating happens during the enqueue_task(), so the rq's uclamp needs to be updated before the task is enqueued. For sched-delayed tasks, the rq uclamp should only be updated when they are enqueued upon being awakened.

---
  kernel/sched/core.c | 14 ++++++--------
  1 file changed, 6 insertions(+), 8 deletions(-)
kernel/sched/core.c +1 -1
index 67189907214d..b07e78910221 100644
@@ -1747,7 +1747,7 @@ static inline void uclamp_rq_dec_id(struct rq
 *rq, struct task_struct *p,
         }
  }
 
-static inline void uclamp_rq_inc(struct rq *rq, struct task_struct *p)
+static inline void uclamp_rq_inc(struct rq *rq, struct task_struct
 *p, int flags)
  {
         enum uclamp_id clamp_id;
kernel/sched/core.c +2 -1
@@ -1763,7 +1763,8 @@ static inline void uclamp_rq_inc(struct rq *rq,
 struct task_struct *p)
         if (unlikely(!p->sched_class->uclamp_enabled))
                 return;
 
-       if (p->se.sched_delayed)
+       /* Only inc the delayed task which is being woken up. */
+       if (p->se.sched_delayed && !(flags & ENQUEUE_DELAYED))
                 return;
 

for_each_clamp_id(clamp_id)

kernel/sched/core.c +1 -1
@@ -2031,7 +2032,7 @@ static void __init init_uclamp(void)
  }
 
  #else /* !CONFIG_UCLAMP_TASK */
-static inline void uclamp_rq_inc(struct rq *rq, struct task_struct *p) { }
+static inline void uclamp_rq_inc(struct rq *rq, struct task_struct
 *p, int flags) { }
  static inline void uclamp_rq_dec(struct rq *rq, struct task_struct *p) { }
  static inline void uclamp_fork(struct task_struct *p) { }

static inline void uclamp_post_fork(struct task_struct *p) { }

kernel/sched/core.c +2 -6
@@ -2067,12 +2068,9 @@ void enqueue_task(struct rq *rq, struct
 task_struct *p, int flags)
         if (!(flags & ENQUEUE_NOCLOCK))
                 update_rq_clock(rq);
 
+       uclamp_rq_inc(rq, p, flags);
+
         p->sched_class->enqueue_task(rq, p, flags);
-       /*
-        * Must be after ->enqueue_task() because ENQUEUE_DELAYED can clear
-        * ->sched_delayed.
-        */
-       uclamp_rq_inc(rq, p);
 
         psi_enqueue(p, flags);
 
--

Thanks!

BR
---

Quoted 20 lines

sched/uclamp: Simplify uclamp_eff_value()
sched/uclamp: Propagate negative bias
sched/uclamp: Solve under-utilization problem

  include/linux/sched.h            |   8 +-
  init/Kconfig                     |  32 ---
  kernel/sched/core.c              | 308 ++--------------------
  kernel/sched/cpufreq_schedutil.c |   6 +-
  kernel/sched/debug.c             |   2 +-
  kernel/sched/fair.c              | 430 ++++++++++++++++---------------
  kernel/sched/pelt.c              |  62 +++++
  kernel/sched/rt.c                |   4 -
  kernel/sched/sched.h             | 132 +++-------
  kernel/sched/syscalls.c          |   2 +
  10 files changed, 341 insertions(+), 645 deletions(-)

--
2.34.1

#11 lore raw
Quoted 159 lines · Hongyan Xia

Hi Xuewen,

Quoted 136 lines · Xuewen Yan

Hi Hongyan,

Quoted 125 lines · Hongyan Xia

This series gives an alternative implementation that addresses some of the problems in uclamp max aggregation. Sum aggregation mostly gives:

1. Simplicity. Sum aggregation implements uclamp with less than half of code than max aggregation.
2. Effectiveness. Sum aggregation shows better uclamp effectiveness, either in benchmark scores or sometimes in energy efficiency.

The key idea of sum aggregation is fairly simple. Each task has a util_avg_bias, which is obtained by:

     util_avg_bias = clamp(util_avg, uclamp_min, uclamp_max) - util_avg;

If a CPU has N tasks, p1, p2, p3... pN, then we sum the biases up and obtain a rq total bias:

     rq_bias = util_avg_bias1 + util_avg_bias2... + util_avg_biasN;

Then we use the biased rq utilization rq_util + rq_bias to select OPP and to schedule tasks.

PATCH BREAKDOWN:

Patch 1/6 reverts a patch that accommodate uclamp_max tasks under max aggregation. This patch is not needed and creates other problems for sum aggregation. It is discussed elsewhere that this patch will be improved and there may not be the need to revert it in the future.

Patch 2, 3 and 4 implement sum aggregation.

Patch 5 and 6 remove max aggregation.

Patch 7 applies PELT decay on negative util_avg_bias. This improves energy efficiency and task placement, but is not strictly necessary.

Patch 8 addresses sum aggregation under-utilization problem.

TESTING:

Two notebooks are shared at

https://nbviewer.org/github/honxia02/notebooks/blob/aac12d9becae2b2fe4690cbb672439fd884ede30/whitebox/max.ipynb
https://nbviewer.org/github/honxia02/notebooks/blob/aac12d9becae2b2fe4690cbb672439fd884ede30/whitebox/sum-offset.ipynb

The experiments done in notebooks are on Arm Juno r2 board. CPU0-3 are little cores with capacity of 383. CPU4-5 are big cores. The rt-app profiles used for these experiments are included in the notebooks.

Scenario 1: Scheduling 4 tasks with UCLAMP_MAX at 110.

The scheduling decisions are plotted in Out[11]. Both max and sum aggregation understand the UCLAMP_MAX hint and schedule all 4 tasks on the little cluster. Max aggregation sometimes schedule 2 tasks on 1 CPU, and this is the reason why sum aggregation reverts the 1st commit.

Scenario 2: Scheduling 4 tasks with UCLAMP_MIN and UCLAMP_MAX at a value slightly above the capacity of the little CPU.

Results are in Out[17]. The purpose is to use UCLAMP_MIN to place tasks on the big core. Both max and sum aggregation handle this correctly.

Scenario 3: Task A is a task with a small utilization pinned to CPU4. Task B is an always-running task pinned to CPU5, but UCLAMP_MAX capped at 300. After a while, task A is then pinned to CPU5, joining B.

Results are in Out[23]. Max aggregation sees a frequency spike at 873.64s. When zoomed in, one can see square-wave-like utilization values because of A periodically going to sleep. When A wakes up, its default UCLAMP_MAX of 1024 will uncap B and reach the highest CPU frequency. When A sleeps, B's UCLAMP_MAX will be in effect and will reduce rq utilization. This happens repeatedly, hence the square wave. In contrast, sum aggregation sees a normal increase in utilization when A joins B, without any square-wave behavior.

Scenario 4: 4 always-running tasks with UCLAMP_MAX of 110 pinned to the little PD (CPU0-3). 4 same tasks pinned to the big PD (CPU4-5). After a while, remove the CPU pinning of the 4 tasks on the big PD.

Results are in Out[29]. After unpinning, max aggregation moves all 8 tasks to the little cluster, but schedules 5 tasks on CPU0 and 1 each on CPU1-3. In contrast, sum aggregation schedules 2 on each little CPU after unpinning, which is the desired balanced task placement.

EVALUATION:

We backport patches to GKI kernel v6.1 on Pixel 9 and run Android benchmarks.

Speedometer:

We run Speedometer 2.1 on Chrome v131 to test ADPF/uclamp effectiveness. Because sum aggregation does not circumvent the 25% OPP margin, we reduce uclamp values to 80% to be fair.

|   score   | score |   %    | CPU power % |
|    max    | 192.4 |        |             |
|  sum_0.8  | 230.8 | +19.96 |   +31.54    |
| sum_tuned | 201.8 |  +4.89 |    -0.41    |

We see a consistant higher score and higher average power consumption. Note that a higher score also means a reduction in run-time, total energy increase for sum_0.8 is only 9.65%.

We then reduce uclamp values so that power consumption is roughly the same. If we do so, then sum aggregation achieves slightly better scores, shown in the sum_tuned row.

UIBench:

|   score   | jank percentage |   %    | CPU power (mW) |   %   |
|    max    |     0.115%      |        |     158.1      |       |
|  sum_0.8  |     0.129%      | +11.96 |     154.9      | -4.19 |

UIBench on Pixel 9 by default already has a low enough jank percentage. Moving to sum aggregation gives slightly higher jank percentage and lower power consumption.

---
Changed in v2:
- Completely separate uclamp component from PELT and util_est.
- Separate util_est_uclamp into an individual patch.
- Address the under-utilization problem.
- Update Python notebooks to reflect the latest sched/tip.

Quoted 7 lines

Hongyan Xia (8):
Revert "sched/uclamp: Set max_spare_cap_cpu even if max_spare_cap is

     0"

sched/uclamp: Track a new util_avg_bias signal sched/uclamp: Add util_est_uclamp
sched/fair: Use util biases for utilization and frequency sched/uclamp: Remove all uclamp bucket logic

Quoted 8 lines

I’ve recently been looking into the issue with uclamp and delayed-dequeue, and I found that uclamp_rq_inc should be placed before enqueue_task, which led to a patch.
Before sending the patch, I came across your series of patches. I haven’t fully understood your patch yet, but it seems like uclamp_rq_inc is no longer needed.
Do you think the patch below is still necessary?

I posted a fix of the issue you mentioned days ago here

https://lore.kernel.org/lkml/84441660bef0a5e67fd09dc3787178d0276dad31.1740664400.git.hongyan.xia2@arm.com/

I think we found the same issue, but our approaches are different. I fear that as more complexity goes into each sched_class like delayed dequeue, it's better to just let the sched_class handle how uclamp is enqueued and dequeued within itself rather than leaking into core.c. Would be nice if you could take a look at my fix.

Quoted 4 lines

Your patch is definitely necessary. The thing with this uclamp sum aggregation series is that there are still debates around it and it might take a while before everything is settled. So, at the moment we should view this series and the uclamp enqueue fix as separate things.

Okay, I will look at the patch.

Thanks!

Quoted 97 lines
Quoted 96 lines

--->

Subject: [PATCH] sched/uclamp: Update the rq's uclamp before enqueue task

When task's uclamp is set, we hope that the CPU frequency can increase as quickly as possible when the task is enqueued. Because the cpu frequency updating happens during the enqueue_task(), so the rq's uclamp needs to be updated before the task is enqueued. For sched-delayed tasks, the rq uclamp should only be updated when they are enqueued upon being awakened.

---
  kernel/sched/core.c | 14 ++++++--------
  1 file changed, 6 insertions(+), 8 deletions(-)
kernel/sched/core.c +1 -1
index 67189907214d..b07e78910221 100644
@@ -1747,7 +1747,7 @@ static inline void uclamp_rq_dec_id(struct rq
 *rq, struct task_struct *p,
         }
  }
 
-static inline void uclamp_rq_inc(struct rq *rq, struct task_struct *p)
+static inline void uclamp_rq_inc(struct rq *rq, struct task_struct
 *p, int flags)
  {
         enum uclamp_id clamp_id;
kernel/sched/core.c +2 -1
@@ -1763,7 +1763,8 @@ static inline void uclamp_rq_inc(struct rq *rq,
 struct task_struct *p)
         if (unlikely(!p->sched_class->uclamp_enabled))
                 return;
 
-       if (p->se.sched_delayed)
+       /* Only inc the delayed task which is being woken up. */
+       if (p->se.sched_delayed && !(flags & ENQUEUE_DELAYED))
                 return;
 

for_each_clamp_id(clamp_id)

kernel/sched/core.c +1 -1
@@ -2031,7 +2032,7 @@ static void __init init_uclamp(void)
  }
 
  #else /* !CONFIG_UCLAMP_TASK */
-static inline void uclamp_rq_inc(struct rq *rq, struct task_struct *p) { }
+static inline void uclamp_rq_inc(struct rq *rq, struct task_struct
 *p, int flags) { }
  static inline void uclamp_rq_dec(struct rq *rq, struct task_struct *p) { }
  static inline void uclamp_fork(struct task_struct *p) { }

static inline void uclamp_post_fork(struct task_struct *p) { }

kernel/sched/core.c +2 -6
@@ -2067,12 +2068,9 @@ void enqueue_task(struct rq *rq, struct
 task_struct *p, int flags)
         if (!(flags & ENQUEUE_NOCLOCK))
                 update_rq_clock(rq);
 
+       uclamp_rq_inc(rq, p, flags);
+
         p->sched_class->enqueue_task(rq, p, flags);
-       /*
-        * Must be after ->enqueue_task() because ENQUEUE_DELAYED can clear
-        * ->sched_delayed.
-        */
-       uclamp_rq_inc(rq, p);
 
         psi_enqueue(p, flags);
 
--

Thanks!

BR
---

Quoted 20 lines

sched/uclamp: Simplify uclamp_eff_value()
sched/uclamp: Propagate negative bias
sched/uclamp: Solve under-utilization problem

  include/linux/sched.h            |   8 +-
  init/Kconfig                     |  32 ---
  kernel/sched/core.c              | 308 ++--------------------
  kernel/sched/cpufreq_schedutil.c |   6 +-
  kernel/sched/debug.c             |   2 +-
  kernel/sched/fair.c              | 430 ++++++++++++++++---------------
  kernel/sched/pelt.c              |  62 +++++
  kernel/sched/rt.c                |   4 -
  kernel/sched/sched.h             | 132 +++-------
  kernel/sched/syscalls.c          |   2 +
  10 files changed, 341 insertions(+), 645 deletions(-)

--
2.34.1

#12 lore raw
Quoted 8 lines · Xuewen Yan
Quoted 7 lines · Hongyan Xia

Hi Xuewen,

Quoted 3 lines · Xuewen Yan

Hi Hongyan,

On Tue, Mar 4, 2025 at 10:26 PM Hongyan Xia <[email protected]> wrote:

[...]

Quoted 64 lines
Quoted 64 lines
Quoted 64 lines

Subject: [PATCH] sched/uclamp: Update the rq's uclamp before enqueue task

When task's uclamp is set, we hope that the CPU frequency can increase as quickly as possible when the task is enqueued. Because the cpu frequency updating happens during the enqueue_task(), so the rq's uclamp needs to be updated before the task is enqueued. For sched-delayed tasks, the rq uclamp should only be updated when they are enqueued upon being awakened.

---
  kernel/sched/core.c | 14 ++++++--------
  1 file changed, 6 insertions(+), 8 deletions(-)
kernel/sched/core.c +1 -1
index 67189907214d..b07e78910221 100644
@@ -1747,7 +1747,7 @@ static inline void uclamp_rq_dec_id(struct rq
 *rq, struct task_struct *p,
         }
  }
 
-static inline void uclamp_rq_inc(struct rq *rq, struct task_struct *p)
+static inline void uclamp_rq_inc(struct rq *rq, struct task_struct
 *p, int flags)
  {
         enum uclamp_id clamp_id;
kernel/sched/core.c +2 -1
@@ -1763,7 +1763,8 @@ static inline void uclamp_rq_inc(struct rq *rq,
 struct task_struct *p)
         if (unlikely(!p->sched_class->uclamp_enabled))
                 return;
 
-       if (p->se.sched_delayed)
+       /* Only inc the delayed task which is being woken up. */
+       if (p->se.sched_delayed && !(flags & ENQUEUE_DELAYED))
                 return;
 

for_each_clamp_id(clamp_id)

kernel/sched/core.c +1 -1
@@ -2031,7 +2032,7 @@ static void __init init_uclamp(void)
  }
 
  #else /* !CONFIG_UCLAMP_TASK */
-static inline void uclamp_rq_inc(struct rq *rq, struct task_struct *p) { }
+static inline void uclamp_rq_inc(struct rq *rq, struct task_struct
 *p, int flags) { }
  static inline void uclamp_rq_dec(struct rq *rq, struct task_struct *p) { }
  static inline void uclamp_fork(struct task_struct *p) { }

static inline void uclamp_post_fork(struct task_struct *p) { }

kernel/sched/core.c +2 -5
@@ -2067,12 +2068,9 @@ void enqueue_task(struct rq *rq, struct
 task_struct *p, int flags)
         if (!(flags & ENQUEUE_NOCLOCK))
                 update_rq_clock(rq);
 
+       uclamp_rq_inc(rq, p, flags);
+
         p->sched_class->enqueue_task(rq, p, flags);
-       /*
-        * Must be after ->enqueue_task() because ENQUEUE_DELAYED can clear
-        * ->sched_delayed.
-        */
-       uclamp_rq_inc(rq, p);
 
Quoted 1 line
Quoted 1 line

kernel/sched/core.c void enqueue_task(struct rq *rq, struct · line 2076

Quoted 1 line
kernel/sched/core.c +0 -0
         psi_enqueue(p, flags);

Like I mentioned already in the original thread:

https://lkml.kernel.org/r/[email protected]

I would prefer that uclamp stays in core.c. ENQUEUE_DELAYED among all the other flags is already used there (ttwu_runnable()).

task_struct contains sched_{,rt_,dl_}entity}. We just have to be careful when switching policies.

--

Could you also incorporate the changes in {en,de}queue_task_fair() ((task_on_rq_migrating(p) || (flags & {RESTORE,DEQUEUE}_SAVE))) vs. (!p->se.sched_delayed || (flags & ENQUEUE_DELAYED)) and (!p->se.sched_delayed) so the uclamp-util_est relation is easier to spot?

[...]

#13 lore raw
Quoted 88 lines · Dietmar Eggemann
Quoted 8 lines · Xuewen Yan
Quoted 7 lines · Hongyan Xia

Hi Xuewen,

Quoted 3 lines · Xuewen Yan

Hi Hongyan,

On Tue, Mar 4, 2025 at 10:26 PM Hongyan Xia <[email protected]> wrote:

[...]

Quoted 64 lines
Quoted 64 lines
Quoted 64 lines

Subject: [PATCH] sched/uclamp: Update the rq's uclamp before enqueue task

When task's uclamp is set, we hope that the CPU frequency can increase as quickly as possible when the task is enqueued. Because the cpu frequency updating happens during the enqueue_task(), so the rq's uclamp needs to be updated before the task is enqueued. For sched-delayed tasks, the rq uclamp should only be updated when they are enqueued upon being awakened.

---
   kernel/sched/core.c | 14 ++++++--------
   1 file changed, 6 insertions(+), 8 deletions(-)
kernel/sched/core.c +1 -1
index 67189907214d..b07e78910221 100644
@@ -1747,7 +1747,7 @@ static inline void uclamp_rq_dec_id(struct rq
 *rq, struct task_struct *p,
          }
   }
 
-static inline void uclamp_rq_inc(struct rq *rq, struct task_struct *p)
+static inline void uclamp_rq_inc(struct rq *rq, struct task_struct
 *p, int flags)
   {
          enum uclamp_id clamp_id;
kernel/sched/core.c +2 -1
@@ -1763,7 +1763,8 @@ static inline void uclamp_rq_inc(struct rq *rq,
 struct task_struct *p)
          if (unlikely(!p->sched_class->uclamp_enabled))
                  return;
 
-       if (p->se.sched_delayed)
+       /* Only inc the delayed task which is being woken up. */
+       if (p->se.sched_delayed && !(flags & ENQUEUE_DELAYED))
                  return;
 

for_each_clamp_id(clamp_id)

kernel/sched/core.c +1 -1
@@ -2031,7 +2032,7 @@ static void __init init_uclamp(void)
   }
 
   #else /* !CONFIG_UCLAMP_TASK */
-static inline void uclamp_rq_inc(struct rq *rq, struct task_struct *p) { }
+static inline void uclamp_rq_inc(struct rq *rq, struct task_struct
 *p, int flags) { }
   static inline void uclamp_rq_dec(struct rq *rq, struct task_struct *p) { }
   static inline void uclamp_fork(struct task_struct *p) { }
   static inline void uclamp_post_fork(struct task_struct *p) { }
kernel/sched/core.c +2 -5
@@ -2067,12 +2068,9 @@ void enqueue_task(struct rq *rq, struct
 task_struct *p, int flags)
          if (!(flags & ENQUEUE_NOCLOCK))
                  update_rq_clock(rq);
 
+       uclamp_rq_inc(rq, p, flags);
+
          p->sched_class->enqueue_task(rq, p, flags);
-       /*
-        * Must be after ->enqueue_task() because ENQUEUE_DELAYED can clear
-        * ->sched_delayed.
-        */
-       uclamp_rq_inc(rq, p);
 
Quoted 1 line
Quoted 1 line

kernel/sched/core.c void enqueue_task(struct rq *rq, struct · line 2076

Quoted 1 line
kernel/sched/core.c +0 -0
          psi_enqueue(p, flags);

Like I mentioned already in the original thread:

https://lkml.kernel.org/r/[email protected]

I would prefer that uclamp stays in core.c. ENQUEUE_DELAYED among all the other flags is already used there (ttwu_runnable()).

task_struct contains sched_{,rt_,dl_}entity}. We just have to be careful when switching policies.

Quoted 8 lines

--

Could you also incorporate the changes in {en,de}queue_task_fair() ((task_on_rq_migrating(p) || (flags & {RESTORE,DEQUEUE}_SAVE))) vs. (!p->se.sched_delayed || (flags & ENQUEUE_DELAYED)) and (!p->se.sched_delayed) so the uclamp-util_est relation is easier to spot?

[...]

At the moment we can't do this. Sum aggregation was designed before delayed dequeue and it syncs with p->se.on_rq. If we sync with something else and take care of delayed dequeue cases (like util_est) then I have to rewrite part of the series.

#14 lore raw
Quoted 10 lines · Hongyan Xia
Quoted 9 lines · Dietmar Eggemann
Quoted 8 lines · Xuewen Yan
Quoted 7 lines · Hongyan Xia

Hi Xuewen,

Quoted 3 lines · Xuewen Yan

Hi Hongyan,

On Tue, Mar 4, 2025 at 10:26 PM Hongyan Xia <[email protected]>

[...]

Quoted 17 lines
Quoted 17 lines

Like I mentioned already in the original thread:

https://lkml.kernel.org/r/[email protected]

I would prefer that uclamp stays in core.c. ENQUEUE_DELAYED among all the other flags is already used there (ttwu_runnable()).

task_struct contains sched_{,rt_,dl_}entity}. We just have to be careful when switching policies.

Signature · 4 lines
Could you also incorporate the changes in {en,de}queue_task_fair()
((task_on_rq_migrating(p) || (flags & {RESTORE,DEQUEUE}_SAVE))) vs.
(!p->se.sched_delayed || (flags & ENQUEUE_DELAYED)) and
(!p->se.sched_delayed) so the uclamp-util_est relation is easier to spot?
Quoted 6 lines
Quoted 1 line

[...]

At the moment we can't do this. Sum aggregation was designed before delayed dequeue and it syncs with p->se.on_rq. If we sync with something else and take care of delayed dequeue cases (like util_est) then I have to rewrite part of the series.

Ah, OK! But this 'uclamp not in sync with util_est' is already an issue on today's mainline so I would like to see this fixed as discreet as possible and then another prep-patch for 'uclamp sum aggregation'. IMHO, then it's clearer why we would need more rework in this area.