0/8 已展开

LLM 分析

Linux PSI 聚合优化:跳过 zero non-idle delta 的 CPU

系列概况

补丁目的

collect_percpu_times() 每个采样窗口遍历所有 possible CPU,对每个 CPU 算出 PSI_NONIDLE 时间(nsecs_to_jiffies)作为权重,再加权到 deltas[]

当 PSI_NONIDLE delta 为 0 时,加权贡献本身就是 0,纯属空跑。

本 patch 利用 get_recent_times() 已经设好的 cpu_changed_states & (1 << PSI_NONIDLE) 位作为天然 zero-delta 标记,命中即 continue,省掉 nsecs_to_jiffies 与后续 u64 mul-add。

旧流程的问题

for each possible cpu:
    get_recent_times(... &cpu_changed_states);
    changed_states |= cpu_changed_states;
    nonidle = nsecs_to_jiffies(times[PSI_NONIDLE]);   /* 一定执行的除法 */
    nonidle_total += nonidle;
    for s in PSI_NONIDLE:
        deltas[s] += times[s] * nonidle;              /* 结果是 += 0 */

nsecs_to_jiffies 一定执行;容器/监控密集场景下,PSI trigger 触发频率(百 Hz 级)放大 O(nr_possible_cpus) 扫描的成本,CPU 越多浪费越明显。

新流程

for each possible cpu:
    get_recent_times(... &cpu_changed_states);
    if (!(cpu_changed_states & (1 << PSI_NONIDLE)))
        continue;
    changed_states |= cpu_changed_states;
    nonidle = nsecs_to_jiffies(times[PSI_NONIDLE]);
    nonidle_total += nonidle;
    for s in PSI_NONIDLE:
        deltas[s] += times[s] * nonidle;

changed_states |= cpu_changed_states 在循环里仍执行,聚合层 trigger reschedule 决策不受影响。

Patch 概览

  • v1:if (!nonidle) continue; —— 先算 nonidle 再判断,跳过一次 jiffies 转换。
  • v2/v3:依 Johannes 建议改为 cpu_changed_states 位判断,省掉 nsecs_to_jiffies
  • v4:去掉误加的 Reviewed-by,改 Suggested-by;commit 措辞改为 "delta"。
  • v5:精简注释(+3 行)+ i7-8700 的 4 组 A/B 数据。
  • v6:补 -smp 2..32 的 A/B 数据;强调 psi_rtpoll_work() 调用频率。

关键实现

最终 hunk(v5/v6)落在 collect_percpu_times() 内、get_recent_times() 之后:

get_recent_times(group, cpu, aggregator, times, &cpu_changed_states);
+ if (!(cpu_changed_states & (1 << PSI_NONIDLE)))
+     continue;
changed_states |= cpu_changed_states;
nonidle = nsecs_to_jiffies(times[PSI_NONIDLE]);

get_recent_times() 只在 PSI_NONIDLE delta 非零时置位,是天然的"非零标记",无需新状态变量。

类比

老办法像图书馆每周盘点:每类书不管这周有没有新书,都要把书架重新数一遍再算借出率;新办法先翻"本周新书登记本"(cpu_changed_states 位),没新增就直接跳过这一类(continue)。馆藏(CPU 数)越多,省得越多。

+--------------------------+
|   for each possible cpu  |
+--------------------------+
            |
            v
+--------------------------+
|  get_recent_times(...)   |
|   sets PSI_NONIDLE bit   |
+--------------------------+
            |
            v
+--------------------------+
|  test PSI_NONIDLE bit    |
+-----------+--------------+
  bit clear |          bit set
            v              v
    +-----------+   +-----------------------+
    | continue  |   | changed_states |= ... |
    +-----------+   +-----------------------+
                            |
                            v
                  +-----------------------+
                  | nsecs_to_jiffies()    |
                  +-----------------------+
                            |
                            v
                  +-----------------------+
                  | deltas[s] += t[s]*n   |
                  +-----------------------+

Highlight:风险与注意点

  • 假设 get_recent_times() 的 PSI_NONIDLE 置位语义稳定;若日后调整置位条件,skip 可能漏掉真正需要累加的 CPU,回归旧 bug。
  • v2/v3 把 Johannes 的 "Makes sense + 实现建议" 误标 Reviewed-by,v4 改 Suggested-by 才符合 review tag 的语义(v4 changelog 已明确说明)。
  • Peter Zijlstra 在 v4 直接要求 benchmark 证据;前几版没数据会被打回,v5/v6 的 A/B 表是入主干的关键。
  • skip 路径不能影响 changed_states 最终的 OR 结果,否则 psi_rtpoll_work() 的 trigger 行为会变化;当前实现中 changed_states |= cpu_changed_states 仍在 skip 之后执行,行为正确。
  • 用户态监控 agent 是最大受益者,但 -smp 超过物理核时基线本身有调度噪声,v6 数据展示的是"趋势"而非单点最优。

版本变化

  • v1 → v2:判断条件由 !nonidle 改为 cpu_changed_states 位检查,省掉 nsecs_to_jiffies
  • v2 → v3:仅 rebase 重发,逻辑无变化。
  • v3 → v4:删除 Reviewed-by 改 Suggested-by;commit message 措辞改为 "PSI_NONIDLE delta"。
  • v4 → v5:精简掉内联注释块(-4 行)+ i7-8700 (6C/12T) 的 4 组 A/B 数据。
  • v5 → v6:补 -smp 2/4/8/12/16/24/32 的 A/B 数据;明确 psi_rtpoll_work() 调用频率量级(~160 次/s)。

一句话总结

get_recent_times() 已设的 PSI_NONIDLE 位作为 zero-delta 标记,让 collect_percpu_times() 直接跳过对加权贡献为 0 的 CPU,在 12 线程以上机器的 /proc/pressure/cpu 读延迟上省 4%-18%,随 CPU 数放大。