sched discussion
[PATCH 1/2] sched/fair: reuse the ENQUEUE_DELAYED calculation in enqueue_task_fair()
LLM 分析
sched/fair:减少 enqueue 路径上的重复计算
系列概况
- 标题:sched/fair: reduce repeated work in enqueue path
- 作者:Kayra Cizmeci kayracizmeci@gmail.com
- 版本:v1(2026-08-24,2 patch,无 cover)、v2(2026-08-26,cover + 2 patch)
- 规模:1 个文件
kernel/sched/fair.c;v2 合计 25 insertions(+), 17 deletions(-) - Message-ID:
- v1 1/2:
20260824125223.508178-1-kayracizmeci@gmail.com - v1 2/2:
20260824125223.508178-2-kayracizmeci@gmail.com - v2 cover:
cover.1787737648.git.kayracizmeci@gmail.com - v2 1/2:
0b1ef9d0122ae3037dac38d2549f13c9b063369a.1787737648.git.kayracizmeci@gmail.com - v2 2/2:
60fd4fc5d17b706766a428b63760d1f69e6d11bb.1787737648.git.kayracizmeci@gmail.com - Prateek 评审:
3ae49b35-2188-4b29-af4a-6fff500098d3@amd.com - 作者回复:
20260826184432.911321-1-kayracizmeci@gmail.com
- v1 1/2:
- 完整性:完整(cover、两轮 patch、一条 reviewer 反馈与作者回复)
补丁目的
CFS 的 enqueue_task_fair() 主路径上出现三类重复:
flags & ENQUEUE_DELAYED在同一函数里被判断两次。cfs_rq->curr == se在enqueue_task_fair()和其唯一调用者requeue_delayed_entity()里各算一次。- 当
se == curr时,place_entity()对同一权重调用avg_vruntime_weight()两次。
本系列把前两个布尔提到调用入口算一次再透传,并把 avg_vruntime_weight() 的结果复用一次。作者声明 "No functional change intended",本质是 cleanup 加微小优化。
Patch 概览
- 1/2:
enqueue_task_fair()顶部新增bool delayed = (flags & ENQUEUE_DELAYED);,把函数内两处(flags & ENQUEUE_DELAYED)替换为delayed。 - 2/2:
enqueue_task_fair()把curr = (cfs_rq->curr == se)上提到较早位置,新增参数bool curr传给requeue_delayed_entity()、新增bool is_curr传给place_entity(),并在se == curr路径下复用curr_weight,避免avg_vruntime_weight()重复调用。
旧流程的问题
enqueue_task_fair(rq, p, flags)
|
+-- if (flags & ENQUEUE_DELAYED) [expr A]
+-- if (flags & ENQUEUE_DELAYED) { ... } [expr A' recomputed]
+-- curr = (cfs_rq->curr == se) [expr X]
+-- requeue_delayed_entity(cfs_rq, se)
|
+-- if (se != cfs_rq->curr) [expr X' recomputed]
+-- place_entity(cfs_rq, se, 0)
| |
| +-- if (curr && curr->on_rq)
| | load += avg_vruntime_weight(curr->h_load.weight) [call #1]
| +-- weight = avg_vruntime_weight(se->h_load.weight)
| [call #2: same input
| when se == curr]
+-- if (se != cfs_rq->curr) [expr X'' recomputed]
新流程
enqueue_task_fair(rq, p, flags)
|
+-- delayed = (flags & ENQUEUE_DELAYED) [compute once]
+-- curr = (cfs_rq->curr == se) [compute once]
|
+-- requeue_delayed_entity(cfs_rq, se, curr)
| |
| +-- if (!curr) [use parameter]
| | place_entity(cfs_rq, se, 0, curr)
| | |
| | +-- curr_weight = avg_vruntime_weight(curr->h_load.weight)
| | +-- if (curr->on_rq) load += curr_weight
| | +-- weight = is_curr ? curr_weight
| | : avg_vruntime_weight(se->h_load.weight)
| +-- if (!curr) [use parameter]
|
+-- place_entity(cfs_rq, se, flags, curr)
+-- place_entity(cfs_rq, se, flags|ENQUEUE_QUEUED, curr)
v2 在 place_entity() 内进一步把 curr_weight 的计算包在 if (curr && (curr->on_rq || is_curr)) 守卫里,避免 curr_weight 在不会被消费的场景下被算出。
关键实现
Patch 1/2 — 收敛 ENQUEUE_DELAYED
bool curr, delayed = (flags & ENQUEUE_DELAYED);
...
if (!p->se.sched_delayed || delayed)
...
if (delayed) {
requeue_delayed_entity(cfs_rq, se);
return;
}
Patch 2/2 — 透传 curr 并复用 curr_weight
place_entity(struct cfs_rq *cfs_rq, struct sched_entity *se,
int flags, bool is_curr)
{
...
long load, weight, curr_weight;
...
if (curr && (curr->on_rq || is_curr)) {
curr_weight = avg_vruntime_weight(cfs_rq, curr->h_load.weight);
if (curr->on_rq)
load += curr_weight;
}
if (is_curr)
weight = curr_weight;
else
weight = avg_vruntime_weight(cfs_rq, se->h_load.weight);
}
requeue_delayed_entity(struct cfs_rq *cfs_rq,
struct sched_entity *se, bool curr)
{
if (!curr)
place_entity(cfs_rq, se, 0, curr);
if (!curr)
...;
}
enqueue_task_fair() 把 curr = (cfs_rq->curr == se) 的赋值位置上移到 requeue_delayed_entity() 调用之前;对两处 place_entity() 与两处 requeue_delayed_entity() 调用全部改为透传 curr;原代码 se != cfs_rq->curr 的两处判断直接换成 !curr。
类比
把 CFS enqueue 路径想成食堂窗口:
delayed是 VIP 餐贴纸:原本阿姨每看一眼托盘都要重新辨认一次。curr是"是不是刚才那个排在队伍里的同一个人":阿姨反复对照队列。avg_vruntime_weight()是过秤,同一个托盘有时被称两次。
新流程是阿姨进门就在小票上盖"VIP 章"和"同一人章",后面无论走到称菜台还是结账台都只看小票上的章,称重只发生一次。窗口功能没变,但少了一连串重复劳动,对后续维护者也更直观。
Highlight:风险与注意点
curr不变性假设:curr是函数级快照;进入requeue_delayed_entity()后cfs_rq->curr可能在place_entity()中改变,但透传的curr仍按原决策生效。v1 中提到的WARN_ON_ONCE(curr != new_calc_curr)设想并未在 v2 落地,需要关注后续是否引入运行时校验。is_curr与curr->on_rq的语义重叠:v2 用curr && (curr->on_rq || is_curr)守卫curr_weight,但is_curr语义上应蕴含curr->on_rq;用||(而非&&)属于防御写法,需要确认is_curr=true且curr->on_rq=false的组合是否真的会出现,避免出现逻辑空隙。- 命名可读性争议:Prateek 指出
if (!p->se.sched_delayed || delayed)读起来反直觉("非 delayed,或者 delayed?")。作者回复建议统一改为wakeup_delayed或保留原始ENQUEUE_DELAYED,分歧需要 v3 解决。 - 性能收益实际幅度:
avg_vruntime_weight()只是几次乘除,省一次调用在单核 CFS 路径上几乎不可观测;真正价值是 readability 与减少"读者心算",review 时应明确这是 cleanup,不要误读为性能修复。 - 测试覆盖面:作者只跑了
perf bench sched messaging 200 groups × 5000 loops,未覆盖sched_delayed路径下的并发压力;建议 v3 中加入hackbench、tbench或通过sched_debug触发sched_delayed的回归测试。
版本变化
| 版本 | 主要改动 |
|---|---|
| v1(2026-08-24) | 1/2 把 ENQUEUE_DELAYED 收敛为 bool delayed;2/2 透传 curr 与 is_curr,在 place_entity() 内复用 curr_weight。无 cover。 |
| v2(2026-08-26) | 新增 cover letter;1/2 无改动;2/2 加 `curr && (curr->on_rq |
一句话总结
这是一个两 patch 的 CFS cleanup:把 enqueue_task_fair() 入口的 ENQUEUE_DELAYED 与 cfs_rq->curr == se 算好并透传给 requeue_delayed_entity() 与 place_entity(),省一次 avg_vruntime_weight() 调用并提升可读性;v2 新增守卫并产生一条命名 review,等待 v3 收尾。