Linux Cache Aware Scheduling

深度洞察报告

从讨论到实现的全历程分析 | 2025-2026

2026年3月 Linux Kernel x86 / ARM64

目录

01

背景与动机

为什么需要 Cache Aware Scheduling

02

现代CPU缓存架构

x86 和 ARM64 的缓存拓扑

03

问题分析

缓存不感知调度的性能损失

04

社区讨论历程

从问题发现到方案成型

05

方案演进

从v1到v6的完整技术演进

06

核心实现原理

深入代码层面的技术实现

07

测试与验证

性能数据与案例分析

08

未来展望

下一步发展方向

01

背景与动机

为什么需要 Cache Aware Scheduling?

现代数据中心CPU的缓存拓扑演进

多LLC成为主流架构

现代服务器CPU(Intel Xeon、AMD EPYC、ARM64 Neoverse)普遍采用多Chiplet/Tile设计,每个Die拥有独立的Last Level Cache(LLC)

AMD EPYC架构

  • • Rome→Genoa→Turin:CCD从8个增至12-16个
  • • 每CCD 32MB L3,Genoa总计384MB L3
  • • 分布在4个NUMA节点上

Intel Xeon架构

  • • SPR:4 Tile设计,每Tile独立L3
  • • Clearwater Forest:576MB LLC分布在3 Base Tile
  • • 3D Foveros Direct封装技术

ARM64 Neoverse

  • • DSU-120集群设计,支持8-192核心
  • • 每核心集群共享L3缓存
  • • CMN互联形成2D/3D mesh拓扑

关键问题

Linux传统调度器将同一NUMA节点内的所有CPU视为等价,忽略了LLC边界,导致任务在不同LLC间"跳动",产生严重的缓存抖动问题。

缓存拓扑架构

缓存未命中的性能代价

缓存层次延迟对比

L1 Cache 3-4 cycles
L2 Cache 12-16 cycles
L3/LLC Cache 40-60 cycles
Main Memory 200-300 cycles

缓存抖动场景

多线程共享数据

进程多线程共享堆内存,分散到不同LLC产生跨LLC流量

生产者-消费者

数据在不同LLC间传递产生缓存一致性开销

锁竞争

锁的缓存行在不同LLC间"乒乓"传输

数据库连接池

连接处理线程共享查询缓存,分散调度导致失效

实际性能损失

在数据库、Web服务器、科学计算等共享数据密集的工作负载中,缓存抖动可导致20-50%的性能损失。缓存未命中导致处理器流水线停顿,降低ILP利用率。

现有调度器机制的局限性

NUMA Balancing的不足

Linux现有的NUMA Balancing主要关注内存节点亲和性,而非缓存亲和性。当多个LLC共享一个NUMA节点时(如AMD EPYC),NUMA Balancing无法区分不同LLC。

CFS负载均衡的盲区

CFS(完全公平调度器)的负载均衡以CPU负载为唯一指标,不考虑任务的数据共享模式。负载均衡器可能将共享数据的任务分散到不同LLC。

Wake-up路径的短视

任务唤醒时的CPU选择(select_idle_sibling)只考虑空闲状态和简单拓扑,不追踪任务的历史缓存访问模式。

SMT/核心/LLC层次混淆

调度器虽然能识别SMT、MC(内存控制器)、NUMA层次,但缺乏对LLC边界的显式处理

静态调度特征

现有机制无法动态适应工作负载的缓存访问特征,缺乏运行时反馈机制。

核心洞察

调度器需要理解"共享数据的任务应该尽可能在同一个LLC上运行"这一核心原则,而不是仅仅追求CPU负载的数值均衡。

02

现代CPU缓存架构详解

深入理解 x86 和 ARM64 的缓存拓扑

AMD EPYC 缓存架构演进

