sched discussion
[PATCH v2] sched/fair: Prefer fully idle cores for NOHZ balancing
LLM 分析
sched/fair:NOHZ 平衡优先选择整核空闲的 CPU
系列概况
- 标题:[PATCH v2] sched/fair: Prefer fully idle cores for NOHZ balancing
- 作者:Andrea Righi arighi@nvidia.com
- 版本:v2(单封 patch,非系列)
- 规模:1 file changed, 39 insertions(+), 9 deletions(-),净 +30 行
- 修改文件:
kernel/sched/fair.c(仅find_new_ilb()) - 代码统计:在
find_new_ilb()内把"找第一个 idle housekeeping CPU"改写为"优先找整核空闲的 housekeeping CPU,并保留 fallback" - Message-ID:
20260729163225.1987068-1-arighi@nvidia.com - 完整性:thread 共 3 封邮件——1 patch + 2 reply;两封 reply 的正文在 lore 抓取中均被截断,只能看到对 v2 commit message 的大段引用,无法读到完整的 review 意见
补丁目的
find_new_ilb() 负责为 NOHZ idle load balancer(ILB)挑一个 housekeeping CPU 跑平衡任务。旧逻辑只关心"集合里第一个 idle CPU",没看这个 CPU 的物理核上是不是还有兄弟线程在跑。
在 NVIDIA Vera 的 Olympus 核心上,这种短暂唤醒会带来两个问题:
- 算力下降:被唤醒的兄弟线程会暂时挤占 busy 兄弟的可用算力。
- 恢复延迟:兄弟线程进入 WFI 后,单线程性能完全恢复要等 10 Ki cycles 的资格窗口(qualification interval)。
ILB 是周期性触发的,所以即使单次唤醒时间很短,频繁的短脉冲会让 busy 兄弟长期处于"性能未完全恢复"的状态。
目标是:让 ILB 优先挑一个整核都空闲的 housekeeping CPU,避免踩到 Olympus 这种恢复成本高的兄弟;同时不能因此让 NOHZ 平衡停摆。
旧流程的问题
for_each_cpu_and(ilb_cpu, nohz.idle_cpus_mask, hk_mask) {
if (idle_cpu(ilb_cpu))
return ilb_cpu;
}
return -1;
问题点:
- 候选集合是
nohz.idle_cpus_mask ∩ HK_TYPE_KERNEL_NOISE,不感知 SMT 拓扑; - 选出的 CPU 可能和某个 housekeeping 线程共享同一物理核;
- 在 Olympus/Vera 上,唤醒这个 CPU 会让兄弟的性能暂时下降,并在兄弟进入 WFI 后持续约 10 Ki cycles;
- ILB 是周期性任务,反复触发会让干扰长期存在,即使实际工作时间重叠很少。
新流程
ilb_cpus = this_cpu_cpumask_var_ptr(select_rq_mask); /* 复用每 CPU 位图 */
cpumask_and(ilb_cpus, nohz.idle_cpus_mask,
housekeeping_cpumask(HK_TYPE_KERNEL_NOISE));
fallback = -1;
for_each_cpu(ilb_cpu, ilb_cpus) {
if (!idle_cpu(ilb_cpu))
continue;
/* 部分忙的核心:记下首个 idle CPU 作 fallback,
* 再把这个核的其他兄弟从候选里抠掉,避免重复 is_core_idle */
if (sched_smt_active() && !is_core_idle(ilb_cpu)) {
if (fallback < 0)
fallback = ilb_cpu;
cpumask_andnot(ilb_cpus, ilb_cpus, cpu_smt_mask(ilb_cpu));
continue;
}
return ilb_cpu; /* 整核空闲:直接选中 */
}
return fallback; /* 没有任何整核空闲的 SMT 核:退回首个 idle CPU */
非 SMT 系统下 sched_smt_active() 为 false,整段 SMT 分支不进入,逻辑退化为"返回第一个 idle CPU",与旧行为一致。
Patch 概览
单文件单 hunk,所有改动集中在 find_new_ilb():
- hk_mask, ilb_cpu
+ ilb_cpus (复用 select_rq_mask), ilb_cpu, fallback = -1
+ lockdep_assert_irqs_disabled() 断言持锁/关中断
- for_each_cpu_and(...) { if idle return }
+ cpumask_and(...) 预处理候选集
+ for_each_cpu(...) 内层判断 is_core_idle,剪枝 cpu_smt_mask()
+ 末尾 return fallback 兜底
关键实现
- 复用
select_rq_mask:避免在栈上分配可能很大的 cpumask,前提是当前 CPU 关中断(lockdep_assert_irqs_disabled())。 fallback只记一次:只保留遍历中第一个 idle CPU 作 fallback,不会每次循环都更新,避免偏向"更靠后"的 idle CPU。- 剪枝部分忙核的兄弟:
cpumask_andnot(ilb_cpus, ilb_cpus, cpu_smt_mask(ilb_cpu))把当前"部分忙"核的所有 SMT 兄弟直接从候选里抠掉,下一次循环直接跳过,省掉重复的is_core_idle()检查。 - 不退化 NOHZ 平衡:找不到整核空闲时退回 fallback,确保
find_new_ilb()不会因为 SMT 拓扑变成"完全无用"。
类比
- 会议室场景:办公楼找空会议室。旧策略是"哪层有灯关着的就进去";新策略是"先看整层有没有人——有人就跳过整层(同一层其他会议室也不再考虑),只有整层空着才选这层;要是所有楼层都有人,最早看到的空会议室也还能用"。这和 patch 里"优先整核空闲 + fallback 第一个 idle CPU"的语义一致。
- 共享水压场景:兄弟线程共享一条水管的水压。短时间开水龙头冲一下,水压会短暂下降;普通水管很快就恢复正常,但 Olympus 这种特殊水管要在水流停后等 10 Ki 周期才完全恢复。反复"开一下就关"会让另一边的水压长期偏低。新流程就是尽量别去开"已经有人用水的那一侧的水龙头"。
Highlight:风险与注意点
- 额外扫描成本:在 wide SMT(如 8-thread/核)上需要遍历更多 CPU 才选中一个 ILB;某些架构上这可能抬高 ILB 的 wakeup 延迟和能耗。
- fallback 选择策略:当前 fallback 锁的是"遍历中第一个 idle CPU",不一定是"最不忙核的 idle CPU"。在 SMT 严重不均衡的极端负载下,可能持续偏向某个被反复踩踏的兄弟,需要作者解释 fallback 的挑选依据。
select_rq_mask复用假设:依赖关中断/持锁前提,hotplug、softirq、preempt 等路径的调用方需要再确认 lockdep 不变量。- 跨架构影响:10 Ki cycles 的恢复窗口是 Vera/Olympus 的现象,但 patch 是通用代码,所有 SMT 系统都会被改成"优先整核空闲"。在没有这种恢复延迟的 x86/AMD/POWER SMT 上可能带来微小回归,需要 hackbench、schebench、netperf、kernel build 等通用负载的回归数据。
- 测试覆盖:patch 只给了一个 ad hoc GEMM benchmark 的数字(6.2 → 9.4 TFLOP/s),缺少 sysbench、DB 类负载、混合读写场景的对比。
- review 反馈不可见:thread 中 Mete Durlu 和 Andrea Righi 的两封 reply 在 lore 抓取中均被截断,无法看到对 v1/v2 的具体评审意见。
版本变化
- v1 → v2:在遍历过程中对"部分忙的核心"调用
cpumask_andnot,把它的其他 SMT 兄弟直接从候选位图中移除,避免在 wide SMT 上对同一核的兄弟重复做is_core_idle()。这条优化来自 v1 的 reviewer K Prateek Nayak 的反馈(patch log 直接点名)。这是 v2 唯一的代码改动。
一句话总结
让 NOHZ idle load balancer 优先挑"整核都空闲"的 housekeeping CPU,并在遍历中剪掉部分忙核的兄弟;找不到整核空闲时退回首个 idle CPU,确保在 NVIDIA Olympus/Vera 这种兄弟线程需要 10 Ki cycles 才能恢复单线程性能的 SMT 上,ILB 不会反复踩到忙兄弟。