0/3 已展开

LLM 分析

sched/fair:互反 sync 唤醒优先留在 waker CPU

系列概况

字段内容
标题[PATCH] sched/fair: Prefer waker CPU for reciprocal sync wakeups
作者Shubhang Kaushik (Ampere)
版本v1(单 patch)
规模1 file changed, 30 insertions(+), 1 deletion(-)
修改文件kernel/sched/fair.c
基线v7.2-rc4(mainline origin/master @ b95f03f04d47)
Message-ID20260721-b4-sched-sync-wakeup-v1-1-dc94f184e27f@gentwo.org
完整性标题、commit message、diffstat、性能数据完整;含 Christian Loehle 的两轮评审,作者在 message 3 已承认两项需修改

补丁目的

pipe 类两进程轮流传数据负载会形成稳定 ping-pong(A 唤醒 B,B 再唤醒 A…)。当前 select_task_rq_fair()WF_SYNC 唤醒时倾向把 wakee 投到"看起来更闲"的 CPU,让 task 对反复跨越 runqueue,cache 冷启动与迁移成本压过 idle 收益。

补丁要做的:识别 narrow 互反同步唤醒,让 wakee 继续留在 waker CPU,绕过 wake_affine + select_idle_sibling 的强制再分布。

旧流程的问题

        A (running) on cpu0                  idle CPU = cpu2
            |                                    ^
            | wake B (WF_SYNC)                   |
            v                                    |
   select_task_rq_fair() ------ pick cpu2 ------+
            |
            v
   B migrates to cpu2  -> cache cold, ping latency high
            |
   B wakes A -> A pushed to cpu3 by wake_affine / load_balance
            |
   cross-CPU handoff cost dominates pipe throughput

只要 B 落到空闲 CPU,下一轮 A 又会被 SDF_WAKE_AFFINE 推到旁边。两 task 在两颗 CPU 间来回弹跳。

新流程

        A (running) on cpu,  p->last_wakee == current (B)
            |
            | wake B (WF_SYNC, !wake_wide)
            v
   select_task_rq_fair()
            |
            | sync && !wide &&
            | READ_ONCE(p->last_wakee) == current &&
            | prefer_sync_pair_cpu(p, cpu)
            | yes
            +-----------> return cpu        (Christian points out:
                                            this branch must move
                                            BELOW SD_WAKE_AFFINE
                                            check, otherwise it
                                            bypasses isolcpus)
            | no
            v
   want_affine + select_idle_sibling  (existing path)

prefer_sync_pair_cpu() 三道闸门:

static bool prefer_sync_pair_cpu(struct task_struct *p, int cpu)
{
    struct rq *rq = cpu_rq(cpu);

    /* (1) waker CPU must have no other runnable fair task */
    if ((rq->nr_running - cfs_h_nr_delayed(rq)) != 1)
        return false;

    /* (2) wakee must be allowed on this CPU */
    if (!cpumask_test_cpu(cpu, p->cpus_ptr))
        return false;

    /* (3) on asymmetric-capacity systems, wakee must fit */
    if (sched_asym_cpucap_active()) {
        sync_entity_load_avg(&p->se);
        if (!task_fits_cpu(p, cpu))
        return false;
    }

    return true;
}

关键实现

调用点在 select_task_rq_fair(),改造片段:

int wide = 0;
...
if (wake_flags & WF_TTWU) {
    record_wakee(p);                 /* record who woke current */
    wide = wake_wide(p);             /* hoist, avoid recomputation */
    ...
}
...
/* NOTE: per Christian's review, this must move BELOW the
 * SD_WAKE_AFFINE check; early-return here bypasses isolcpus */
if (sync && !wide &&
    READ_ONCE(p->last_wakee) == current &&
    prefer_sync_pair_cpu(p, cpu))
    return cpu;

want_affine = !wide && cpumask_test_cpu(cpu, p->cpus_ptr);

record_wakee() 前一轮 try_to_wake_up 已经写过 p->last_wakee;再用 READ_ONCE 读回对比 current(waker),就把"A↔B 互为唤醒"恢复出来。wake_wide() 基于前几次 wakeup 间隔判定是否 wide,本补丁取其反向信号。

类比

把调度器想成乒乓球馆里的球童。两人对打时,球童不该每次都把球送隔壁空球桌——搬运时间反而盖过打球时间。同一张球桌继续打最快:先确认这张桌没别人在用(nr_running - cfs_h_nr_delayed == 1),再确认对方能在这张桌上打(cpus_ptr 检查),再确认桌子的尺寸装得下(不对称算力检查)——满足就保留,不满足才去找空桌。

Highlight:风险与注意点

  • WF_SYNC 覆盖范围:commit message 写"futex ping-pong",但 Christian 指出 futex 唤醒通常不带 WF_SYNC,实际不命中 fast path。message 中要么把覆盖范围限定在"kernel 内部同步原语",要么给出真实命中集合。
  • early-return 位置错误会绕开 isolcpus:新分支必须后移到 SD_WAKE_AFFINE 之后;当前实现先 short-circuit,会无视 isolcpus= / cpuset 钉死的 CPU 列表,破坏部署策略。Christian 明指,作者已在 message 3 确认要挪位。
  • nr_running - cfs_h_nr_delayed == 1 偏窄:仅统计 CFS、扣除 delayed 任务,没算 RT、IRQ、kworker。若 waker CPU 挂着 RT 而 CFS 视图"剩一个",唤醒后实际争用仍存在;可考虑 rq->nr_running == 1 整盘判定或额外排除 RT。
  • wake_wide() 启发式滞后:依赖前几次 wakeup 间隔统计,对突变敏感;ping-pong 节奏一旦被破就退回老路径,性能回退呈阶跃式而非平滑。
  • 跨 LLC / NUMA 不感知:补丁对同 LLC 上的 pair 友好;跨 LLC 或跨节点时 cache 收益很快被摊薄,目前没有按拓扑距离的二次过滤。
  • 测试覆盖偏窄:仅在 80 核 Ampere Altra 上跑 perf bench sched pipe(+21%),hackbench/schbench/SPECjBB 测无回归。还需在 SMT、小核混合、不同 cache 拓扑下复测,避免只在 Altra 上"看起来好"。
  • 与 wake_affine 既有机制的张力:是少在原 CPU 的反向偏好,与 select_idle_sibling 在 narrow pair 上互为制约,需要互斥策略文档化以免被并入 future task 时语义漂移。

版本变化

仅有 v1。作者在 message 3 已承认 futex 范围描述偏宽、early-return 位置需后挪;可预期 v2 调整实现位置、改写 commit message,并把 wide = wake_wide(p) 改成 READ_ONCE()

一句话总结

这是一条 bugfix 性质的提交:在 narrow 互反 WF_SYNC 唤醒中让 wakee 留在 waker CPU、对抗 ping-pong handoff cost,但评审指出 futex 实际不带 WF_SYNC、early-return 必须后移以免绕过 isolcpus。primary_category = bugfix 因其修正"反复迁移导致 latency 退化"这一可重现性能缺陷;is_important = true 因为涉及核心 select_task_rq_fair 路径且已有 maintainer 评审跟进。