架构 CCD数 核心/CCX L3/CCD 总L3 NUMA 内存
Rome (Zen 2) 8 4×2 CCX 32MB 256MB 4节点 8×DDR4
Milan (Zen 3) 8 8×1 CCX 32MB 256MB 4节点 8×DDR4
Milan-X 8 8×1 CCX 96MB 768MB 4节点 8×DDR4
Genoa (Zen 4) 12 8×1 CCX 32MB 384MB 4节点 12×DDR5
Genoa-X 12 8×1 CCX 96MB 1152MB 4节点 12×DDR5
Turin (Zen 5) 16 8×1 CCX 32MB 384MB 4-8节点 12×DDR5

关键观察:从Rome到Turin,CCD数量增加,每个NUMA节点的LLC数量也在增加。Genoa每NUMA节点有3个LLC,调度复杂度显著提升,这正是Cache Aware Scheduling的价值所在。

Intel Xeon 缓存架构演进

Sapphire Rapids (2022)

  • • 4 Tile设计,每Tile独立L3
  • • 支持HBM2e内存
  • • EMIB 2.5D封装技术
  • • 最多60核心

Granite Rapids (2024)

  • • Intel 3工艺
  • • 最多128核心
  • • 12通道DDR5-6400
  • • 支持MRDIMM

Clearwater Forest (2026)

  • • Intel 18A工艺
  • • 最多288 Darkmont E-core
  • • 3D Foveros Direct封装
  • • 576MB LLC

Clearwater Forest 架构详情

Compute Tile (Intel 18A) 12个

每Tile 24核心,6模块×4核心,每模块4MB L2

Base Tile (Intel 3) 3个

每Tile 192MB LLC,4 DDR5控制器

I/O Tile (Intel 7) 2个

PCIe Gen5, CXL 2.0, UPI 2.0

总LLC容量 576MB

分布在3个Base Tile上,每Tile被4个Compute Tile共享

Intel的LLC设计更复杂,Clearwater Forest形成层次化缓存结构,需要调度器理解这种拓扑关系。

ARM64 Neoverse 缓存架构

Neoverse N2

架构:Armv9 (2021)

DSU:DSU-110

核心:8-64核心

L2:每核心1MB

L3:最大64MB共享

扩展:SVE2支持

Neoverse N3

架构:Armv9.2 (2025)

DSU:DSU-120

核心:8-192核心

L2:每核心2MB

提升:Perf/Watt +20%

ML:3x性能提升

Neoverse V3

架构:Armv9.2 (2025)

定位:高性能计算

优化:云计算/AI

向量:更大向量单元

缓存:增强一致性

CMN (Coherent Mesh Network) 互联

ARM64服务器使用CMN互联,形成2D/3D mesh拓扑,缓存一致性通过分布式目录实现

Linux支持DIE、MC、CLUSTER等多个层次,需要处理更复杂的缓存拓扑

03

问题分析

缓存不感知调度带来的性能损失

缓存抖动场景分析

场景1:多线程共享数据

一个进程的多个线程共享堆内存,如果被调度到不同LLC,每次数据共享都产生跨LLC流量,严重降低性能。

场景2:生产者-消费者

一个线程生产数据,另一个线程消费,如果两者在不同LLC,每次数据传递都产生缓存一致性开销

场景3:锁竞争

多个线程竞争同一锁,锁的缓存行在不同LLC间"乒乓"传输,严重降低性能。

场景4:数据库连接池

数据库服务器的连接处理线程共享查询缓存和连接状态,分散调度导致缓存失效

性能损失量化

根据Intel和AMD的测试,这些场景下性能损失可达20-50%,极端情况下更高。

Linux调度器负载均衡机制回顾

调度域(Sched Domain)层次

L0 SMT(超线程)
L1 MC(内存控制器)
L2 DIE/LLC
L3 NUMA

负载均衡触发条件

  • • tick时检查不平衡
  • • 任务唤醒时
  • • CPU空闲时
  • • fork/exec时

负载均衡算法

从顶层域(NUMA)向下遍历,找到最忙的调度组,选择任务迁移到轻载CPU。

现有缺陷

负载均衡只考虑"负载"(运行队列长度和任务权重),不考虑"缓存亲和性"。一个"高负载"的LLC可能只是因为聚集了共享数据的任务,迁移它们反而降低性能。

