0/1 已展开

LLM 分析

CFS EEVDF 调度器:避免在 pick_eevdf() 中重复计算 curr 的 key

系列概况

  • 标题: [PATCH] sched/fair: Avoid calculating curr's key twice in pick_eevdf()
  • 作者: Kayra Cizmeci kayracizmeci@gmail.com
  • 版本: 单封 PATCH(无 v2/v3)
  • 规模: 1 文件改动(kernel/sched/fair.c),+28 / -1 行
  • Message-ID: 20260812150958.141846-1-kayracizmeci@gmail.com
  • 完整性: 单封邮件附带完整 diff 与 commit message,自带 boot test 说明,无后续回复

补丁目的

kernel/sched/fair.c 的 EEVDF(Earliest Eligible Virtual Deadline First)选下一个 task 的路径中,pick_eevdf() 已经在更早的位置用 entity_key(cfs_rq, curr) 算出过一次 key = curr->vruntime - min_vruntime;随后调用 entity_eligible(cfs_rq, curr),该函数又会进入 vruntime_eligible(cfs_rq, curr->vruntime),并再次执行 key = vruntime - min_vruntime——和前面那次完全等价的多余减法。

补丁把这段重复路径抽到新函数 curr_eligible(),让 entity_key() 只调用一次,并把 curr != NULLon_rq 这两个已经做过的不再重复检查的不变量交给调用方维护。

旧流程的问题

  • pick_eevdf() 在更早处算过 entity_key(cfs_rq, curr)(得 key1)。
  • 紧接着调用 entity_eligible(cfs_rq, curr)vruntime_eligible(cfs_rq, curr->vruntime)
  • vruntime_eligible 内部又做 key = vruntime - cfs_rq->min_vruntime(得 key2),然后照常累加 avg += key2*weightload += weight,最后比较 avg >= key2*load
  • key1 和 key2 计算路径完全相同,第二次纯属冗余。
  • 同时 entity_eligible / vruntime_eligiblecurr 的存在性和 on_rq 状态也会重新校验,而调用方在前面早就检查过。

新流程

  • 直接调用新加的 curr_eligible(cfs_rq)
    • 假定 curr 已在 runqueue 上(不再做 NULL / on_rq 检查);
    • 内部调用 entity_key(cfs_rq, curr) 算 key,只算一次;
    • sum_w_vruntimesum_weight 之上加上 curr 的贡献,再做一次 avg >= key*load 比较。
  • 调用点变成 if (curr && (!curr->on_rq || !curr_eligible(cfs_rq))),最外层的 curr &&!curr->on_rq 仍然由 pick_eevdf 自己保证。

关键实现

static int curr_eligible(struct cfs_rq *cfs_rq)
{
    struct sched_entity *curr = cfs_rq->curr;
    s64 key, avg = cfs_rq->sum_w_vruntime;
    long load = cfs_rq->sum_weight;
    unsigned long weight = avg_vruntime_weight(cfs_rq, curr->load.weight);

    key = entity_key(cfs_rq, curr);
    avg += key * weight;
    load += weight;

#ifdef CONFIG_64BIT
#ifdef CONFIG_ARCH_SUPPORTS_INT128
    return avg >= (__int128)key * load;
#else
    s64 rhs;
    if (check_mul_overflow(key, load, &rhs))
        return key <= 0;
    return avg >= rhs;
#endif
#else
    return avg >= key * load;
#endif
}

调用点 diff:

- if (curr && (!curr->on_rq || !entity_eligible(cfs_rq, curr)))
+ if (curr && (!curr->on_rq || !curr_eligible(cfs_rq)))

注意 entity_eligible() / vruntime_eligible() 仍保留给"非 curr"的 entity(例如 leftmost 候选)使用,所以新函数只是替换 curr 这条专用路径,而不是完全删除旧 API。

       old (entity_eligible)                 new (curr_eligible)
       ------------------------              ------------------------
  pick_eevdf(cfs_rq, ...)                pick_eevdf(cfs_rq, ...)
       |                                       |
       +-- entity_key(cfs_rq, curr)            +-- curr_eligible(cfs_rq)
       |     => key1 = curr->vr - min_vr            -> entity_key(cfs_rq, curr)
       |                                                  => key = curr->vr - min_vr
       +-- entity_eligible(cfs_rq, curr)                  -> avg += key*weight
             |                                            -> load += weight
             +-- vruntime_eligible(cfs_rq, vr)             -> return avg >= key*load
                   -> key2 = vr - min_vr     (== key1)    (only one key computation)
                   -> avg += key2*weight
                   -> load += weight
                   -> return avg >= key2*load

类比

想象课堂点名:第一轮老师已经确认"小明还在座位上、是这个班的"。第二轮老师本来只想问"小明今天分数够不够升级",但他每次都把"还在座位吗""是这个班吗"再问一遍。补丁相当于把第二轮的两个重复问题剪掉,只保留"分数够不够"这一项——结论不变,但少念了几道题。

Highlight:风险与注意点

  • 没有功能变化(commit message 写明 "No functional change intended"),但 curr_eligible() 隐式依赖"调用前 curr 一定存在且在 runqueue 上"——这是把不变量挪到了调用方。以后若有人调整 pick_eevdf() 的判断顺序(比如先调 curr_eligible() 再判 NULL/on_rq),会立刻踩坑。建议加 WARN_ON_ONCE(!curr || !curr->on_rq) 或干脆写成 __always_inline、把 curr 显式传参,避免隐式契约。
  • key 的类型由 u64(vruntime 视角)变成 s64entity_key() 视角)。在 __int128 比较路径里隐式转为有符号 128 位乘,需要确认不同 KEY_DECR / vruntime 边界下编译告警干净、无 UB。
  • 64 位 / 128 位 / 32 位三条溢出分支(check_mul_overflow(__int128)key*load、裸 key*load)是从 vruntime_eligible() 原样搬过来的,本身没改动;只是搬迁,需要确认逻辑真的等价。
  • 作者坦言 perf stat 噪声过大、无法据此下结论。维护者通常希望见到更明确的 micro-bench(perf bench sched messaginghackbench、tbench)或在 -rt/压力场景下的回归数据,否则"无功能变化 + 无量化收益"很容易被 NACK。
  • 这次只发了 1 封 patch,没有 maintainer 跟进,分类上更接近"自我清理",不是 RFC、也不是 bug 修复。
  • entity_eligible() / vruntime_eligible() 本身并未删除,仍被 pick_eevdf 处理 leftmost 时使用,注意不要顺手把它一起删了导致其它调用点 NPE。

一句话总结

本 patch 把 pick_eevdf() 中对 curr 判 eligible 时重复算一次 entity_key(cfs_rq, curr) 的路径抽成 curr_eligible(),省掉冗余的 vruntime - min_vruntime 减法,并把 curr!=NULL / on_rq 的不变量交给调用方维护——属于纯清理型 micro-optimization。