* Re: [PATCH v2 1/2] sched/numa: Drive NUMA task tick from execution context
2026-09-03 21:30 ` Tim Chen
@ 2026-09-04 5:16 ` Hui Su
2026-09-04 14:10 ` Hui Su
2026-09-04 16:10 ` Chen Yu
2 siblings, 0 replies; 15+ messages in thread
From: Hui Su @ 2026-09-04 5:16 UTC (permalink / raw)
To: Tim Chen, Chen Yu
Cc: Hui Su, K Prateek Nayak, Juri Lelli, Vincent Guittot,
Dietmar Eggemann, Valentin Schneider, John Stultz, Ingo Molnar,
Peter Zijlstra, linux-kernel, chen.yu
On Thu, Sep 3, 2026 at 2:30 PM, Tim Chen wrote:
> On Thu, 2026-09-03 at 20:41 +0800, Chen, Yu C wrote:
> > Thanks for bringing this up. Previously Prateek has suggested to fix the
> > rq->donor issue [1] and unfortunately I missed the task_tick_cache() part.
> >
> > Regarding above line in the commit log, although I agree that
> > task_tick_numa() should be moved one level up, I did not quite get the
> > reason why sum_exec_runtime is mentioned here?
> > I guess what you mean is that, in task_tick_numa(), the
> > curr->se.sum_exec_runtime is used to check if there is a timeout to launch
> > the task_numa_work(), so curr->se.sum_exec_runtime has to be up-to-date.
> > With proxy execution, the se.sum_exec_runtime is only accumulated in
> > rq->curr rather than rq->donor, so passing a "paused"
> > rq->donor.sum_exec_runtime to task_tick_numa() is inaccurate?
>
> I think task_tick_core() needs to stay with the donor's context
> as it is the scheduling context.
>
> task_tick_core() is not about the execution context -- it decides
> whether the current scheduling context has used up enough of its slice
> to let a force-idled SMT sibling run. That slice belongs to the donor,
> so the donor is the right task to pass.
>
> There is a separate issue lurking here, task_tick_core() measures
> consumed slice as se->sum_exec_runtime - se->prev_sum_exec_runtime.
> Under proxy the donor's sum_exec_runtime does not advance, so that
> delta stays near zero and the force-idle resched may never trigger.
> Passing rq->curr does not fix it either. This is somewhat orthogonal
> to the issue that the execution context series is trying to solve.
> It should be fixed separately.
>
> I agree with you on this.
Hi Tim, Chenyu,
Thanks for the clarification.
The sum_exec_runtime paragraph was intended to point out that
task_tick_numa() uses execution runtime to drive NUMA scanning, but I
agree that this is too narrow. The more general reason is that
task_tick_numa() operates on execution-context state, including the task
and mm associated with the execution context. I'll reword the changelog
accordingly in v3.
For task_tick_core(), I agree that it should remain associated with the
donor, since the slice being evaluated belongs to the scheduling
context. The fact that the donor's sum_exec_runtime does not advance
under proxy execution is a separate issue, so I won't fold that into
this series.
I'll test the virtual-time prototype separately and follow up on that
issue independently if the approach holds up.
Thanks,
Hui
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH v2 1/2] sched/numa: Drive NUMA task tick from execution context
2026-09-03 21:30 ` Tim Chen
2026-09-04 5:16 ` Hui Su
@ 2026-09-04 14:10 ` Hui Su
2026-09-04 20:15 ` Tim Chen
2026-09-04 16:10 ` Chen Yu
2 siblings, 1 reply; 15+ messages in thread
From: Hui Su @ 2026-09-04 14:10 UTC (permalink / raw)
To: Tim Chen, Chen, Yu C
Cc: Hui Su, K Prateek Nayak, Juri Lelli, Vincent Guittot,
Dietmar Eggemann, Valentin Schneider, John Stultz, Ingo Molnar,
Peter Zijlstra, linux-kernel
On Thu, 2026-09-03 at 14:30 -0700, Tim Chen wrote:
> On Thu, 2026-09-03 at 20:41 +0800, Chen, Yu C wrote:
> > Hi Su,
> >
> > On 9/3/2026 12:11 PM, Hui Su wrote:
> > > Proxy execution separates the scheduling context in rq->donor from the
> > > execution context in rq->curr. sched_tick() invokes task_tick() for the
> > > donor's scheduling class.
> > >
> > > task_tick_numa() is currently called from task_tick_fair(). This works
> > > when the donor is a fair task, but not when a fair task executes on
> > > behalf of an RT or deadline donor. In that case the donor's task_tick()
> > > still updates the execution task's sum_exec_runtime through
> > > update_curr_common(), but task_tick_fair() is not invoked and NUMA scan
> > > work for the execution task is not driven.
> >
> > Thanks for bringing this up. Previously Prateek has suggested to fix the
> > rq->donor issue [1] and unfortunately I missed the task_tick_cache() part.
> >
> > Regarding above line in the commit log, although I agree that
> > task_tick_numa() should be moved one level up, I did not quite get the
> > reason why sum_exec_runtime is mentioned here, could you please elaborate
> > a little more?
> > I guess what you mean is that, in task_tick_numa(), the
> > curr->se.sum_exec_runtime is used to check if there is a timeout to launch
> > the task_numa_work(), so curr->se.sum_exec_runtime has to be up-to-date.
> > With proxy execution, the se.sum_exec_runtime is only accumulated in
> > rq->curr rather than rq->donor, so passing a "paused" rq->donor.sum_exec_runtime
> > to task_tick_numa() is inaccurate?
> >
> > But I also see that in task_tick_core(), the sum_exec_runtime is also
> > leveraged to calculate the delta "wall time" via __entity_slice_used():
> > se->sum_exec_runtime - se->prev_sum_exec_runtime
> > does it mean task_tick_core() also needs to be bring one level up to
> > sched_tick() and passed with rq->curr?
>
> I think task_tick_core() needs to stay with the donor's context
> as it is the scheduling context.
>
> task_tick_core() is not about the execution context --
> it decides whether the current scheduling context has used
> up enough of its slice to let a force-idled SMT sibling run. That
> slice belongs to the donor, so the donor is the right task to pass.
>
> There is a separate issue lurking here, task_tick_core() measures consumed slice as
> se->sum_exec_runtime - se->prev_sum_exec_runtime. Under proxy the
> donor's sum_exec_runtime does not advance (update_se() charges the
> runtime to rq->curr instead), so that delta stays near zero and the
> force-idle resched may never trigger.
>
> Passing rq->curr does not fix it either. __entity_slice_used() takes
> the runtime and the slice from the same entity, so passing rq->curr
> just compares the running task against its own slice. But this check
> is about the donor: it asks whether the scheduling context that owns
> the CPU has used up its slice. The running task is only borrowing the
> CPU through proxy, so its slice is not the one we care about here.
>
> Maybe something like below (only compile tested) to fix the issue.
> That said, this is somewhat orthogonal to the issue that the execution
> context series is trying to solve. It should be fixed separately.
>
> diff --git a/kernel/sched/fair.c b/kernel/sched/fair.c
> index 8dff37059faf..cd240bf52d03 100644
> --- a/kernel/sched/fair.c
> +++ b/kernel/sched/fair.c
> @@ -14748,10 +14748,29 @@ static void rq_offline_fair(struct rq *rq)
> static inline bool
> __entity_slice_used(struct sched_entity *se, int min_nr_tasks)
> {
> - u64 rtime = se->sum_exec_runtime - se->prev_sum_exec_runtime;
> - u64 slice = se->slice;
> + u64 vslice, vused;
>
> - return (rtime * min_nr_tasks > slice);
> + /*
> + * @se is the scheduling context (rq->donor). Under proxy execution
> + * it need not be the task executing on the CPU, so its
> + * sum_exec_runtime is not advanced and cannot be used to tell how
> + * much of its slice it has consumed. Its vruntime, however, is
> + * advanced by update_curr() with the proxy runtime, and its EEVDF
> + * deadline reflects the granted slice, so measure the consumed
> + * fraction in virtual time instead.
> + *
> + * This is equivalent to the previous real-time comparison in the
> + * non-proxy case: both @vused and @vslice are scaled by the same
> + * weight factor, so the ratio (and thus the min_nr_tasks test) is
> + * unchanged.
> + */
> + if (vruntime_cmp(se->vruntime, ">=", se->deadline))
> + return true;
> +
> + vslice = calc_delta_fair(se->slice, se);
> + vused = vslice - (se->deadline - se->vruntime);
> +
> + return (vused * min_nr_tasks > vslice);
> }
>
> Thanks.
> Tim
Hi Tim, Chen Yu,
Thanks for pointing out this separate issue and for the prototype.
I tested the deadline-based calculation from the prototype with the
same proxy-execution and core-scheduling reproducer. The reproducer has
a FAIR donor proxy-executing a task on an SMT CPU while the sibling is
force-idled.
The relevant ordering in the current code is:
update_curr()
-> vruntime += delta
-> update_deadline()
...
task_tick_core()
When update_deadline() advances the deadline before
__entity_slice_used() is called, the prototype observes the newly
advanced deadline. Since the new deadline is based on the current
vruntime plus a new virtual slice, deadline - vruntime is reset to
approximately vslice. Consequently, the reconstructed vused becomes
small even though the donor has consumed scheduling service since it was
selected.
In the CONFIG_HZ=1000, nice-0 run, instrumentation showed:
existing runtime delta == 0
vslice == 2100000
reconstructed vused in the tens or hundreds of thousands
used == 0
For comparison, I tested a selection-time vruntime snapshot:
set_next_entity():
core_prev_vruntime = se->vruntime;
__entity_slice_used():
vused = se->vruntime - se->core_prev_vruntime;
I then repeated the test with CONFIG_HZ=1000, 250 and 100, and also
with a nice -10 FAIR donor at HZ=250 and HZ=100.
For the counts below I only included ticks where rq->donor != rq->curr,
core force-idle was active, rq->cfs.h_nr_queued == 1, and the donor's
existing runtime delta == 0.
The observed behavior was consistent across these runs:
configuration deadline prototype vruntime snapshot
HZ=1000, nice 0 10/10 used=0 8/8 used=1
HZ=250, nice 0 10/10 used=0 6/6 used=1
HZ=100, nice 0 14/14 used=0 16/16 used=1
HZ=250, nice -10 30/30 used=0 6/6 used=1
HZ=100, nice -10 15/15 used=0 17/17 used=1
The number of ticks in each window is timing-dependent and can change
when a successful slice check triggers rescheduling. The comparison
above is based on the per-tick result, rather than on equal window
lengths.
I also compared the existing runtime-based predicate with the snapshot
predicate on the non-proxy path. In a CONFIG_HZ=1000 run, the decisions
matched for all 83 observed force-idle ticks with rq->donor == rq->curr.
For all 177 matching proxy ticks in the same run, the existing predicate
was false while the snapshot predicate was true.
In these runs, the snapshot version tracked the donor's vruntime
progress across deadline rollovers and triggered the force-idle
reschedule. It returned used == 1 for every matching tick observed in
the proxy windows listed above. This also matches the previous
sum_exec_runtime - prev_sum_exec_runtime semantics more closely: the
measurement starts when the scheduling context is selected and is not
tied to the current EEVDF request after a deadline rollover.
These tests suggest that reconstructing the consumed service from the
current deadline may lose the original selection-time semantics across
a deadline rollover. The virtual-time direction still looks
appropriate, while the consumed service appears to need a
selection-time baseline rather than being reconstructed from a
deadline that update_deadline() may already have advanced.
I am keeping this as a separate patch from the execution-context tick
series. I will continue validating the snapshot approach with
fair-group scheduling before posting it.
Thanks,
Hui
^ permalink raw reply [flat|nested] 15+ messages in thread* Re: [PATCH v2 1/2] sched/numa: Drive NUMA task tick from execution context
2026-09-04 14:10 ` Hui Su
@ 2026-09-04 20:15 ` Tim Chen
2026-09-05 13:58 ` Hui Su
0 siblings, 1 reply; 15+ messages in thread
From: Tim Chen @ 2026-09-04 20:15 UTC (permalink / raw)
To: Hui Su, Chen, Yu C
Cc: K Prateek Nayak, Juri Lelli, Vincent Guittot, Dietmar Eggemann,
Valentin Schneider, John Stultz, Ingo Molnar, Peter Zijlstra,
linux-kernel
On Fri, 2026-09-04 at 22:10 +0800, Hui Su wrote:
[...]
>
> Hi Tim, Chen Yu,
>
> Thanks for pointing out this separate issue and for the prototype.
>
> I tested the deadline-based calculation from the prototype with the
> same proxy-execution and core-scheduling reproducer. The reproducer has
> a FAIR donor proxy-executing a task on an SMT CPU while the sibling is
> force-idled.
>
> The relevant ordering in the current code is:
>
> update_curr()
> -> vruntime += delta
> -> update_deadline()
> ...
> task_tick_core()
>
> When update_deadline() advances the deadline before
> __entity_slice_used() is called, the prototype observes the newly
> advanced deadline. Since the new deadline is based on the current
> vruntime plus a new virtual slice, deadline - vruntime is reset to
> approximately vslice. Consequently, the reconstructed vused becomes
> small even though the donor has consumed scheduling service since it was
> selected.
Yes, you have a good point. The code I proposed just look at whether
we have consumed our allotment in the current slice.
What we should have looked at is whether the donor's total run time has
exceeded its quota when doing core scheduling. And we may happen to
hit __entity_slice_used() at the front of the slice after advancing
the deadline and __entity_slice_used()
returns false instead of true, even though I have consumed more than
my fair share when looking at longer time period across multiple slices.
This breaks the lone task case that Chen Yu has raised as it could
just keep running as __entity_slice_used() returns false.
>
> In the CONFIG_HZ=1000, nice-0 run, instrumentation showed:
>
> existing runtime delta == 0
> vslice == 2100000
> reconstructed vused in the tens or hundreds of thousands
> used == 0
>
> For comparison, I tested a selection-time vruntime snapshot:
>
> set_next_entity():
> core_prev_vruntime = se->vruntime;
>
> __entity_slice_used():
> vused = se->vruntime - se->core_prev_vruntime;
>
> I then repeated the test with CONFIG_HZ=1000, 250 and 100, and also
> with a nice -10 FAIR donor at HZ=250 and HZ=100.
>
> For the counts below I only included ticks where rq->donor != rq->curr,
> core force-idle was active, rq->cfs.h_nr_queued == 1, and the donor's
> existing runtime delta == 0.
>
> The observed behavior was consistent across these runs:
>
> configuration deadline prototype vruntime snapshot
> HZ=1000, nice 0 10/10 used=0 8/8 used=1
> HZ=250, nice 0 10/10 used=0 6/6 used=1
> HZ=100, nice 0 14/14 used=0 16/16 used=1
> HZ=250, nice -10 30/30 used=0 6/6 used=1
> HZ=100, nice -10 15/15 used=0 17/17 used=1
>
> The number of ticks in each window is timing-dependent and can change
> when a successful slice check triggers rescheduling. The comparison
> above is based on the per-tick result, rather than on equal window
> lengths.
>
> I also compared the existing runtime-based predicate with the snapshot
> predicate on the non-proxy path. In a CONFIG_HZ=1000 run, the decisions
> matched for all 83 observed force-idle ticks with rq->donor == rq->curr.
> For all 177 matching proxy ticks in the same run, the existing predicate
> was false while the snapshot predicate was true.
>
> In these runs, the snapshot version tracked the donor's vruntime
> progress across deadline rollovers and triggered the force-idle
> reschedule. It returned used == 1 for every matching tick observed in
> the proxy windows listed above. This also matches the previous
> sum_exec_runtime - prev_sum_exec_runtime semantics more closely: the
> measurement starts when the scheduling context is selected and is not
> tied to the current EEVDF request after a deadline rollover.
>
> These tests suggest that reconstructing the consumed service from the
> current deadline may lose the original selection-time semantics across
> a deadline rollover. The virtual-time direction still looks
> appropriate, while the consumed service appears to need a
> selection-time baseline rather than being reconstructed from a
> deadline that update_deadline() may already have advanced.
The accumulated run time of the donor since it was picked for running
should be used for selection time baseline.
So maybe a patch like the following instead.
diff --git a/include/linux/sched.h b/include/linux/sched.h
index 8b3d47a325cc..bf105f436808 100644
--- a/include/linux/sched.h
+++ b/include/linux/sched.h
@@ -590,6 +590,10 @@ struct sched_entity {
u64 sum_exec_runtime;
u64 prev_sum_exec_runtime;
u64 vruntime;
+#ifdef CONFIG_SCHED_CORE
+ /* vruntime at the last pick, for the force-idle slice check: */
+ u64 core_slice_vruntime;
+#endif
/* Approximated virtual lag: */
s64 vlag;
/* 'Protected' deadline, to give out minimum quantums: */
diff --git a/kernel/sched/core.c b/kernel/sched/core.c
index f78275192036..dde1f45051e3 100644
--- a/kernel/sched/core.c
+++ b/kernel/sched/core.c
@@ -4580,6 +4580,9 @@ static void __sched_fork(u64 clone_flags, struct task_struct *p)
p->se.prev_sum_exec_runtime = 0;
p->se.nr_migrations = 0;
p->se.vruntime = 0;
+#ifdef CONFIG_SCHED_CORE
+ p->se.core_slice_vruntime = 0;
+#endif
p->se.vlag = 0;
p->se.rel_deadline = 0;
INIT_LIST_HEAD(&p->se.group_node);
diff --git a/kernel/sched/fair.c b/kernel/sched/fair.c
index 8dff37059faf..ee06ee62e8dc 100644
--- a/kernel/sched/fair.c
+++ b/kernel/sched/fair.c
@@ -6502,6 +6502,9 @@ set_next_entity(struct cfs_rq *cfs_rq, struct sched_entity *se)
}
se->prev_sum_exec_runtime = se->sum_exec_runtime;
+#ifdef CONFIG_SCHED_CORE
+ se->core_slice_vruntime = se->vruntime;
+#endif
}
static bool __dequeue_task(struct rq *rq, struct task_struct *p, int flags);
@@ -14748,10 +14751,21 @@ static void rq_offline_fair(struct rq *rq)
static inline bool
__entity_slice_used(struct sched_entity *se, int min_nr_tasks)
{
- u64 rtime = se->sum_exec_runtime - se->prev_sum_exec_runtime;
- u64 slice = se->slice;
+ u64 vused, vslice;
+
+ /*
+ * @se is the scheduling context (rq->donor), which under proxy
+ * execution may not be the running task; its sum_exec_runtime is then
+ * not advanced. Use vruntime instead -- update_curr() advances it with
+ * the proxy runtime -- measured from a baseline taken at pick time in
+ * set_next_entity(). Being pick-based rather than per-slice, it stays
+ * correct when the tick period exceeds the slice, and matches the old
+ * rtime/slice test in the non-proxy case (same weight scaling).
+ */
+ vused = se->vruntime - se->core_slice_vruntime;
+ vslice = calc_delta_fair(se->slice, se);
- return (rtime * min_nr_tasks > slice);
+ return (vused * min_nr_tasks > vslice);
}
#define MIN_NR_TASKS_DURING_FORCEIDLE 2
base-commit: cee9395acd8043be0644b25c34bfa86623f2b935
>
> I am keeping this as a separate patch from the execution-context tick
> series. I will continue validating the snapshot approach with
> fair-group scheduling before posting it.
Tim
>
> Thanks,
> Hui
>
^ permalink raw reply [flat|nested] 15+ messages in thread* Re: [PATCH v2 1/2] sched/numa: Drive NUMA task tick from execution context
2026-09-04 20:15 ` Tim Chen
@ 2026-09-05 13:58 ` Hui Su
0 siblings, 0 replies; 15+ messages in thread
From: Hui Su @ 2026-09-05 13:58 UTC (permalink / raw)
To: Tim Chen, Chen, Yu C
Cc: Hui Su, K Prateek Nayak, Juri Lelli, Vincent Guittot,
Dietmar Eggemann, Valentin Schneider, John Stultz, Ingo Molnar,
Peter Zijlstra, linux-kernel
On Fri, Sep 4, 2026 at 1:15 PM, Tim Chen wrote:
> Yes, you have a good point. The code I proposed just look at whether
> we have consumed our allotment in the current slice.
>
> What we should have looked at is whether the donor's total run time has
> exceeded its quota when doing core scheduling. And we may happen to
> hit __entity_slice_used() at the front of the slice after advancing
> the deadline and __entity_slice_used()
> returns false instead of true, even though I have consumed more than
> my fair share when looking at longer time period across multiple slices.
>
> The accumulated run time of the donor since it was picked for running
> should be used for selection time baseline.
>
> So maybe a patch like the following instead.
Thanks for the updated prototype.
I tested the pick-time vruntime snapshot further. With a fixed weight,
it behaves as expected: it fixes the proxy case and continues to match
the existing runtime-based predicate in the non-proxy tests I ran.
I then tested it with CONFIG_FAIR_GROUP_SCHED, using nested cgroups and
changing the hierarchical weights in an observation-only setup. The
kernel computes the existing predicate, the pick-time vruntime predicate,
and a pick-time task-clock predicate on the same tick, while the existing
predicate remains the actual return value.
I think the "same weight scaling" condition in the non-proxy equivalence
is the interesting part here. If the hierarchical weight changes after
core_slice_vruntime is captured, vused represents service accumulated
since the pick-time baseline, while vslice is converted using the current
weight.
Across 11 instrumented runs I observed 5985 non-proxy samples. 29 of
those were explicitly identified as cases where the same task's
sched_entity had been reweighted after the current selection-time
baseline was taken. The existing and task-clock predicates matched in all
5985 non-proxy samples, while the vruntime predicate differed in four of
the 29 reweight-after-pick samples.
I also reproduced the divergence with actual runnable load in the
hierarchy. In one causal trace:
selection=5
pick_hweight=1048576
current_hweight=15138
reweight_vruntime=768327601->768327601
existing:
rtime=7980540
used=1
pick-time vruntime:
vused=7980540
vslice=145462386
used=0
task-clock:
rtime=7980540
used=1
This was the same selection: the pick-time baseline had already been
established, the hierarchical weight then changed, and there was no
new pick before the predicate was evaluated.
In this example reweight_eevdf() did not change se->vruntime, so the
divergence does not depend on a vruntime coordinate adjustment. The
weight change alone is enough for the accumulated vused and the
current-weight vslice to no longer necessarily use the same scale.
I also observed the reverse predicate direction in the observation-only
runs, although I have not yet reduced that case to the same detailed
causal trace.
One way to keep the vruntime approach would be to rebase the accumulated
virtual service whenever the relevant weight changes, preserving the
vused / vslice ratio across the reweight. Simply resetting
core_slice_vruntime at reweight time would lose service already consumed
before the reweight.
A simpler alternative I have been testing is to keep the check in the
same real-time domain as the existing predicate:
set_next_entity():
core_sched_start = se->exec_start;
__entity_slice_used():
rtime = se->exec_start - se->core_sched_start;
return rtime * min_nr_tasks > se->slice;
update_se() advances the donor's exec_start from rq_clock_task() while
it is the scheduling context, including during proxy execution, even
though the task-level sum_exec_runtime is charged to rq->curr. This
keeps the force-idle slice check in the same real-time domain as the
existing sum_exec_runtime - prev_sum_exec_runtime comparison and avoids
converting accumulated service across weight changes.
For reference, the local task-clock variant I am testing is below. It
is not intended as a formal posting yet:
diff --git a/include/linux/sched.h b/include/linux/sched.h
index 8b3d47a325cc..c32d9931129f 100644
--- a/include/linux/sched.h
+++ b/include/linux/sched.h
@@ -590,6 +590,9 @@ struct sched_entity {
u64 sum_exec_runtime;
u64 prev_sum_exec_runtime;
u64 vruntime;
+#ifdef CONFIG_SCHED_CORE
+ u64 core_sched_start;
+#endif
/* Approximated virtual lag: */
s64 vlag;
/* 'Protected' deadline, to give out minimum quantums: */
diff --git a/kernel/sched/core.c b/kernel/sched/core.c
index f78275192036..22ae5dc57337 100644
--- a/kernel/sched/core.c
+++ b/kernel/sched/core.c
@@ -4580,6 +4580,9 @@ static void __sched_fork(u64 clone_flags, struct task_struct *p)
p->se.prev_sum_exec_runtime = 0;
p->se.nr_migrations = 0;
p->se.vruntime = 0;
+#ifdef CONFIG_SCHED_CORE
+ p->se.core_sched_start = 0;
+#endif
p->se.vlag = 0;
p->se.rel_deadline = 0;
INIT_LIST_HEAD(&p->se.group_node);
diff --git a/kernel/sched/fair.c b/kernel/sched/fair.c
index 8dff37059faf..1bd05c906d3a 100644
--- a/kernel/sched/fair.c
+++ b/kernel/sched/fair.c
@@ -6502,6 +6502,9 @@ set_next_entity(struct cfs_rq *cfs_rq, struct sched_entity *se)
}
se->prev_sum_exec_runtime = se->sum_exec_runtime;
+#ifdef CONFIG_SCHED_CORE
+ se->core_sched_start = se->exec_start;
+#endif
}
static bool __dequeue_task(struct rq *rq, struct task_struct *p, int flags);
@@ -14748,10 +14751,9 @@ static void rq_offline_fair(struct rq *rq)
static inline bool
__entity_slice_used(struct sched_entity *se, int min_nr_tasks)
{
- u64 rtime = se->sum_exec_runtime - se->prev_sum_exec_runtime;
- u64 slice = se->slice;
+ u64 rtime = se->exec_start - se->core_sched_start;
- return (rtime * min_nr_tasks > slice);
+ return (rtime * min_nr_tasks > se->slice);
}
#define MIN_NR_TASKS_DURING_FORCEIDLE 2
The task-clock version has matched the existing predicate in the
non-proxy tests so far and fixes the proxy reproducer as well. I am
still validating reselection, migration, and the remaining proxy
boundary cases, so I have not posted either implementation.
Do you think keeping the check in the original real-time/task-clock
domain is a reasonable direction here, or would you prefer preserving
the vruntime approach by carrying the accumulated service across
reweights?
Thanks,
Hui
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH v2 1/2] sched/numa: Drive NUMA task tick from execution context
2026-09-03 21:30 ` Tim Chen
2026-09-04 5:16 ` Hui Su
2026-09-04 14:10 ` Hui Su
@ 2026-09-04 16:10 ` Chen Yu
2026-09-04 20:24 ` Tim Chen
2 siblings, 1 reply; 15+ messages in thread
From: Chen Yu @ 2026-09-04 16:10 UTC (permalink / raw)
To: Tim Chen
Cc: Hui Su, K Prateek Nayak, Juri Lelli, Vincent Guittot,
Dietmar Eggemann, Valentin Schneider, John Stultz, Ingo Molnar,
Peter Zijlstra, linux-kernel, chen.yu
On Thu, Sep 03, 2026 at 02:30:25PM -0700, Tim Chen wrote:
> On Thu, 2026-09-03 at 20:41 +0800, Chen, Yu C wrote:
[ ... ]
> > But I also see that in task_tick_core(), the sum_exec_runtime is also
> > leveraged
> > to calculate the delta "wall time" via __entity_slice_used():
> > se->sum_exec_runtime - se->prev_sum_exec_runtime
> > does it mean task_tick_core() also needs to be bring one level up to
> > sched_tick()
> > and passed with rq->curr?
>
> I think task_tick_core() needs to stay with the donor's context
> as it is the scheduling context.
>
> task_tick_core() is not about the execution context --
> it decides whether the current scheduling context has used
> up enough of its slice to let a force-idled SMT sibling run. That
> slice belongs to the donor, so the donor is the right task to pass.
>
> There is a separate issue lurking here, task_tick_core() measures consumed slice as
> se->sum_exec_runtime - se->prev_sum_exec_runtime. Under proxy the
> donor's sum_exec_runtime does not advance (update_se() charges the
> runtime to rq->curr instead), so that delta stays near zero and the
> force-idle resched may never trigger.
>
> Passing rq->curr does not fix it either. __entity_slice_used() takes
> the runtime and the slice from the same entity, so passing rq->curr
> just compares the running task against its own slice. But this check
> is about the donor: it asks whether the scheduling context that owns
> the CPU has used up its slice. The running task is only borrowing the
> CPU through proxy, so its slice is not the one we care about here.
>
Got it, I see.
> Maybe something like below (only compile tested) to fix the issue.
> That said, this is somewhat orthogonal to the issue that the execution context
> series is trying to solve. It should be fixed separately.
>
> diff --git a/kernel/sched/fair.c b/kernel/sched/fair.c
> index 8dff37059faf..cd240bf52d03 100644
> --- a/kernel/sched/fair.c
> +++ b/kernel/sched/fair.c
> @@ -14748,10 +14748,29 @@ static void rq_offline_fair(struct rq *rq)
> static inline bool
> __entity_slice_used(struct sched_entity *se, int min_nr_tasks)
> {
> - u64 rtime = se->sum_exec_runtime - se->prev_sum_exec_runtime;
> - u64 slice = se->slice;
> + u64 vslice, vused;
>
> - return (rtime * min_nr_tasks > slice);
> + /*
> + * @se is the scheduling context (rq->donor). Under proxy execution
> + * it need not be the task executing on the CPU, so its
> + * sum_exec_runtime is not advanced and cannot be used to tell how
> + * much of its slice it has consumed. Its vruntime, however, is
> + * advanced by update_curr() with the proxy runtime, and its EEVDF
> + * deadline reflects the granted slice, so measure the consumed
> + * fraction in virtual time instead.
> + *
> + * This is equivalent to the previous real-time comparison in the
> + * non-proxy case: both @vused and @vslice are scaled by the same
> + * weight factor, so the ratio (and thus the min_nr_tasks test) is
> + * unchanged.
> + */
> + if (vruntime_cmp(se->vruntime, ">=", se->deadline))
> + return true;
> +
> + vslice = calc_delta_fair(se->slice, se);
> + vused = vslice - (se->deadline - se->vruntime);
> +
> + return (vused * min_nr_tasks > vslice);
> }
This fix looks good to me. And just one minor question that I'm
trying to figure out:
Consider that there is only one running task p on one of the SMT siblings.
The original comparison is between:
se->sum_exec_runtime - se->prev_sum_exec_runtime vs slice,
and since there is only one runnable task, p continues to run
without any preemption, so se->prev_sum_exec_runtime remains
unchanged, while se->sum_exec_runtime moves forward. Therefore,
the duration delta of se->sum_exec_runtime - se->prev_sum_exec_runtime
could expand to many slices in theory.
After switching to the vruntime-based comparison, even
if p has not been preempted, se->deadline together with se->vruntime
will move forward by update_dealine(). That is to say, we now only
consider the delta within one slice. This seems to tighten the
restriction for task_tick_core() to trigger a force reschedule.
But Overall I think it should not be a good deal to check
within a slice period.
thanks,
Chenyu
^ permalink raw reply [flat|nested] 15+ messages in thread* Re: [PATCH v2 1/2] sched/numa: Drive NUMA task tick from execution context
2026-09-04 16:10 ` Chen Yu
@ 2026-09-04 20:24 ` Tim Chen
0 siblings, 0 replies; 15+ messages in thread
From: Tim Chen @ 2026-09-04 20:24 UTC (permalink / raw)
To: Chen Yu
Cc: Hui Su, K Prateek Nayak, Juri Lelli, Vincent Guittot,
Dietmar Eggemann, Valentin Schneider, John Stultz, Ingo Molnar,
Peter Zijlstra, linux-kernel, chen.yu
On Sat, 2026-09-05 at 00:10 +0800, Chen Yu wrote:
> On Thu, Sep 03, 2026 at 02:30:25PM -0700, Tim Chen wrote:
> > On Thu, 2026-09-03 at 20:41 +0800, Chen, Yu C wrote:
>
> [ ... ]
>
> > > But I also see that in task_tick_core(), the sum_exec_runtime is also
> > > leveraged
> > > to calculate the delta "wall time" via __entity_slice_used():
> > > se->sum_exec_runtime - se->prev_sum_exec_runtime
> > > does it mean task_tick_core() also needs to be bring one level up to
> > > sched_tick()
> > > and passed with rq->curr?
> >
> > I think task_tick_core() needs to stay with the donor's context
> > as it is the scheduling context.
> >
> > task_tick_core() is not about the execution context --
> > it decides whether the current scheduling context has used
> > up enough of its slice to let a force-idled SMT sibling run. That
> > slice belongs to the donor, so the donor is the right task to pass.
> >
> > There is a separate issue lurking here, task_tick_core() measures consumed slice as
> > se->sum_exec_runtime - se->prev_sum_exec_runtime. Under proxy the
> > donor's sum_exec_runtime does not advance (update_se() charges the
> > runtime to rq->curr instead), so that delta stays near zero and the
> > force-idle resched may never trigger.
> >
> > Passing rq->curr does not fix it either. __entity_slice_used() takes
> > the runtime and the slice from the same entity, so passing rq->curr
> > just compares the running task against its own slice. But this check
> > is about the donor: it asks whether the scheduling context that owns
> > the CPU has used up its slice. The running task is only borrowing the
> > CPU through proxy, so its slice is not the one we care about here.
> >
>
> Got it, I see.
>
> > Maybe something like below (only compile tested) to fix the issue.
> > That said, this is somewhat orthogonal to the issue that the execution context
> > series is trying to solve. It should be fixed separately.
> >
> > diff --git a/kernel/sched/fair.c b/kernel/sched/fair.c
> > index 8dff37059faf..cd240bf52d03 100644
> > --- a/kernel/sched/fair.c
> > +++ b/kernel/sched/fair.c
> > @@ -14748,10 +14748,29 @@ static void rq_offline_fair(struct rq *rq)
> > static inline bool
> > __entity_slice_used(struct sched_entity *se, int min_nr_tasks)
> > {
> > - u64 rtime = se->sum_exec_runtime - se->prev_sum_exec_runtime;
> > - u64 slice = se->slice;
> > + u64 vslice, vused;
> >
> > - return (rtime * min_nr_tasks > slice);
> > + /*
> > + * @se is the scheduling context (rq->donor). Under proxy execution
> > + * it need not be the task executing on the CPU, so its
> > + * sum_exec_runtime is not advanced and cannot be used to tell how
> > + * much of its slice it has consumed. Its vruntime, however, is
> > + * advanced by update_curr() with the proxy runtime, and its EEVDF
> > + * deadline reflects the granted slice, so measure the consumed
> > + * fraction in virtual time instead.
> > + *
> > + * This is equivalent to the previous real-time comparison in the
> > + * non-proxy case: both @vused and @vslice are scaled by the same
> > + * weight factor, so the ratio (and thus the min_nr_tasks test) is
> > + * unchanged.
> > + */
> > + if (vruntime_cmp(se->vruntime, ">=", se->deadline))
> > + return true;
> > +
> > + vslice = calc_delta_fair(se->slice, se);
> > + vused = vslice - (se->deadline - se->vruntime);
> > +
> > + return (vused * min_nr_tasks > vslice);
> > }
>
> This fix looks good to me. And just one minor question that I'm
> trying to figure out:
>
> Consider that there is only one running task p on one of the SMT siblings.
> The original comparison is between:
> se->sum_exec_runtime - se->prev_sum_exec_runtime vs slice,
> and since there is only one runnable task, p continues to run
> without any preemption, so se->prev_sum_exec_runtime remains
> unchanged, while se->sum_exec_runtime moves forward. Therefore,
> the duration delta of se->sum_exec_runtime - se->prev_sum_exec_runtime
> could expand to many slices in theory.
> After switching to the vruntime-based comparison, even
> if p has not been preempted, se->deadline together with se->vruntime
> will move forward by update_dealine(). That is to say, we now only
> consider the delta within one slice.
>
You raised a good point. And coupled with Hui's comment make me realize
that the deadline advancement in each slice means that I am only checking
whether I am using up my quota in the current slice.
I should check the run time since the task was picked to run to
see if the donor has exceeded its allotment if it keeps running
across slices in the lone task case.
I replied to Hui's email with a new proposal.
Tim
> This seems to tighten the
> restriction for task_tick_core() to trigger a force reschedule.
> But Overall I think it should not be a good deal to check
> within a slice period.
>
> thanks,
> Chenyu
^ permalink raw reply [flat|nested] 15+ messages in thread