迁移成本估算

当前调度器对不同层次的迁移成本有粗略估计(SMT低、NUMA高),但缺乏对LLC边界的显式处理

04

社区讨论历程

从问题发现到方案成型的完整时间线

讨论起源:Peter Zijlstra 的原型

2025年3月

Peter Zijlstra(Linux调度器核心维护者)在LKML发布首个Cache Aware Scheduling原型补丁,开启了这一重要特性的讨论。

核心思想

追踪每个进程的"最热LLC"(hottest LLC),即该进程线程运行时间最长的LLC,优先将任务调度到该LLC。

实现机制
  • • 在task_struct中添加preferred_llc字段
  • • 在mm_struct中维护每个LLC的运行时间统计
  • • 任务唤醒时优先选择preferred LLC中的CPU
社区反馈

初步反馈积极,但指出需要处理:

  • • NUMA Balancing的冲突
  • • 避免过度聚合
  • • LLC大小差异
RFC v1发布

2025年3月25日
Peter发布正式RFC补丁集,包含基础框架和初步实现

Peter Zijlstra

Linux调度器核心维护者,CFS主要开发者之一,Red Hat资深工程师

Intel团队的接管与扩展

核心开发者

Tim Chen

Intel,主要开发者

Chen Yu

Intel,主要开发者

开发目标

将原型发展为可合入主线的生产级代码,解决社区反馈的问题。

关键改进方向

1

与NUMA Balancing的协调

2

避免过度聚合导致的负载不均

3

支持动态LLC拓扑(CPU热插拔)

4

添加调试和调优接口

测试平台

Intel Sapphire Rapids(2 socket,30核心/socket,2 LLC)

AMD Genoa(4 NUMA节点,32 CPU/节点,2 CCX/节点)

AMD工程师K Prateek Nayak、Gautham R. Shenoy参与测试和优化,确保在AMD平台的效果

Linux Plumbers Conference 2025 讨论

会议信息

2025年11月,东京

Linux Plumbers Conference(LPC)

演讲者

Tim Chen, Chen Yu

Intel工程师

演讲主题

"Cache Aware Scheduling - Improving Performance on Modern CPUs"

主要讨论点

1 用户接口设计

是否需要通过cgroups或prctl暴露调度策略控制

2 任务分组扩展

除了进程级别,是否支持NUMA组、waker/wakee关系、用户定义组

3 性能验证

社区呼吁更多真实工作负载的测试数据

4 合入时间线

目标Linux 6.19或6.20

根据会议反馈,Intel团队发布v2补丁,重点改进NUMA协调和动态调整

关键社区反馈与回应

反馈1:过度聚合问题

在LLC较小的系统(如IBM Power10,SMT4共享LLC)上,过度聚合导致性能回退

回应:v4引入RSS监控,当进程内存占用超过LLC容量时禁用缓存感知调度

反馈2:与NUMA Balancing冲突

当NUMA Balancing决定将任务迁移到远程节点时,缓存感知调度不应阻止

回应:v2引入优先级机制,NUMA Balancing决策优先于缓存亲和性

反馈3:调试困难

缺乏运行时观察和调优手段

回应:添加多个debugfs接口(/sys/kernel/debug/sched/sched_cache_*)

反馈4:静态键优化

单LLC系统不应承担缓存感知调度的开销

回应:引入sched_cache_present静态键,仅在多LLC时启用

社区反馈驱动的迭代开发模式确保了方案的实用性和通用性

05

方案演进

从v1到v6的完整技术演进

v1 原型(2025年3月)

Peter Zijlstra

核心机制

preferred_llc 字段添加到task_struct

pcpu_sched 数组维护per-LLC运行时间统计

任务唤醒时选择preferred LLC中的空闲CPU

统计更新

account_mm_sched()中,每次任务获得CPU时间时更新对应LLC的统计。

LLC切换策略

task_tick_cache()中定期检查(每10ms一个epoch):

  • • 找出运行时间最长的LLC(hottest_llc)
  • • 如果hottest_llc与当前preferred_llc不同
  • • 且运行时间差超过阈值,则切换

