sched discussion
[PATCH v5 linux 0/2] Cache aware scheduling: Reduce the overhead of task_cache_work
LLM 分析
sched/cache:仅扫描任务实际访问过的 CPU
系列概况
- 标题:
[PATCH v5 linux 0/2] Cache aware scheduling: Reduce the overhead of task_cache_work - 作者:Luo Gengkun
- 版本:v5
- 规模:2 个补丁;1/2 正式优化,2/2 明确标注
DO NOT APPLY的测试补丁 - 修改文件:
include/linux/sched.h、include/trace/events/sched.h、kernel/sched/fair.c、kernel/sched/features.h - 代码统计:正式补丁约
+30/-9,调试补丁约+40/-5 - Message-ID:
20260709130053.2749834-1-luogengkun2@huawei.com - 完整性:0/2、1/2、2/2 与 5 封回复均在输入中;cover letter 末尾和 4 封回复正文被截断,评审争议的最终结论无法从当前材料确认
该系列是已进入 mainline 的 cache-aware scheduling 之上的性能修复,不是在重新引入整套缓存感知调度。
补丁目的
cache-aware scheduling 通过 task_cache_work() 统计同一 mm 在各 CPU/LLC 的活动,用来选 pref_llc。原实现可能遍历整机的 CPU,即使某个进程从未在绝大部分 CPU 上运行过。384 CPU 的 AMD 测试机单次扫描经常扫满 384 个 CPU。
本系列在每个 mm 上增加 visited_cpus:
- 任务在 CPU 上获得运行时间时记录该 CPU。
task_cache_work()只扫描允许范围、且该mm实际访问过的 CPU。- 长时间未访问的 CPU 从集合中淘汰。
- 保留
get_scan_cpumasks(),避免突破 NUMA balancing 扫描约束。
AMD 测试中,扫描数量从 384 降至最多约 16,task_cache_work() 的 cycles 占比由约 0.82%~1.12% 降至 0.02%。
旧流程的问题
get_scan_cpumasks(cpus, p);
for_each_relevant_llc(sd) {
for_each_cpu(cpu, sched_domain_span(sd)) {
occ = fraction_mm_sched(cpu, mm);
update_preferred_llc(occ);
}
}
主要无效工作:扫描进程从未运行过的 CPU、扫描曾经运行但已不再活跃的 CPU、为每个候选 CPU 取状态并计算占用比例。成本随机器 CPU 数量放大,而不是随进程真实 CPU 足迹放大。Redis 多实例中这部分额外工作直接反映为 p99 延迟回退。
Old task_cache_work
|
v
Allowed CPU mask
|
v
Scan almost every CPU in each LLC
|
+--> CPU never used by mm
+--> CPU used long ago
`--> CPU currently relevant
新流程
任务运行时,account_mm_sched() 刷新时间并把 CPU 加进集合:
pcpu_sched->epoch_timeout = rq->cpu_epoch;
if (!cpumask_test_cpu(cpu_of(rq), &mm->sc_stat.visited_cpus))
cpumask_set_cpu(cpu_of(rq), &mm->sc_stat.visited_cpus);
task_cache_work() 把允许范围与访问集合做交集后扫描:
get_scan_cpumasks(cpus, p);
cpumask_and(cpus, cpus, &mm->sc_stat.visited_cpus);
for_each_relevant_llc(sd) {
for_each_cpu_and(cpu, sched_domain_span(sd), cpus)
inspect_cpu(cpu);
}
fraction_mm_sched() 检查访问是否过期,差值超过 llc_epoch_affinity_timeout 就清掉对应 bit 并跳过:
if (rq->cpu_epoch - pcpu_sched->epoch_timeout >
llc_epoch_affinity_timeout) {
cpumask_clear_cpu(cpu, &mm->sc_stat.visited_cpus);
return 0;
}
关系示意:
NUMA-allowed CPUs ----+
+--> AND --> LLC CPUs --> scan
visited_cpus ---------+
|
`--> expire stale CPU bits
关键点是先保留 get_scan_cpumasks() 给出的 NUMA/调度约束,再叠加访问集合和 LLC domain 的过滤,而不是用 visited_cpus 取代原约束。
Patch 概览
1/2:正式优化
sched_cache_time::epoch_timeout:记录此mm最近在该 CPU 活动时的 epoch。sched_cache_stat::visited_cpus:记录此mm实际访问过的 CPU。mm_init_sched()初始化epoch_timeout并清空visited_cpus。account_mm_sched()刷新访问时间并设置 CPU bit。fraction_mm_sched()淘汰过期 CPU。task_cache_work()遍历sched_domain_span(sd)∩ 原扫描范围 ∩visited_cpus。
2/2:仅用于测试,不能合入
- 增加
SC_VISITsched feature,用作开关。 - 增加
sched_cache_scantrace event,输出 comm、pid、扫描 CPU 数量。 - 为 cover letter 中
384 -> 1~16的 trace 数据提供观测手段。
关键实现
扫描成本从机器规模收缩为任务足迹:
cost ~= number_of_cpus_in_scanned_domains
--> number_of_recently_visited_cpus
Valkey/Redis 20 万/30 万/40 万 RPS:未优化前 p99 相对 baseline 恶化约 47.3%/38.4%/27.4%;应用本系列后差距收窄到 1.5%/2.0%/1.7%;task_cache_work() 的 cycles 比例都降到约 0.02%。这说明本系列是在消除 cache-aware scheduling 自身的周期性扫描成本,不改变其 LLC 选择目标。
评审发现的 epoch 停滞风险:
CPU1: account_mm_sched()
set CPU1 in visited_cpus
epoch_timeout = CPU1.cpu_epoch
CPU1 becomes idle; cpu_epoch 冻结
CPU2: task_cache_work()
delta = cpu_epoch - epoch_timeout == 0
CPU1 bit 不被清除
later: __update_mm_sched() 更新 epoch,已晚
该问题不会让活跃 CPU 被错误淘汰,反而会让已空闲的 CPU 长期留在 visited_cpus,使优化逐渐退化为额外扫描。评审者建议在过期判断前调用 __update_mm_sched(),但后续邮件在关键位置被截断,无法确认作者和维护者最终是否接受。
类比
把整机 CPU 想象成酒店 384 个房间,task_cache_work() 是每天统计某位客人在哪些楼层活动的管理员。
旧方法是每天打开 384 个房间逐一查,即使客人只去过其中 16 个。新方法给客人维护一张“最近到访房间清单”,管理员先按酒店规则确定允许检查的楼层,再只检查清单上落在这些楼层的房间。epoch_timeout 就是清单上的“最后到访日期”。
评审争议在于:如果某个房间的时钟在空置后停止走动,管理员会误以为访问记录仍然很新,迟迟不把房间从清单删除。
Highlight:风险与注意点
- epoch 停滞影响淘汰:空闲 CPU 的
rq->cpu_epoch不再推进,差值可能为零,visited_cpus中的陈旧 bit 无法清除。 - 当前材料没有评审结论:4 封相关回复在关键位置被截断,不能据此宣称竞态已解决。
- v5 必须保留 NUMA 约束:
visited_cpus只是额外过滤器,不能替代get_scan_cpumasks(),否则可能违反 NUMA_BALANCING 边界。 - Hackbench 并非全部改善:启用 visited 扫描后仍有约
-1%~-7%的回退项;不能等同于“所有性能项目均无回退”。 - 测试平台覆盖有限:核心数据来自一台 AMD 服务器,还需覆盖不同 LLC/NUMA 拓扑、CPU hotplug、长时间 idle、cpuset/affinity 变化。
- 并发状态需要继续验证:写入 bit 与清除 bit 的锁和生命周期应通过 KCSAN/lockdep 继续检查。
- 调试补丁不可合入:第二次 cpumask 分配失败时的资源清理值得检查,但不应与正式补丁混为一谈。
- 描述与可见 diff 存在细节差异:调试补丁说明提到“两项 sched features”,但可见新增只有
SC_VISIT,可能是正文被截断。
版本变化
- v2:在设置/清除
visited_cpus前增加预检查,减少 cache-to-cache 开销;以 static key 优化 timeout 路径。 - v3:移除 static key 并默认启用;复用
llc_epoch_affinity_timeout;把 epoch 差值计算移入fraction_mm_sched();曾重置work->next防止同进程多线程并发。 - v4:基于 master 重放;引入独立
epoch_timeout避免周期性fraction_mm_sched()刷新原 epoch 后无法淘汰;调整nr_running统计顺序;删除多余work->next重置;加入扫描计数调试补丁。 - v5:恢复
get_scan_cpumasks(),避免违反 NUMA_BALANCING 约束;使用for_each_cpu_and()把扫描进一步限制在 LLC domain 与候选 cpumask 的交集内。
系列与已合入 mainline 的 cache-aware scheduling 基础功能配套:前者提供按 mm 统计 CPU/LLC 活动并选 pref_llc 的机制,本系列只优化其 task_cache_work() 扫描路径。
一句话总结
本系列通过维护每个 mm 最近访问的 CPU 集合,把 task_cache_work() 从近似全机扫描缩减为任务实际 CPU 足迹扫描,但基于 rq->cpu_epoch 淘汰空闲 CPU 的可靠性仍是线程中尚未确认解决的核心问题。