sched discussion
[PATCH] sched/fair: Avoid calculating curr's key twice in pick_eevdf()
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 != NULL 与 on_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*weight、load += weight,最后比较avg >= key2*load。- key1 和 key2 计算路径完全相同,第二次纯属冗余。
- 同时
entity_eligible/vruntime_eligible对curr的存在性和on_rq状态也会重新校验,而调用方在前面早就检查过。
新流程
- 直接调用新加的
curr_eligible(cfs_rq):- 假定
curr已在 runqueue 上(不再做 NULL / on_rq 检查); - 内部调用
entity_key(cfs_rq, curr)算 key,只算一次; - 在
sum_w_vruntime和sum_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 视角)变成s64(entity_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 messaging、hackbench、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。