局限性

仅在唤醒路径实现,负载均衡器不知情,可能导致负载不均

v2 负载均衡感知(2025年6月)

Tim Chen, Chen Yu

核心改进:将缓存感知扩展到负载均衡路径

1. NUMA协调

当NUMA Balancing和缓存感知决策冲突时,优先NUMA Balancing

2. 动态LLC ID

使用连续的LLC-ID空间,可直接作为数组索引

3. 动态结构大小

根据系统LLC数量动态调整per-LLC统计结构大小

4. SCHED_CACHE特性

添加三个调度特性开关:SCHED_CACHE、SCHED_CACHE_LB、SCHED_CACHE_WAKE

负载均衡策略

识别"偏好LLC"的任务,优先将其迁移到目标LLC

阻止将任务从其偏好LLC迁出

在busiest queue选择任务时,优先选择偏好目标LLC的任务

测试结果

Sapphire Rapids上hackbench提升24%,schbench唤醒延迟降低21-28%

v3 解决过度聚合(2025年7月)

Tim Chen, Chen Yu

问题发现

v2在AMD Milan和IBM Power10上某些工作负载出现性能回退

  • • 过度聚合导致单个LLC过载
  • • 其他LLC空闲
  • • 特别是内存密集型工作负载

解决方案

1. RSS监控

添加/sys/kernel/debug/sched/sched_cache_ignore_rss接口

2. 线程数监控

追踪进程活跃线程数,与LLC核心数比较

3. 扫描范围限制

限制LLC候选扫描范围到相关NUMA节点

RSS控制接口

0 完全禁用
1(默认) RSS > LLC时禁用
100 不考虑RSS
N RSS > (N-1)*256*LLC

测试结果

回退问题得到缓解,hackbench仍保持20-30%提升

v4 静态键优化(2025年8月)

Chen Yu

问题

单LLC系统(如大多数桌面CPU、小型服务器)不应承担缓存感知调度的开销

解决方案

引入sched_cache_present静态键

DEFINE_STATIC_KEY_FALSE(
  sched_cache_present
);

启用条件

系统存在至少一个拥有多个LLC的NUMA节点

且非非对称CPU拓扑

检查时机:build_sched_domains()

代码实现

if (!sched_feat(SCHED_CACHE) ||
    !static_branch_likely(
      &sched_cache_present))
  return;

条件不满足时,编译器优化掉相关代码,零运行时开销

影响:单LLC系统完全不受代码体积和性能影响

v5 准备合入(2025年10月)

Tim Chen, Chen Yu

里程碑

移除RFC标记,正式请求合入主线

代码成熟度

  • • 19个补丁
  • • 覆盖调度器核心、调试接口、文档
  • • 通过Intel和AMD多平台测试
  • • 社区审查反馈基本解决

核心架构

Patch 1

Peter的原始补丁(基础框架)

Patch 2-5

修复和调优

Patch 6-12

负载均衡基础设施

Patch 13-18

缓存感知负载均衡逻辑

Patch 19-20

SCHED_CACHE_LB和SCHED_CACHE_WAKE特性

测试亮点

44%

AMD Genoa ChaCha20提升

24-31%

Intel SPR hackbench提升

30+

Phoronix测试用例提升

v6 最新优化(2026年2月)

v3 Post-RFC

1. 跳过重复失败

当缓存感知负载均衡连续失败(达到cache_nice_tries)后,跳过该LLC

2. 移除排序开销

不再对busiest runqueue排序,改为在迁移时跳过不偏好目标的任务

3. 简化LLC ID计算

直接使用sched_domain_topology_level数据计算LLC ID

4. per-CPU统计

将每个LLC的任务偏好计数移到per-CPU的最低层调度域

代码重构

sched_cache_stats从mm_struct分离

统一迁移策略到_get_migrate_hint()函数

测试平台

Linux 6.19-rc3
Intel Sapphire Rapids
AMD Genoa

测试结果

hackbench 1-group:+29-38%
schbench 99th延迟:-21-28%

