0/6 已展开

LLM 分析

sched/fair:非 SMT reciprocal sync wakeup 偏向 waker CPU

系列概况

  • 标题: [PATCH v3] sched/fair: Prefer waker CPU for non-SMT reciprocal sync wakeups
  • 作者: Shubhang Kaushik (Ampere) <sh@gentwo.org>
  • 版本: v3(前序 v1, v2)
  • 规模: 单 patch,1 文件
  • 修改文件: kernel/sched/fair.c
  • 代码统计: +25 / -0
  • Message-ID: 20260727-b4-sched-sync-wakeup-v3-1-90cf481dbd85@gentwo.org
  • 完整性: 完整(commit message + perf bench 数据 + v2/v3 changelog + diff + 测试基线)

补丁目的

针对 pipe-style ping-pong(A↔B互踢)工作负载。当前调度器在 wakee醒来时倾向于把它放到一个 idle CPU 上,但这对 ping-pong 模式反而是负优化——handoff / cache 迁移成本压过 idle选核收益。补丁借助现有 last_wakeewake_wide() 状态识别 narrow reciprocal WF_SYNC 唤醒,在非 SMT平台上让 wakee 优先留在 waker CPU,从而压低 handoff 成本。

旧流程的问题

  1. select_task_rq_fair() 选中 idle CPU,A→B 时把 B 派到远处。
  2. B 立刻唤醒 A,A 又被派到另一个 idle CPU。
  3. 双方每次互踢都跨 runqueue /跨 LLC,handoff 成本成瓶颈。
  4. select_idle_sibling() 在非 SMT 上其实找不到 sibling,原有 "waker-affine 路径" 在这些平台上收益有限。

新流程

  1. select_task_rq_fair() 入口加一个 fast path。
  2. 触发条件:sync && !sched_smt_active() && p->last_wakee == current
  3. 调用 prefer_sync_pair_cpu(p, cpu)
    • (rq->nr_running - cfs_h_nr_delayed(rq)) == 1 → waker CPU 上只有这一个可运行 fair 任务。
    • sched_asym_cpucap_active(),再调 sync_entity_load_avg() + task_fits_cpu() 校验负载是否放得下。
  4. 全部满足 → 直接返回 waker CPU;否则落回原有 wake_affine() / select_idle_sibling() 路径。

Patch 概览

+/*
+ * For reciprocal WF_SYNC handoffs, prefer the waker CPU when it has no
+ * other runnable fair task.
+ */
+static bool prefer_sync_pair_cpu(struct task_struct *p, int cpu)
+{
+    struct rq *rq = cpu_rq(cpu);
+
+    if ((rq->nr_running - cfs_h_nr_delayed(rq)) != 1)
+        return false;
+
+    if (sched_asym_cpucap_active()) {
+        sync_entity_avg(&p->se);
+        if (!task_fits_cpu(p, cpu))
+            return false;
+    }
+
+    return true;
+}
+    if (sync && !sched_smt_active() &&
+        p->last_wakee == current &&
+        prefer_sync_pair_cpu(p, cpu))
+        return cpu;

关键实现

触发条件三层过滤:

  • sync标记:必须是 WF_SYNC/__wake_up_sync* 路径,普通 wake_up 不走。
  • !sched_smt_active():v3 故意把 SMT 系统排除,让 SMT 仍走原有路径,避免 sibling 上的缓存共享被破坏。
  • p->last_wakee == current:上一次 wakee唤醒的是 current(waker),确认是 B→A 反向唤醒的 ping-pong 模式。

waker CPU 空闲度检查:
nr_running - cfs_h_nr_delayed(rq) == 1 只看 fair 层且排除 SLEEPER / delayed dequeue 后的真实可运行数。这样能确保把 wakee 放过去不会挤压其他任务。

asym cpucap 适配:
在大小核系统中,先 sync_entity_avg() 同步 se 的负载平均,再 task_fits_cpu() 验证该 CPU 容量足够,避免在 LITTLE核上塞 big-task。

