深入理解 Linux NUMA:拓扑、分配策略与调优实践
1. 概览
NUMA(Non-Uniform Memory Access,非一致性内存访问)把系统内存按"节点(node)"分组,每个节点有本地内存与若干 CPU。访问本地内存延迟低、带宽高;跨节点访问(remote)延迟更高、带宽更低。Linux 的总体目标是:
- 优先在本地节点分配内存,必要时按策略回退到其他节点。
- 在运行时根据访问热度,自动迁移页,使计算与数据尽量同节点。
这通过"节点优先 + 策略驱动回退 + 自动均衡纠偏"的组合实现。
2. 拓扑与术语
Node(NUMA 节点)
:CPU 组 + 其本地内存;内核以pg_data_t
表示。Zone(内存域)
:节点内按用途划分的区域(如DMA/Normal/HighMem
),由伙伴系统管理。结构为struct zone
。Zonelist(域列表)
:为一次分配构建的按优先级排序的候选 Zone 列表,体现"本地优先、再回退"。Per-CPU pageset(每 CPU 页集)
:每 CPU × 每 Zone 的order=0
快速路径缓存,减少锁争用并提高局部性。
相关定义位置:
include/linux/mmzone.h
:pg_data_t
、struct zone
、struct per_cpu_pages
、struct per_cpu_pageset
。mm/page_alloc.c
:页分配/释放及 per-CPU 缓存逻辑。
3. 核心内核结构
-
pg_data_t
(节点)- 维护该节点上的内存域、统计信息、回退关系等。
-
struct zone
(域)- 由伙伴分配器(Buddy)管理不同阶(order)的空闲页块;包含水位与统计。
-
struct per_cpu_pages
/struct per_cpu_pageset
- 字段要点:
count
(当前缓存页数)、high
(高水位)、batch
(一次回填/回收批量)、lists[MIGRATE_PCPTYPES]
(按迁移类型分组的单页链表)。 - 每 CPU 每 Zone 维护一份,分配与释放的
order=0
快速路径依赖它。
- 字段要点:
4. 分配路径(从本地到回退)
- 高层入口:
alloc_pages()
→__alloc_pages_nodemask()
→get_page_from_freelist()
。 - 快速路径(
order=0
):mm/page_alloc.c: buffered_rmqueue()
优先从当前 CPU 的目标 Zone 的 per-CPU 缓存取页。- 若缓存为空,
rmqueue_bulk()
在一次锁持有期内从伙伴系统批量取页,回填到 per-CPU 缓存后再返回。
- 回退机制:
- 若本地节点无法满足(高阶或水位不足),沿
zonelist
逐级回退到其他 Zone / 节点。 - NUMA 策略会影响首选节点与回退顺序(详见下一节)。
- 若本地节点无法满足(高阶或水位不足),沿
该设计减少热点锁竞争,强化"就近分配",同时在资源紧张时平滑回退。
5. 释放路径(快速接受与溢出回收)
- 释放单页(
order=0
)快速路径:mm/page_alloc.c: free_hot_cold_page()
。- 将页按迁移类型挂入
pcp->lists
;pcp->count
超过pcp->high
时触发溢出回收。
- 将页按迁移类型挂入
- 溢出回收:
free_pcppages_bulk()
- 按批量与迁移类型,批量把缓存页归还伙伴系统(
__free_one_page
)。
- 按批量与迁移类型,批量把缓存页归还伙伴系统(
- 主动回收:
drain_zone_pages()
/drain_pages()
/drain_local_pages()
/drain_all_pages()
- 在负载切换、内存压力或 NUMA 策略变更时,可主动清空 per-CPU 缓存,减少跨 CPU/节点的"旧页"滞留。
6. NUMA 内存策略(应用可控)
应用可通过系统调用或工具设置策略,影响节点选择与回退:
MPOL_PREFERRED
:优先节点;不足时回退到其他节点。MPOL_BIND
:只在指定节点集合内分配;不足则失败(或等待)。MPOL_INTERLEAVE
:跨节点轮转分配,提高带宽、降低单节点热点。
接口与实现:
- 系统调用:
set_mempolicy()
、mbind()
、get_mempolicy()
(mm/mempolicy.c
)。 - 工具示例:
numactl --hardware
(查看拓扑)numactl --preferred=0 ./app
(优先节点 0)numactl --interleave=all ./app
(跨节点交织分配)numactl --membind=1,2 ./app
(仅在节点 1/2 分配)
7. 自动 NUMA 均衡(AutoNUMA)
当开启 kernel.numa_balancing=1
且内核配置 CONFIG_NUMA_BALANCING
时:
- 机制概览:
- 内核周期性采样页访问(结合缺页/访问模式),识别"错置页"(远端频繁访问的页)。
- 通过
migrate_pages()
(mm/migrate.c
)把页迁往访问最频繁的节点,使线程与数据同节点。
- 关键路径:
- 参考
mm/numa.c
与do_numa_page()
相关逻辑(在 4.4 系列中分布于内存子系统)。
- 参考
- 注意事项:
- 适合通用场景;对强绑定(
MPOL_BIND
)或极端局部性场景可能需要关闭或调参。
- 适合通用场景;对强绑定(
8. 大页与透明大页(THP)与 NUMA
- THP(Transparent Huge Page)在 NUMA 上仍遵循"本地优先"。
- 高阶(order>0)分配失败时可能更早触发回退;可结合
numactl
与 THP 参数调优。- 检查:
/sys/kernel/mm/transparent_hugepage/enabled
- 检查:
9. 观测与诊断
- 拓扑与负载:
lscpu --extended=CPU,NODE
(CPU-节点映射)numactl --hardware
(节点与内存大小)numastat
(用户态工具,统计各节点分配/访问;参见Documentation/numastat.txt
)
- 进程视角:
/proc/<pid>/numa_maps
(各 VMA 的节点分布与策略)cat /proc/self/numa_maps
(自查)
- 性能热点:
perf mem
、perf stat -e numa_*
(采样远端访问)hwloc-ls
/lstopo
(可视化硬件拓扑)
10. 应用侧调优实践
- 绑定计算与内存:用
taskset
与numactl
将线程/进程与节点绑定,避免跨节点频繁访问。 - 在工作线程启动处完成大对象分配:确保分配发生在工作线程所在节点。
- 分片与池化:按节点分片数据结构(如每节点一个缓存/队列/池),减少跨节点交互。
- 批处理与就地处理:尽量在同节点完成流水线的多个步骤,避免来回迁移。
- 避免跨节点锁热点:采用更细粒度的分片锁或无锁结构。
- 监控并校正:结合
numa_maps
与numastat
定期检查数据错置并调参。
11. 常见陷阱与对策
- 远端访问比率过高:
- 对策:绑定策略、数据分片、提升本地命中率、检查自动均衡是否有效。
- 高阶分配失败频繁:
- 对策:预留/预热、减少高阶需求、启用/调优 THP、放宽策略回退。
- 策略不当(误用
MPOL_BIND
):- 对策:评估负载特征,切换到
PREFERRED
或INTERLEAVE
。
- 对策:评估负载特征,切换到
- 页迁移开销与抖动:
- 对策:在峰值负载场景下谨慎开启自动均衡,必要时降低采样或关闭。
12. 内核配置与参数(4.4.94)
CONFIG_NUMA
:启用 NUMA 支持。CONFIG_NUMA_BALANCING
:启用自动均衡;CONFIG_NUMA_BALANCING_DEFAULT_ENABLED
影响默认开关。- 运行时参数:
kernel.numa_balancing
(/proc/sys/kernel/numa_balancing
)
13. 与伙伴分配器与 Per-CPU 页缓存的关系
- 每个
Zone
属于一个Node
,伙伴分配器在 Zone 内管理空闲块;per-CPU pageset
则为单页分配/释放提供近路。 - 结果:单页分配在 NUMA 上更易"本地命中";但当本地资源不足或策略要求时,仍会回退到其他节点。
- 关键函数:
- 分配:
buffered_rmqueue()
、rmqueue_bulk()
(mm/page_alloc.c
) - 释放:
free_hot_cold_page()
、free_pcppages_bulk()
(mm/page_alloc.c
) - 结构定义:
per_cpu_pages
、per_cpu_pageset
(include/linux/mmzone.h
)
- 分配:
14. 快速检查清单(运维与开发)
- 确认拓扑:
numactl --hardware
、lscpu
。 - 绑定/策略:
taskset
、numactl
(preferred/bind/interleave)。 - 观察偏置:
/proc/<pid>/numa_maps
、numastat
、perf numa_*
。 - 评估 THP 与高阶需求:避免不必要的高阶分配。
- 评估自动均衡:按负载特征决定开关与阈值。
15. 参考与代码位置
- 文档:
Documentation/numastat.txt
- 头文件:
include/linux/mmzone.h
- 分配器与 per-CPU 缓存:
mm/page_alloc.c
- 策略:
mm/mempolicy.c
- 迁移:
mm/migrate.c
- 自动均衡(相关逻辑):
mm/numa.c
(与内存子系统其他文件协同)
------ 完 ------