06

核心实现原理

深入代码层面的技术实现

数据结构设计

task_struct扩展

struct task_struct {
  int preferred_llc;
  struct callback_head
    cache_work;
  ...
};

preferred_llc: -1表示无偏好

mm_struct扩展

struct mm_struct {
  struct
    sched_cache_stats
    *pcpu_sched;
  ...
};

struct sched_cache_stats {
  atomic64_t
    time_on_llc[0];
};

调度域扩展

struct sched_domain {
  int llc_id;
  ...
};
extern int max_llcs;

DEFINE_STATIC_KEY_FALSE(
  sched_cache_present
);

关键设计:使用柔性数组存储per-LLC统计,根据实际LLC数量动态分配内存

运行时统计机制

统计更新时机

account_mm_sched()中,每次任务获得CPU时间时更新

更新逻辑

void account_mm_sched(
  struct rq *rq,
  struct task_struct *p,
  s64 delta_exec)
{
  if (!sched_feat(SCHED_CACHE))
    return;
  int llc = llc_id(cpu_of(rq));
  atomic64_add(delta_exec,
    &mm->pcpu_sched->time_on_llc[llc]);
}

LLC切换决策

task_tick_cache()中定期检查(每10ms一个epoch)

1

找出运行时间最长的LLC(hottest_llc)

2

如果hottest_llc与当前preferred_llc不同

3

且运行时间差超过阈值,则切换

延迟更新

使用task_work_add()延迟执行LLC切换,避免在调度关键路径上执行复杂逻辑

负载均衡算法

缓存感知负载均衡流程

1

在update_sg_lb_stats()中统计每个调度组的"偏好LLC任务数"

2

在update_sg_if_llc()中标记需要LLC均衡的调度组

3

在llc_balance()中决定是否进行缓存感知均衡

任务选择策略

detach_tasks()中,优先选择偏好目标LLC的任务

迁移决策函数

enum migrate_hint {
  mig_allow,
  mig_forbid,
  mig_cache,
};

static int get_migrate_hint(
  int src_cpu,
  int dst_cpu,
  struct task_struct *p)
{
  int src_llc = llc_id(src_cpu);
  int dst_llc = llc_id(dst_cpu);

  if (p->preferred_llc == src_llc)
    return mig_forbid;

  if (p->preferred_llc == dst_llc)
    return mig_cache;

  return mig_allow;
}

唤醒路径优化

唤醒CPU选择流程

select_task_rq_fair()
  → select_idle_sibling()
    → select_cache_cpu()

与WAKE_WIDE的协调

当任务被多个CPU频繁唤醒时,禁用缓存感知唤醒,避免过度聚合

select_cache_cpu()实现

static int select_cache_cpu(
  struct task_struct *p,
  int prev_cpu)
{
  struct mm_struct *mm = p->mm;
  int cpu;

  if (!sched_feat(SCHED_CACHE))
    return prev_cpu;

  int llc = p->preferred_llc;
  if (llc < 0)
    return prev_cpu;

  for_each_cpu(cpu,
      cpumask_of_llc(llc)) {
    if (idle_cpu(cpu))
      return cpu;
  }

  return prev_cpu;
}

调试与调优接口

debugfs接口

sched_cache_ignore_rss 默认: 1
sched_cache_aggr_cap 默认: 50
sched_cache_aggr_imb 默认: 0
sched_cache_nice_tries 默认: 1

内核启动参数

nosched_cache

完全禁用缓存感知调度

sched_features控制

SCHED_CACHE

总开关,启用缓存感知调度

SCHED_CACHE_LB

启用缓存感知负载均衡

SCHED_CACHE_WAKE

启用缓存感知唤醒

观察工具

/proc/sched_debug

查看每个runqueue的preferred_llc任务数

schedstat

追踪任务迁移次数和原因

07

测试与验证

性能数据与案例分析

测试平台配置

Intel Sapphire Rapids

Socket 2
核心/Socket 30
LLC 2
CPU/LLC 60
内存 DDR5-4800

