多路(multi-socket)服务器上,每个 CPU socket 挂接本地 DRAM;线程访问本地 node 与远端 node 的延迟、带宽并不相同——这就是 NUMA(Non-Uniform Memory Access)。忽略拓扑时,常见症状不是「算力不够」,而是 跨 node 内存流量放大、尾延迟抖动、numa_miss 计数攀升;具体倍数取决于 CPU 代际、互连(Intel UPI / AMD Infinity Fabric)与工作集,本文不写入未在本机实测的纳秒级延迟表。
单机单 node 环境(例如云主机只暴露一个 NUMA node)仍应会读拓扑:远端访问问题不会出现,但 mempolicy、AutoNUMA 与容器 topology-manager 的配置语义相同——误绑 policy 照样会在多 node 机器上踩坑。
一、先看图
flowchart LR
C0["CPU socket 0<br/>Node 0"] --> M0["Local DRAM<br/>Node 0"]
C1["CPU socket 1<br/>Node 1"] --> M1["Local DRAM<br/>Node 1"]
C0 -.interconnect.-> M1
C1 -.interconnect.-> M0
CXL["CXL device"] -.CXL.mem.-> M2["CXL memory<br/>Node 2"]
classDef local fill:#3fb95022,stroke:#3fb950,color:#adbac7;
classDef remote fill:#f0883e22,stroke:#f0883e,color:#adbac7;
classDef cxl fill:#a371f722,stroke:#a371f7,color:#adbac7;
class C0,C1,M0,M1 local
class CXL,M2 cxl
二、学术谱系与 Linux 落点
| 阶段 | 代表 work | 相对前一代 |
|---|---|---|
| 1970s–1980s | 共享总线 SMP | 内存延迟近似均匀 |
| 1990s | Dash / DASH(Lenoski et al., ISCA 1992 等) | 显式 distributed shared memory,远端访问代价入模型 |
| 1990s–2000s | SGI Origin、早期 x86 多路 | 商用 NUMA 机器;OS 需感知 node |
| 2000s+ | Linux 2.6+ mempolicy、ACPI SRAT/SLIT |
内核调度与分配按 node 拓扑 |
| 2010s | AutoNUMA(Rik van Riel, 2012) | 运行时根据 fault 迁移页/任务 |
| 2020s | CXL.mem 热插拔、memory tiering(Linux 6.1+) | 多于两级 的延迟层次 |
工程间隙:论文与教材常用 node distance 矩阵(ACPI SLIT)描述相对代价,但 distance 10/21/31 是归一化权重,不是可直接当纳秒用的线性标尺。Intel / AMD 白皮书中的「本地 vs 远端带宽比」也随代际变化——调优应看 numastat / perf 的 node-miss 计数与工作集,而不是背诵固定 ns 数字。
三、拓扑发现(本机可执行)
下列命令在撰写环境(Linux 6.8.0-90-generic,2 vCPU,单 NUMA node)下实际执行;输出经删减,只保留与 NUMA 相关的行。
lscpu | grep -E 'NUMA|Socket|CPU\(s\)|Model name'
numactl --hardwareCPU(s): 2
Model name: AMD EPYC 9754 128-Core Processor
Socket(s): 1
NUMA node(s): 1
NUMA node0 CPU(s): 0,1
---
available: 1 nodes (0)
node 0 cpus: 0 1
node 0 size: 1935 MB
node 0 free: 89 MB
node distances:
node 0
0: 10
如何读:
NUMA node(s): 1:本机没有跨 socket 远端 DRAM;node distances仅对角为 10,无从谈「远端 21」。- 多路机器上通常
node distances对角为 10、非对角为 21(一跳)或更大(多跳);与 多核负载均衡 中的sched_domainNUMA 层一致。 numactl --hardware的size/free随运行时变化;不要在文档里写死「256GB」类容量,应以命令实时输出为准。
补充工具:lstopo(hwloc)可视化 socket /
core / NUMA 关系;容器场景还需读 cgroup 与 Kubernetes
topology-manager 策略。
四、mempolicy
Linux 给每个进程 / VMA
一套内存分配策略(Documentation/admin-guide/mm/numa_memory_policy.rst):
- default:优先本地 node
- bind:只在指定 node 分配
- interleave:轮询指定 node(适合初始化期大数组、全 CPU 均匀读写的 buffer)
- preferred:优先某 node,不够再退
- preferred_many(5.15+):多个 preferred node
- local:严格本地
# 绑定 node 0
numactl --membind=0 ./app
# 交错分配
numactl --interleave=all ./app代码级
#include <numaif.h>
set_mempolicy(MPOL_BIND, nodemask, maxnode);
// per-VMA
mbind(addr, len, MPOL_BIND, nodemask, maxnode, MPOL_MF_MOVE);五、AutoNUMA / NUMA balancing
5.1 问题
手动 numactl
粒度粗——进程运行中可能被调度器迁到远端 CPU,或 fork
后子进程在远端 node 跑却用本地内存。
5.2 AutoNUMA 方案
内核定期把 PTE 标为不可访问(PROT_NONE)→
触发 NUMA hint fault → 记录访问 node →
若页与任务不同 node → migrate page 或 migrate
task(Rik van Riel, AutoNUMA, 2012)。
cat /proc/sys/kernel/numa_balancing
cat /proc/vmstat | grep numa
# numa_hit numa_miss numa_foreign
# numa_pages_migrated注意:kernel.numa_balancing
并非所有发行版默认 1——撰写环境为
0(单 node 上 AutoNUMA 收益有限)。多 socket
生产机应 以实际
/proc/sys/kernel/numa_balancing
为准,再结合 numa_miss
决定是否关闭。
5.3 代价
NUMA hint fault 有 CPU 开销;对 I/O
密集不明显,对计算密集曾有 约 1–3% CPU
量级的报告(Red Hat / LWN 讨论,随工作集变化大)。大堆 Java
服务若出现周期性 fault 毛刺,可尝试
numa_balancing=0 并改用手动
numactl / -XX:+UseNUMA。
六、page migration
内核可在 node 间迁移物理页:
migrate_pages(pid, old_nodes, new_nodes); // syscall或用户态 migratepages 工具。步骤:目标 node
分配 → 复制 → rmap 更新 PTE →
释放源页;大批量迁移可达毫秒级停顿,需与业务 SLA 对齐。
七、NUMA 与调度
多核负载均衡 已讲调度器的 NUMA 感知,此处只列交叉点:
- 调度器尽量把任务放在其内存所在 node(wake-affine、load balance)
sched_domain层级区分 SMT / MC / DIE / NUMA- NUMA balancing 与调度器协同:迁任务更便宜则迁任务,否则迁页
八、CXL 与分级 NUMA
CXL(Compute Express Link)可把远端 DRAM 或持久内存挂成
新的 NUMA node。Linux 6.x+ 支持 CXL memory
hotplug 与 memory
tiering(Documentation/admin-guide/mm/memory-tiers.rst):热页留在快层,冷页
demote 到慢层(CXL node)。
# 若内核启用了 tier 框架(多 node 机器上才有意义)
ls /sys/devices/system/node/CXL 节点的 绝对延迟
随设备与拓扑变化;调优应结合 tier 统计与
numastat,而非固定「~200ns」类口号。
九、生产调优
9.1 数据库
# MySQL / PostgreSQL:buffer pool 被所有 CPU 访问时,常见做法
numactl --interleave=all mysqld
# 或 membind 配合 CPU 绑定interleave 的逻辑是 避免单 node DRAM
带宽成为瓶颈;若工作集明显小于单 node
容量,membind + CPU 绑定 往往更优——需用
numastat 验证 numa_miss。
9.2 JVM
numactl --localalloc java -Xmx32g ...
# 大堆可评估 -XX:+UseNUMA(G1/ZGC 与版本相关)9.3 HPC
mpirun --map-by numa --bind-to core ./simulation9.4 容器
Kubernetes
topology-manager(TopologyPolicy:
best-effort / restricted /
single-numa-node)尽量保证 Pod 的 CPU
与内存在同一 node。
十、诊断
撰写环境执行 numastat(节选):
node0
numa_hit 17958575334
numa_miss 0
numa_foreign 0
local_node 17958420591
other_node 0
单 node 上 numa_miss /
other_node 为 0 是预期;多 node
机器上若 numa_foreign 或 numa_miss
持续偏高,应排查 mempolicy、绑核与 buffer
分配策略。
常用命令:
numastat
numastat -p <pid>
perf stat -e node-loads,node-load-misses,node-stores,node-store-misses ./app
cat /proc/$$/numa_maps十一、常见问题
A:MySQL 性能波动大
buffer pool 全部分配在进程启动时的 node,而查询线程跑在另一
socket → numa_miss
上升。修复:numactl --interleave=all 或 membind
+ 绑核。
B:NUMA balancing 导致 CPU 毛刺
大内存服务 hint fault 频繁 → 可
numa_balancing=0 并手动绑定。
C:CXL 节点意外参与 default 分配
检查 memory tier / demotion 配置与
mpol;确认慢层 node 是否应 bind
为冷数据专用。
十二、开放问题
- AutoNUMA vs
显式绑定的默认策略:内核/community
尚未给出适用于所有 workload
的默认开关——数据库、HPC、容器混部场景结论相反;仍依赖
numastat+ 业务 SLA 做 A/B。 - 多于两级的 memory tiering:CXL + 本地 DDR + 远端 socket 三层并存时,demotion 策略与 应用透明性 仍在快速演进(Linux 6.x 框架 vs 用户态 tiering 库)。
- Disaggregated memory 与调度:当计算 node 几乎无本地 DRAM(分离式 OS 趋势),CFS 的 NUMA 代价模型是否足够——sched_ext 能否接入可编程内存距离,是 OS 研究活跃方向,生产默认路径尚未统一。
十三、小结
- NUMA 拓扑上内存 非均匀;代价用 distance / perf / numastat 度量,不用未实测的 ns 表糊弄。
mempolicy控制分配位置;AutoNUMA 用 hint fault 做页/任务迁移,有 CPU 代价,默认开关因环境而异。- CXL 扩展出多级内存;需要 memory tiering 与 demotion 策略。
- 生产:DB 大 buffer 常评估
interleave;低延迟服务倾向 membind + 绑核;容器用 topology-manager。 - 诊断:
numactl --hardware、numastat、/proc/<pid>/numa_maps、perfnode-*事件。
参考资料
规范与内核文档(A 级)
- Linux
Documentation/admin-guide/mm/numa_memory_policy.rst - Linux
Documentation/admin-guide/mm/memory-tiers.rst - ACPI SLIT / SRAT(平台 NUMA 拓扑描述)
论文与报告(A 级 / B 级)
- Lenoski, D. et al., The Stanford DASH Multiprocessor, ISCA 1992(NUMA 架构原型)
- Lameter, C., NUMA Memory Policy, Linux Symposium / 内核 mempolicy 设计说明
- van Riel, R., AutoNUMA, Red Hat 2012(B 级:维护者报告)
- Williams, D., CXL memory in Linux, LPC 2022(B 级)
源码(A 级)
mm/mempolicy.c、mm/migrate.c、mm/numa_balancing.c
工具与站内
numactl、numastat、migratepages、lscpu、lstopo(hwloc)/proc/<pid>/numa_maps;perf stat -e node-*- 多核负载均衡、OOM Killer
上一篇:OOM Killer 下一篇:HugeTLB 与 THP
同主题继续阅读
把当前热点继续串成多页阅读,而不是停在单篇消费。
【操作系统百科】可拆分 OS
CXL 内存池化如何动摇 OS 对本地 DRAM 的假设?从资源解耦、CXL.mem 语义、LegoOS/PhantomOS/Twilight 学术原型,到 Linux CXL 驱动、dax、HMM 与 memory tiering,说明 disaggregated OS 的工程边界与 NUMA 演进。
【操作系统百科】OS 的下一个十年
综合 Windows/RTOS/Unikernel/Rust/机密计算/CXL 等前沿篇章,从 Rust 内核、机密计算、可拆分内存、io_uring 统一 I/O、eBPF 可编程内核五条趋势出发,按现在/三年/十年框架说明应用开发者应调整的假设与可执行建议。
【操作系统百科】spinlock 家族
内核 spinlock 从关中断到 qspinlock 演化了四代。本文讲原始 spinlock、ticket lock、MCS lock、qspinlock、paravirt qspinlock、spin_lock_irqsave 的代价与 NUMA 友好性。
【操作系统百科】epoll 内部
epoll 用红黑树管理 fd、就绪链表 O(1) 返回事件——打败 select/poll 的关键。本文讲 eventpoll 结构、LT/ET 语义、EPOLLEXCLUSIVE 与惊群、级联 epoll、EPOLLONESHOT、与 io_uring 的比较。