sched discussion
[PATCH v2] sched/fair: Prefer waker CPU for reciprocal sync wakeups
LLM 分析
sched/fair:偏向 waker CPU 的 reciprocal sync wakeup 优化
系列概况
- 标题:
[PATCH v2] sched/fair: Prefer waker CPU for reciprocal sync wakeups - 作者: Shubhang Kaushik (Ampere Computing)
- 版本: v2(thread 末尾出现 v3 链接
20260727-b4-sched-sync-wakeup-v3-1-90cf481dbd85@gentwo.org) - 规模: 单 patch 系列,1 文件,+28 / -0
- 修改文件:
kernel/sched/fair.c - 代码统计: 新增辅助函数
prefer_sync_pair_cpu()(23 行)+select_task_rq_fair()内 5 行 early-return - Message-ID:
20260722-b4-sched-sync-wakeup-v2-1-f1164560b24b@gentwo.org - 完整性: thread 共 8 封邮件,覆盖 AMD/ARM/IBM 三位 reviewer 的 4 轮反馈与作者的 3 轮回应,节奏清晰、信息齐全
补丁目的
解决 pipe-style ping-pong 工作负载被 handoff 成本主导的性能问题。
两个任务 A、B 互相以 WF_SYNC 唤醒(A→B→A→B…)时,调度器仍然走 select_idle_sibling() 把 wakee 拉到"看似空闲"的远端 CPU,反而带来更高的迁移/cache 重建开销。
目标:在 SD_WAKE_AFFINE 域允许的前提下,对窄 reciprocal WF_SYNC 唤醒直接把 wakee 留在 waker CPU。
实测:80 核 Ampere Altra 上 perf bench sched pipe -l 1000000 平均提升约 30%,hackbench / schbench / SPECjBB 无明显回归。
旧流程的问题
SD_WAKE_AFFINE 分支只是给 wake_affine() 算一个 affine target,
最终仍会进入 select_idle_sibling(),后者只要发现一个 idle CPU 就会覆盖 affine 选择。
在 ping-pong 场景下,"找一个空闲 CPU"的代价(IPI、cache miss)远大于"共用 runqueue"。
新流程
识别窄 reciprocal WF_SYNC 唤醒(A 唤醒 B,紧接着 B 又唤醒 A),仅当满足三个条件时直接返回 waker CPU:
- waker CPU 上"恰好只有 waker 这一个 CFS 任务";
- wakee 允许在该 CPU 上运行;
- asym_cpucap 系统上 wakee 也能 fit 该核容量。
不满足就继续走原有 wake_affine() / select_idle_sibling() 路径。
Patch 概览
唯一 hunk 落在 select_task_rq_fair() 的 SD_WAKE_AFFINE 入口,插入一次 early-return:
if (sync &&
READ_ONCE(p->last_wakee) == current &&
prefer_sync_pair_cpu(p, cpu))
return cpu;
同时新增 prefer_sync_pair_cpu() 辅助函数。
关键实现
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 (!cpumask_test_cpu(cpu, p->cpus_ptr))
return false;
if (sched_asym_cpucap_active()) {
sync_entity_load_avg(&p->se);
if (!task_fits_cpu(p, cpu))
return false;
}
return true;
}
逐条解读:
nr_running - cfs_h_nr_delayed(rq) == 1:剔除 deadline/delayed 等非 fair 任务后,waker CPU 上只剩 waker 一个可运行 CFS 任务,这是"没人抢"的硬性条件。cpumask_test_cpu():常规 affinity 兜底;Prateek 指出挪进SD_WAKE_AFFINE分支后已被want_affine覆盖,可删。sched_asym_cpucap_active():大/小核系统上需要先把 PELT 同步到当前时间,再判断任务"能不能 fit",避免重任务被塞到小核。- 调用点的
READ_ONCE(p->last_wakee) == current:识别 reciprocal 模式;Prateek 认为p此时已离队,last_wakee稳定,READ_ONCE多余。
类比
把 A、B 想象成乒乓球双打搭档:
旧调度器每次击球都换球桌(迁去"看似空闲"的 CPU),但来回跑路比两人共用一张桌子慢得多。
新调度器识别出"我刚给你传球,你马上又传回来"这种 reciprocal 模式后,把两人固定在一张桌子上——只要没人抢,就不换桌。
ASCII 流程图:
Pipe workload:
A on cpu0 --WF_SYNC--> B on cpu1
^ |
|------WF_SYNC-------------| (reciprocal, ping-pong)
select_task_rq_fair(p=Wakee, prev_cpu=waker_cpu)
|
+-- SD_WAKE_AFFINE branch ---------------+
| if (sync && |
| p->last_wakee == current && | <-- reciprocal?
| prefer_sync_pair_cpu(p, cpu)) |
| return cpu; (STAY) |
| else |
| wake_affine() -> SIS |
| (may pick a different idle) |
+---------------------------------------+
prefer_sync_pair_cpu(p, cpu):
rq = cpu_rq(cpu)
ok = (nr_running - delayed) == 1 <-- only waker here
ok &= cpumask allows p
ok &= !asym || task_fits_cpu(p, cpu)
return ok
Highlight:风险与注意点
- SMT 语义不匹配:Vineeth 指出 POWER10/11 是 SMT8,把一对任务都堆在同一条线程上、7 个兄弟线程空转并共享 LLC,反而是浪费。该 patch 在 SMT 场景下未做特别处理。
- 可清理项:
READ_ONCE(p->last_wakee)在 p 离队后稳定,可去掉;affinity 检查被want_affine覆盖,可删除。 - SIS 改造路径分歧:Prateek 建议把 early-return 挪进
select_idle_sibling();Shubhang 担心这样会丢掉 v2 修复的 SD_WAKE_AFFINE/isolcpus 语义,并且 SIS 改动若只覆盖 SMT 路径,Altra 这种非 SMT 收益会消失。 WA_IDLE相似性:helper 的结构风格与WA_IDLEwake-affine idle 路径高度相似,未来有合并的可能。- 验证缺口:thread 缺 SMT 上的
perf bench sched pipe数据,Christian 表示愿意补跑;v3 应在合并前给出 Altra 之外平台的回归数据。
版本变化
- v1 → v2:把 reciprocal handoff 偏好移到既有
SD_WAKE_AFFINE域检查之内,避免绕过 isolcpus 等拓扑约束;去掉 changelog 里对 futex 的提及;重新跑perf bench sched pipe数据。 - thread 内 v3 预告:作者计划采纳 Prateek 的清理(去掉
READ_ONCE和冗余 affinity 检查),并把范围限定在非 SMT 路径;同时等待 Christian/Prateek 的 SMT 跑分,再决定是否在本系列里补 SMT 处理。
一句话总结
对窄 reciprocal WF_SYNC 唤醒,让 wakee 留在 waker CPU,可把 pipe-style 负载提速约 30%;代码清理、SMT 适用性与 SIS 改造路径是 v3 的主要拉锯点。