DRAM Interleaving启用,形成1个NUMA节点+2个LLC

AMD Genoa

NUMA节点 4
CPU/节点 32
CCX/节点 2
CPU/CCX 16
总L3 384MB
内存 DDR5-4800, 12通道

测试内核版本

v1-v2: Linux 6.15 v3-v4: Linux 6.18-rc7 v5-v6: Linux 6.19-rc3

Hackbench 测试结果

测试说明

hackbench是Linux调度器基准测试,创建多对发送者/接收者线程通过pipe/socket通信

关键发现

活跃线程数少于LLC容量时,提升最明显;多组场景提升有限

Sapphire Rapids结果(v6)

配置 基线 缓存感知 提升
threads-pipe-2, 1-group 基准 +29.06% 显著
threads-pipe-4, 1-group 基准 +28.63% 显著
threads-pipe-8, 1-group 基准 +24.68% 显著
threads-pipe-16, 1-group 基准 +28.46% 显著
threads-pipe-*, 2-group 基准 -1% ~ +8% 不明显
threads-pipe-*, 4/8-group 基准 -1% ~ +8% 不明显

Schbench 与 ChaCha20 测试结果

Schbench(调度延迟测试)

测试99th百分位唤醒延迟

thread=2 13.33ms → 13.00ms +2.48%
thread=4 12.33ms → 9.67ms +21.57%
thread=8 10.00ms → 10.67ms -6.70%
thread=16 10.00ms → 9.33ms +6.70%

总体趋势:中等负载下唤醒延迟改善明显

ChaCha20-xiangshan

RISC-V模拟器,大量共享内存访问

AMD Genoa

基线

51432ms

缓存感知

28664ms

-45%

Intel Sapphire Rapids

10%提升

该测试最能体现缓存感知调度优势

其他测试与回归分析

Stream

内存带宽测试

512MB/2GB数据集

~0% 无显著差异

Stream是内存密集型,缓存影响有限

Netperf

网络性能测试

1-256对连接

~0% 无显著差异

网络I/O主导,CPU调度影响小

Stress-ng

压力测试

上下文切换测试

~0% 无显著差异

压力测试场景缓存影响有限

已知回归场景

IBM Power10:小LLC(SMT4共享)+ 大内存占用工作负载

缓解方案:可通过调整sched_cache_ignore_rss缓解

08

未来展望

下一步发展方向

待解决问题与后续工作

更灵活的任务分组

当前

仅支持进程级别分组

未来

支持NUMA组、waker/wakee关系、cgroups、用户自定义组

可配置调度策略

当前

全局统一策略

未来

per-process、per-task-group策略,通过prctl()或cgroups暴露

更多架构支持

当前

主要针对x86(Intel/AMD)

未来

优化ARM64(Neoverse)、RISC-V等架构支持

运行时自适应

当前

基于RSS和线程数的启发式

未来

基于实际缓存未命中率反馈的自适应算法

合入时间线预测

当前状态

2026年2月:v6补丁已发布,社区审查中

主要障碍

1

需要更多真实工作负载的长期测试数据

2

某些边缘场景的性能回退需要进一步解决

3

用户接口设计需要社区共识

时间线预测

乐观预测

Linux 6.20

2026年中

保守预测

Linux 6.21 或 6.22

2026年底

关键里程碑

获得Peter Zijlstra、Ingo Molnar等核心维护者的Reviewed-by

通过0-day测试机器人的回归测试

至少两个主要发行版的预览集成

总结

核心洞察

Cache Aware Scheduling是Linux调度器在现代多LLC CPU架构下的必要演进,通过将共享数据的任务聚合到同一LLC,可显著提升性能。

关键数据

44%

AMD Genoa提升

31%

Intel SPR提升

技术贡献

建立了完整的缓存感知调度框架

解决了与NUMA Balancing的协调问题

引入了动态自适应机制避免过度聚合

社区价值

Intel主导开发,但AMD平台获益更大,体现了开源协作的力量

感谢 Peter Zijlstra、Tim Chen、Chen Yu、K Prateek Nayak 及所有社区贡献者