0/7 已展开

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
  • 完整性:完整(cover、两轮 patch、一条 reviewer 反馈与作者回复)

补丁目的

CFS 的 enqueue_task_fair() 主路径上出现三类重复:

  1. flags & ENQUEUE_DELAYED 在同一函数里被判断两次。
  2. cfs_rq->curr == seenqueue_task_fair() 和其唯一调用者 requeue_delayed_entity() 里各算一次。
  3. se == curr 时,place_entity() 对同一权重调用 avg_vruntime_weight() 两次。

本系列把前两个布尔提到调用入口算一次再透传,并把 avg_vruntime_weight() 的结果复用一次。作者声明 "No functional change intended",本质是 cleanup 加微小优化。

Patch 概览

  • 1/2enqueue_task_fair() 顶部新增 bool delayed = (flags & ENQUEUE_DELAYED);,把函数内两处 (flags & ENQUEUE_DELAYED) 替换为 delayed
  • 2/2enqueue_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:风险与注意点

  1. curr 不变性假设curr 是函数级快照;进入 requeue_delayed_entity()cfs_rq->curr 可能在 place_entity() 中改变,但透传的 curr 仍按原决策生效。v1 中提到的 WARN_ON_ONCE(curr != new_calc_curr) 设想并未在 v2 落地,需要关注后续是否引入运行时校验。
  2. is_currcurr->on_rq 的语义重叠:v2 用 curr && (curr->on_rq || is_curr) 守卫 curr_weight,但 is_curr 语义上应蕴含 curr->on_rq;用 ||(而非 &&)属于防御写法,需要确认 is_curr=truecurr->on_rq=false 的组合是否真的会出现,避免出现逻辑空隙。
  3. 命名可读性争议:Prateek 指出 if (!p->se.sched_delayed || delayed) 读起来反直觉("非 delayed,或者 delayed?")。作者回复建议统一改为 wakeup_delayed 或保留原始 ENQUEUE_DELAYED,分歧需要 v3 解决。
  4. 性能收益实际幅度avg_vruntime_weight() 只是几次乘除,省一次调用在单核 CFS 路径上几乎不可观测;真正价值是 readability 与减少"读者心算",review 时应明确这是 cleanup,不要误读为性能修复。
  5. 测试覆盖面:作者只跑了 perf bench sched messaging 200 groups × 5000 loops,未覆盖 sched_delayed 路径下的并发压力;建议 v3 中加入 hackbenchtbench 或通过 sched_debug 触发 sched_delayed 的回归测试。

版本变化

版本主要改动
v1(2026-08-24)1/2 把 ENQUEUE_DELAYED 收敛为 bool delayed;2/2 透传 curris_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_DELAYEDcfs_rq->curr == se 算好并透传给 requeue_delayed_entity()place_entity(),省一次 avg_vruntime_weight() 调用并提升可读性;v2 新增守卫并产生一条命名 review,等待 v3 收尾。