0/3 已展开

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-ID20260729163225.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 核心上,这种短暂唤醒会带来两个问题:

  1. 算力下降:被唤醒的兄弟线程会暂时挤占 busy 兄弟的可用算力。
  2. 恢复延迟:兄弟线程进入 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 兜底

关键实现

  1. 复用 select_rq_mask:避免在栈上分配可能很大的 cpumask,前提是当前 CPU 关中断(lockdep_assert_irqs_disabled())。
  2. fallback 只记一次:只保留遍历中第一个 idle CPU 作 fallback,不会每次循环都更新,避免偏向"更靠后"的 idle CPU。
  3. 剪枝部分忙核的兄弟cpumask_andnot(ilb_cpus, ilb_cpus, cpu_smt_mask(ilb_cpu)) 把当前"部分忙"核的所有 SMT 兄弟直接从候选里抠掉,下一次循环直接跳过,省掉重复的 is_core_idle() 检查。
  4. 不退化 NOHZ 平衡:找不到整核空闲时退回 fallback,确保 find_new_ilb() 不会因为 SMT 拓扑变成"完全无用"。

类比

  • 会议室场景:办公楼找空会议室。旧策略是"哪层有灯关着的就进去";新策略是"先看整层有没有人——有人就跳过整层(同一层其他会议室也不再考虑),只有整层空着才选这层;要是所有楼层都有人,最早看到的空会议室也还能用"。这和 patch 里"优先整核空闲 + fallback 第一个 idle CPU"的语义一致。
  • 共享水压场景:兄弟线程共享一条水管的水压。短时间开水龙头冲一下,水压会短暂下降;普通水管很快就恢复正常,但 Olympus 这种特殊水管要在水流停后等 10 Ki 周期才完全恢复。反复"开一下就关"会让另一边的水压长期偏低。新流程就是尽量别去开"已经有人用水的那一侧的水龙头"。

Highlight:风险与注意点

  1. 额外扫描成本:在 wide SMT(如 8-thread/核)上需要遍历更多 CPU 才选中一个 ILB;某些架构上这可能抬高 ILB 的 wakeup 延迟和能耗。
  2. fallback 选择策略:当前 fallback 锁的是"遍历中第一个 idle CPU",不一定是"最不忙核的 idle CPU"。在 SMT 严重不均衡的极端负载下,可能持续偏向某个被反复踩踏的兄弟,需要作者解释 fallback 的挑选依据。
  3. select_rq_mask 复用假设:依赖关中断/持锁前提,hotplug、softirq、preempt 等路径的调用方需要再确认 lockdep 不变量。
  4. 跨架构影响:10 Ki cycles 的恢复窗口是 Vera/Olympus 的现象,但 patch 是通用代码,所有 SMT 系统都会被改成"优先整核空闲"。在没有这种恢复延迟的 x86/AMD/POWER SMT 上可能带来微小回归,需要 hackbench、schebench、netperf、kernel build 等通用负载的回归数据。
  5. 测试覆盖:patch 只给了一个 ad hoc GEMM benchmark 的数字(6.2 → 9.4 TFLOP/s),缺少 sysbench、DB 类负载、混合读写场景的对比。
  6. 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 不会反复踩到忙兄弟。