v3 相比 v2 的精简:

  • 删除多余的 cpumask_test_cpu(已被 want_affine 覆盖)。
  • p->last_wakee 用普通读而非 READ_ONCE(),因为调用点已持有必要锁。

类比

想象两支接力队伍 A 和 B 在80 跑道的体育馆里:

  • 旧流程:A 跑到第 50 赛道把棒交给 B,B 接力后再跑到第 70 赛道把棒还给 A。每一次交棒都跨半个场馆。
  • 新流程:发现只是 A↔B 互踢,就让 B 守在 A 旁边的赛道等 A 下一次踢过来——两人都几乎原地不动。

另一层类比:办公室茶水间里,A 和 B 在轮流接话。原先每次 A 说完,B 必须去隔壁茶水间;现在系统识别出这是 "来回对聊",就让 B 留在原处回应,省去走廊来回。

Highlight:风险与注意点

  1. SMT 上的扩展争议:K Prateek Nayak(AMD)建议把判定下沉到 select_idle_sibling(),让 SMT 系统也能拿到收益。Shubhang 在 v3 选择保守只做非 SMT,需要关注后续是否并入 SMT路径。
  2. sync wakeup 的语义之争:Shrikanth Hegde(IBM)质疑 sync 是给调用方用的 API(wake_up_interruptible_sync vs wake_up),调度器不应替调用方假设行为。他指出 networking 已存在 sync use/abuse,呼吁先界定策略:总是偏向?仅 idle 时?限定特定场景?
  3. nr_running - cfs_h_nr_delayed 的精确性:必须确认 delayed dequeue 路径下这个减法不被新 deferred wakeup 路径干扰。
  4. task_fits_cpu() 的开销:在 asym系统中每次 sync wakeup 都做一次容量适配检查,吞吐型工作负载可能有影响,目前 patch 没给出 asym 平台数据。
  5. 基线和覆盖度:perf bench sched pipe +30% 漂亮,但只测了 80 核非 SMT Altra;多核 SMT、混部容器、长链路锁竞争场景都没覆盖。
  6. last_wakee 非原子读:v3 用普通读替代 READ_ONCE(),需确认 select_task_rq_fair() 调用点的锁语境已经安全。

版本变化

v1 → v2

  • 把 reciprocal handoff 偏向移到现有 SD_WAKE_AFFINE domain 检查之下。
  • 从 changelog 删除 futex 动机。
  • 重新跑 perf bench sched pipe 数据。

v2 → v3

  • 把直接 waker CPU 偏向限制在 !sched_smt_active()
  • 删除多余的 affinity 检查。
  • 用普通 p->last_wakee 读取代 READ_ONCE()
  • Rebase 到 v7.2-rc5,刷新测试基线。

讨论中的 v4 方向(未发布)

  • 下沉到 select_idle_sibling(),使用 rq->rd->span 替代 sched_domain_span(sd_llc),统一覆盖 asym + SMT 场景。
  • Shrikanth 提议先确定 sync wakeup 的总体语义再决定是否合入。

一句话总结

补丁借助 last_wakee 识别 pipe-style ping-pong WF_SYNC 唤醒,在非 SMT 系统上让 wakee 留在 waker CPU 来压低 handoff 成本;线程后续讨论正围绕 SMT 覆盖和 sync wakeup 整体策略展开。

                     select_task_rq_fair(p, prev_cpu, wake_flags)
                                       |
 sync && !sched_smt_active()
 && p->last_wakee == current ?
                          | \
                         yes                       no
                          |                          \
 prefer_sync_pair_cpu(p, cpu)         wake_affine /
 |                       select_idle_sibling
            rq idle (nr=1) && (original path)
            fits on asym ?
 /         \
              yes no
               |            \
   +-----------v-----------+  \
   | return waker CPU       | \
   | (avoid handoff cost)  |    \
   +------------------------+    \
                                 v                          return new_cpu
                          (idle sibling / affine)