深入理解 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(与内存子系统其他文件协同)
------ 完 ------