0/1 已展开

LLM 分析

sched/numa:修复 scan staggering 的时间单位错误

系列概况

  • 标题:[PATCH] sched/numa: Fix time unit mismatch in scan staggering
  • 作者:Hui Su sh_def@163.com
  • 版本:单封 patch(无 v2/v3 演进,无后续回复)
  • 规模:1 file changed, 4 insertions(+), 5 deletions(-)
  • 修改文件kernel/sched/fair.cinit_numa_balancing()
  • 代码统计:净 -1 行,整体替换 9 行实现
  • Message-ID20260827095902.2645166-1-sh_def@163.com
  • 完整性:单 patch 完整可独立 review;引用 Fixes: 137844759843 指向引入 bug 的提交

补丁目的

init_numa_balancing() 负责为新 fork 出来的 task 初始化 node_stamp
用于错开(stagger)共享同一 mm_struct 的多个线程触发 NUMA scan 的时间,
避免它们同时打爆某个 node 的 page fault 路径。但原实现把"毫秒"和"纳秒"
混在一起做 min_t() 比较,使得实际计算出的 stagger 几乎为 0,
多线程 NUMA 工作负载因此被错误地集中在最初几毫秒内完成扫描。

旧流程的问题

unsigned int delay;
delay = min_t(unsigned int, task_scan_max(current),
              current->numa_scan_period * mm_users * NSEC_PER_MSEC);
delay += 2 * TICK_NSEC;
p->node_stamp = delay;

关键问题:

  1. 单位错配task_scan_max() 返回毫秒(ms),
    current->numa_scan_period * mm_users * NSEC_PER_MSEC 却是纳秒(ns),
    min_t() 在两个完全不同的单位之间取最小值,逻辑上无意义。
  2. unsigned int 截断:所有运算都落在 32 bit,
    当 ns 值超过 UINT_MAX 时直接被截断;32 位平台上乘法本身也会溢出。
  3. 实际效果:默认 scan_period_min = 1000 ms,2 个 mm user 时
    min_t() 选出 2000(ms),但 p->node_stamp 期望 ns 量级;
    结果 node_stamp 大约只有 2 ms,stagger 形同虚设。

新流程

u64 delay_ms;
delay_ms = (u64)current->numa_scan_period * mm_users;
delay_ms = min_t(u64, delay_ms, task_scan_max(current));
p->node_stamp = delay_ms * NSEC_PER_MSEC + 2 * TICK_NSEC;

思路调整:

  1. 先在 ms 域内比较,去掉 (...)*NSEC_PER_MSEC
  2. 全部用 u64,避免 32 位溢出与截断;
  3. min_t() 之后才统一换算成 ns,最后再加 2 个 tick 的偏移。

Patch 概览

内容
修复 commit137844759843 ("sched/numa: Stagger NUMA balancing scan periods for new threads")
影响函数init_numa_balancing()
触发条件新线程 fork,且共享 mm_structmm_users >= 1
受益场景多线程 NUMA 工作负载(数据库、SPECjbb、JVM 等)

关键实现

把整段 delay 替换为 delay_ms,并把 unsigned int 升到 u64

u64 delay_ms;
delay_ms = (u64)current->numa_scan_period * mm_users;
delay_ms = min_t(u64, delay_ms, task_scan_max(current));
p->node_stamp = delay_ms * NSEC_PER_MSEC + 2 * TICK_NSEC;

注意 (u64) 强转放在乘法第一个操作数上——这是标准写法,
能强制编译器把整条乘法链提升到 64 bit,避免 unsigned int * int
先按 32 位算再提升的潜在溢出。

消费侧 task_tick_numa() 已经按 ns 解释 node_stamp
所以最后乘回 NSEC_PER_MSEC 就能让 stagger 时间真正落在秒级/百毫秒级。

类比

node_stamp 想成"闹钟下次响的时刻":

  • 旧实现:把"5 分钟"和"300 纳秒"放在一起选更小的那个,
    闹钟设成了"再等 300 纳秒响",相当于立刻响。
  • 新实现:先把分钟数算清楚(2000 分钟),再统一换算成纳秒,
    闹钟被正确设到 2000 分钟之后,多个线程自然错开响铃。

也可以理解成"先比距离再换单位":你不会拿"3 公里"和"5 米"比谁更短,
而是先把两边都化成米再比。

Highlight:风险与注意点

  • 这是回归修复:bug 由 commit 137844759843 引入,
    旧版本上 stagger 工作正常;stable backport 优先级较高。
  • Fixes: tag 引用的是 commit hash 短前缀:patch 引用是
    137844759843,合并时需要核对完整 hash。
  • 单封 patch,无 review 反馈:作者还没有收到任何回复,
    后续是否会有 Ingo/Peter 给出 ack 或要求 v2 仍待观察。
  • 32 位平台上的额外风险:旧逻辑在 32 位 kernel 上
    numa_scan_period * mm_users * NSEC_PER_MSEC 早早溢出,
    修复同时解决了这条隐藏路径。
  • 验证点:可以用 tracepoint:sched_migrate_task
    /proc/<pid>/numa_scan_seq 在多线程工作负载下观察
    不同线程 node_stamp 是否真正错开。
       task_scan_max()         numa_scan_period * mm_users
             |                              |
             v                              v
           (ms)                     (* NSEC_PER_MSEC)  -> (ns)
             \                              /
              \     min_t(unsigned int)    /   <-- unit mismatch
               +--------------+------------+
                              |
                              v
                      unsigned int delay
                              |
                              v p->node_stamp = delay (expected ns, got ms)
                              |
                              v
              task_tick_numa() fires almost immediately
       task_scan_max()              numa_scan_period * mm_users
             |                              |
             v                              v
           (ms)                          (u64, ms)
             \                              /
              \       min_t(u64)           /   <-- same-unit compare
               +--------------+------------+
                              |
                              v
                        u64 delay_ms
                              |
                              v
              delay_ms * NSEC_PER_MSEC + 2 * TICK_NSEC
                              |
                              v
                 p->node_stamp (ns, correct magnitude)
                              |
                              v
 task_tick_numa() fires after the real stagger window

版本变化

当前只看到 v1,无 v2/v3。

与其他相关 patch 系列的关联

  • 直接修复对象137844759843 ("sched/numa: Stagger NUMA balancing scan periods for new threads")
  • 相关背景sched/numa 子系统中 task_scan_max()
    numa_scan_periodNSEC_PER_MSEC 等单位约定在 fair.c 多个函数中保持一致;
    本 patch 仅修正 init_numa_balancing() 的局部计算错误,
    未触及 task_tick_numa()task_scan_stat() 的消费逻辑。

一句话总结

init_numa_balancing() 把毫秒和纳秒丢进同一个 min_t() 还用 32 bit 算,
导致多线程 NUMA stagger 几乎失效;本 patch 改成在毫秒域内比较、用 u64
算乘法、最后再统一换算成纳秒,让 node_stamp 真正落到应有的时间量级。