4. CPU域拓扑结构:NUMA、多核、超线程下的域层级与标志位
CPU域(sched_domain)是内核调度器感知物理拓扑的核心数据结构。在NUMA、多核、超线程共存的现代处理器上,域层级直接决定了负载均衡的粒度、迁移代价和调度决策路径。理解域层级与标志位的对应关系,是调优组调度场景下CPU域迁移参数的前提。
4.1 域层级与物理拓扑的映射关系
Linux内核将CPU拓扑抽象为树形层级,从底向上依次为:SMT(同步多线程)→ MC(多核)→ DIE(芯片)→ NUMA(非一致性内存访问)。每个层级对应一个调度域,域内包含一组CPU,域间通过父指针串联。
| 拓扑层级 | 域类型 | 包含关系 | 典型跨度 | 迁移代价 |
|---|---|---|---|---|
| 超线程 | SD_SMT | 同一物理核内的逻辑CPU | 2-4个CPU | 极低(共享L1/L2缓存) |
| 多核 | SD_MC | 同一芯片内的物理核 | 4-16个CPU | 低(共享L3缓存) |
| 芯片 | SD_DIE | 同一物理封装内的所有核 | 8-32个CPU | 中(跨L3但同内存控制器) |
| NUMA节点 | SD_NUMA | 跨内存节点的CPU集合 | 16-256个CPU | 高(远程内存访问延迟) |
4.2 域标志位与调度行为控制
每个调度域通过一组标志位(sched_domain_flags)控制负载均衡的行为。标志位在域初始化时由内核根据拓扑特征自动设置,但部分标志位可通过内核参数或sysfs接口调整。
| 标志位 | 含义 | 影响范围 | 组调度相关 |
|---|---|---|---|
| SD_BALANCE_NEWIDLE | CPU空闲时从其他域拉取任务 | 所有域层级 | 影响空闲CPU的组任务迁移 |
| SD_BALANCE_EXEC | 任务exec()时重新选择CPU | MC/DIE/NUMA | 组任务fork/exec时的初始放置 |
| SD_BALANCE_FORK | 任务fork()时选择目标CPU | MC/DIE/NUMA | 组内子任务的分布策略 |
| SD_WAKE_AFFINE | 唤醒时优先选择上次运行的CPU | SMT/MC | 组任务唤醒局部性 |
| SD_SHARE_PKG_RESOURCES | 域内CPU共享封装级资源 | SMT/MC | 缓存亲和性判断 |
| SD_ASYM_PACKING | 非对称封装(如大小核架构) | MC/DIE | 组任务优先放置到大核 |
| SD_SERIALIZE | 域间负载均衡串行化 | NUMA | 防止多域并发迁移冲突 |
SD_WAKE_AFFINE 如果被错误关闭,会导致组内任务频繁跨核唤醒,破坏缓存亲和性,造成吞吐量下降10%-30%。建议在SMT和MC层级始终保留该标志位。
4.3 域层级结构的内联SVG示意图
下图展示了一个典型的4-NUMA节点、每节点16核、每核2超线程的CPU域层级结构。每个矩形代表一个调度域,箭头表示父子关系,颜色区分拓扑层级。
4.4 域层级与组调度迁移路径
在组调度(CFS组调度或RT组调度)场景下,任务迁移遵循域层级的自底向上遍历路径:
- 触发条件:当前CPU负载超过阈值,或CPU空闲(newidle_balance)。
- 遍历顺序:从当前CPU所在的最低级域(SMT)开始,逐级向上检查父域。
- 迁移决策:在每个域层级,检查域内CPU的负载差异,若超过
sched_domain->imbalance_pct则触发迁移。 - 迁移范围:默认只迁移到同域内的其他CPU;跨域迁移需要父域标志位允许。
4.5 查看当前系统的域拓扑
通过 /proc/schedstat 和 /sys/kernel/debug/sched/domains 可以查看运行时的域层级和标志位配置:
# 查看所有CPU的域层级
cat /proc/schedstat | grep -A 20 "domain"
# 查看特定CPU的域标志位(以CPU 0为例)
cat /sys/kernel/debug/sched/domains/cpu0/domain*/flags
# 输出示例(4个域层级)
# domain0 (SMT): flags=0x00001027
# SD_BALANCE_NEWIDLE | SD_BALANCE_EXEC | SD_BALANCE_FORK |
# SD_WAKE_AFFINE | SD_SHARE_PKG_RESOURCES
# domain1 (MC): flags=0x0000102f
# SD_BALANCE_NEWIDLE | SD_BALANCE_EXEC | SD_BALANCE_FORK |
# SD_WAKE_AFFINE | SD_SHARE_PKG_RESOURCES | SD_LOAD_BALANCE
# domain2 (DIE): flags=0x0000102b
# SD_BALANCE_NEWIDLE | SD_BALANCE_EXEC | SD_BALANCE_FORK |
# SD_LOAD_BALANCE
# domain3 (NUMA): flags=0x00001029
# SD_BALANCE_NEWIDLE | SD_BALANCE_EXEC | SD_BALANCE_FORK |
# SD_SERIALIZE
SD_BALANCE_NEWIDLE 和 SD_WAKE_AFFINE 是否在SMT/MC域中同时存在。如果 SD_WAKE_AFFINE 缺失,组内任务可能被分散到不同核,导致缓存命中率下降。
4.6 域层级与迁移代价的量化关系
不同域层级的迁移代价差异显著,直接影响组调度场景下的吞吐量和延迟。下表给出了典型服务器(Intel Xeon 4-NUMA)的实测参考值:
| 迁移类型 | 域层级 | 平均延迟(μs) | 缓存影响 | 组调度建议 |
|---|---|---|---|---|
| 同SMT内迁移 | SMT | 0.5 - 1.0 | L1/L2命中率不变 | 允许,无副作用 |
| 同MC内跨核迁移 | MC | 2.0 - 5.0 | L1/L2失效,L3命中 | 允许,但需关注组内局部性 |
| 同DIE内跨MC迁移 | DIE | 8.0 - 15.0 | L3部分失效 | 谨慎,建议增大 imbalance_pct |
| 跨NUMA节点迁移 | NUMA | 30.0 - 80.0 | 全部缓存失效 + 远程内存访问 | 强烈建议避免,使用 cpuset 绑定 |
4.7 域标志位与组调度参数的交互
域标志位与组调度参数(如 sched_migrate_cost_ns、sched_nr_migrate)之间存在协同关系。理解这种交互是调优的关键:
- SD_BALANCE_NEWIDLE + sched_migrate_cost_ns:当空闲CPU触发负载均衡时,
sched_migrate_cost_ns决定了是否值得从远程域拉取组任务。如果迁移代价高于阈值,空闲CPU将保持空闲。 - SD_WAKE_AFFINE + sched_min_granularity_ns:唤醒时优先选择上次运行的CPU,但如果组任务的调度粒度(
sched_min_granularity_ns)过小,频繁唤醒可能抵消亲和性收益。 - SD_SERIALIZE + sched_nr_migrate:在NUMA域中,串行化标志位防止多个CPU同时进行跨域迁移。如果组任务数量大,
sched_nr_migrate限制每次迁移的任务数,避免单次迁移开销过高。
SD_BALANCE_NEWIDLE 和较低的 sched_migrate_cost_ns,空闲CPU可能频繁从远程NUMA域拉取组任务,导致组内任务分散,破坏缓存亲和性。建议在NUMA域中增大 sched_migrate_cost_ns 或通过 cpuset 限制组任务的CPU范围。
4.8 域层级与cpuset的协同
cpuset(控制组cgroup的cpuset子系统)允许用户显式指定组任务可运行的CPU集合。当cpuset与域层级结合时,调度器会在cpuset限定的CPU集合内构建子域拓扑:
# 创建一个cpuset,将组任务限制在NUMA节点0的CPU 0-15
mkdir /sys/fs/cgroup/cpuset/mygroup
echo 0-15 > /sys/fs/cgroup/cpuset/mygroup/cpuset.cpus
echo 0 > /sys/fs/cgroup/cpuset/mygroup/cpuset.mems
# 此时调度器为mygroup构建的域层级只包含CPU 0-15
# 域层级变为:SMT(0-1) → MC(0-7) → DIE(0-15)
# NUMA域被裁剪,因为所有CPU都在同一NUMA节点内
SD_WAKE_AFFINE 和 SD_BALANCE_NEWIDLE 在SMT/MC域中启用。对于吞吐量敏感的任务(如批处理、视频编码),可以放宽到DIE域,但避免跨NUMA节点。
4.9 域层级标志位的动态调整
部分域标志位可以通过内核参数在运行时调整,无需重新编译内核:
| 内核参数 | 影响标志位 | 默认值 | 组调度调优建议 |
|---|---|---|---|
sched_balance_newidle |
SD_BALANCE_NEWIDLE | 1(启用) | 组任务密集时建议保持启用,避免CPU空转 |
sched_balance_exec |
SD_BALANCE_EXEC | 1(启用) | 组任务fork/exec时建议启用,实现初始负载分布 |
sched_wake_affine |
SD_WAKE_AFFINE | 1(启用) | 组任务建议保持启用,提升缓存亲和性 |
sched_migrate_cost_ns |
影响跨域迁移决策 | 500000(500μs) | NUMA域建议增大到2000000(2ms) |
sysctl 在运行时关闭NUMA域的唤醒亲和性,减少跨节点迁移:sysctl -w kernel.sched_wake_affine=0注意:此操作会全局影响所有域层级,建议在组调度场景下通过cpuset隔离而非全局关闭。
4.10 域层级与组调度性能的关联分析
域层级配置不当对组调度性能的影响可以通过以下指标量化:
- 跨域迁移率:单位时间内组任务跨域迁移的次数。高迁移率通常意味着缓存亲和性差。
- 平均迁移代价:每次迁移的平均延迟,由迁移跨越的域层级决定。
- 组任务吞吐量:组内所有任务的总完成量,受迁移率和迁移代价共同影响。
- 尾延迟:组任务中最长完成时间,跨NUMA迁移可能导致尾延迟显著增加。
通过 perf 工具可以采集域迁移事件:
# 采集跨NUMA域迁移事件
perf stat -e sched:sched_migrate_task -a -- sleep 10
# 结合cgroup过滤,只观察特定组的迁移
perf stat -e sched:sched_migrate_task --cgroup=/mygroup -a -- sleep 10