0/8 已展开

LLM 分析

sched/fair: NOHZ 平衡优先选择整核空闲的 CPU

系列概况

  • 标题: [PATCH v3] sched/fair: Prefer fully idle cores for NOHZ balancing
  • 作者: Andrea Righi arighi@nvidia.com
  • 版本: v3(独立单 patch)
  • 规模: 1 file changed, 47 insertions(+), 9 deletions(-)
  • 修改文件: kernel/sched/fair.c
  • 代码统计: 重写 find_new_ilb();新增 fallback 路径与 cpumask_andnot 剪枝
  • Message-ID: 20260731191957.3199642-1-arighi@nvidia.com
  • 完整性: 8 封邮件齐全,含 1 封 patch + 多位 reviewer 回复(Vincent Guittot、Prateek Nayak、Mete Durlu、Andrea 本人)

补丁目的

让 NOHZ idle load balancer(ILB)挑选目标 CPU 时,优先选整颗 SMT core 都空闲的 CPU,避免短暂唤醒一个本来空闲的 SMT 兄弟线程,从而防止把另一个繁忙线程的单线程性能拖下。问题在 NVIDIA Olympus(Vera)这类 SMT 上尤其明显:唤醒 idle 兄弟会触发 power/performance qualification 窗口——只有兄弟持续空闲 10 Ki cycles 之后,busy 线程才能恢复满单线程性能。

旧流程的问题

旧版 find_new_ilb() 几乎只是简单线性扫描:

hk_mask = housekeeping_cpumask(HK_TYPE_KERNEL_NOISE);
for_each_cpu_and(ilb_cpu, nohz.idle_cpus_mask, hk_mask) {
    if (ilb_cpu == this_cpu) continue;
    if (idle_cpu(ilb_cpu))
        return ilb_cpu;
}
return -1;

它只看候选 CPU 自己是不是 idle,不看它的 SMT 兄弟是不是有任务在跑。这意味着:在 SMT 系统上,即使系统里存在一个整颗 core 都空着、唤醒它完全不打扰任何人的候选,ILB 还是会先选一个"半活" core 上的 idle 兄弟,结果把另一个 busy 线程的单线程性能拖下去。

新流程

+------------------------------+
| find_new_ilb()               |
| lockdep: irqs_disabled       |
+--------------+---------------+
               |
               v
   ilb_cpus = nohz.idle_cpus_mask
               AND
   housekeeping_cpumask(HK_TYPE_KERNEL_NOISE)
               |
               v
   for_each_cpu(ilb_cpu, ilb_cpus)
               |
        +------+------+
        |             |
   CPU is busy    CPU is idle
        |             |
   prune SMT     is_core_idle()?
   siblings in   +----------+----------+
   ilb_cpus if   |                     |
   fallback set  yes                   no
        |        |                     |
     continue    v                fallback =
                 return ilb_cpu    ilb_cpu, prune
                            siblings, continue
                                          |
                                          v
                              loop end: return fallback
                                          |
                                          v
                              nothing found: return -1

核心选择逻辑:

for_each_cpu(ilb_cpu, ilb_cpus) {
    if (ilb_cpu == this_cpu)
        continue;

    if (!idle_cpu(ilb_cpu)) {
        if (sched_smt_active() && fallback >= 0)
            cpumask_andnot(ilb_cpus, ilb_cpus,
                           cpu_smt_mask(ilb_cpu));
        continue;
    }

    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;

关键实现

  • 用每 CPU 的 select_rq_mask 当 scratch bitmask(lockdep_assert_irqs_disabled() 保证不会被并发使用)。
  • 候选集一次性算好:nohz.idle_cpus_mask ∧ housekeeping_cpumask(HK_TYPE_KERNEL_NOISE)
  • 关键剪枝:遇到一个 core 的任一线程非 idle 或半活,就把这个 core 的所有兄弟从 mask 中抹掉,避免在宽 SMT 系统上反复调用 is_core_idle()
  • 保留 fallback:整系统找不到整核空闲时,退回第一个 idle CPU,保证 NOHZ 平衡还能前进,不会卡住。
  • 非 SMT 系统走原路径(sched_smt_active() 为 false)。

类比

把 SMT core 想成一张上下铺:上铺正在深睡(busy 线程跑得正欢),下铺空着(idle 线程在等 ILB 召唤)。

旧策略 = "看到下铺空就直接推他去做 ILB",结果晃动把上铺摇醒半梦半醒,要一段时间才能重新进入深度睡眠(满单线程性能)——而 ILB 是短命任务,做完很快又下去了,于是上铺老是被摇,新策略 = "先看整张床是不是都没人睡":全空才推下铺,否则才推他,并记下他作为 fallback;这样能避就避,必须打扰也只打扰一次。

Highlight:风险与注意点

  • 唤醒延迟:可能挑到更远的 core,ILB latency 略增;某些 arch 上能耗也可能上升。
  • this_cpu 冗余争议:Vincent Guittot 指出 nohz_balancer_kick() 已经先做 nohz_balance_exit_idle(rq),自己不应还在 nohz.idle_cpus_mask 里;Prateek Nayak 建议把 ilb_cpu == this_cpu!idle_cpu(...) 合并为一条 if
  • fallback 副作用:保留了第一条 idle CPU 作 fallback,存在仍选到半活 core 的可能——若该 core 上 busy 线程频繁进出,wfi qualification 干扰可能持续。
  • 剪枝重叠:看到 busy CPU 时只在 fallback >= 0 下才剪枝兄弟;与"半活 core 必剪枝"两条逻辑部分重叠,后续 v4 可能再合并。
  • 测试覆盖:作者只给了 ad hoc GEMM、每 SMT core 一个 CPU-intensive 任务的 6.2 → 9.4 TFLOP/s 数字;多任务混合、不同 SMT 宽度、不同 arch 下的回归数据还没看到。

版本变化

  • v1 → v2(Prateek Nayak 建议):在宽 SMT 上,对半活 core 立即剪枝兄弟,避免重复 is_core_idle() 调用。
  • v2 → v3(Mete Durlu 建议):在已发现 idle fallback 之后,再遇到 busy CPU 时也跳过其所有兄弟,避免在已知 busy core 上重复检查。

一句话总结

让 NOHZ idle load balancer 优先选整核空闲的 CPU 来跑,避免在 NVIDIA Olympus/Vera 这种 SMT 上短暂唤醒空闲兄弟,反复触发 10 Ki cycles 的单线程性能 qualification 窗口。