numastat 观察 NUMA 内存均衡
更新时间:2026-09-18。多路服务器上,CPU 访问「本地内存」比访问另一颗 CPU 的「远端内存」快。
numastat用来观察内存落在哪个节点、有多少跨节点访问。绑定手段见 numactl 拓扑绑定,JVM 大堆见 容器/NUMA 内存规划。
一、先看拓扑
bash
numactl --hardware
# node 0 cpus: 0-15 node 0 size: 65536 MB
# node 1 cpus: 16-31 node 1 size: 65536 MB
# node distances: 10=本地 20=远端distance 10 是本地,20/21 是跨节点——远端访问要经过互连带(如跨 socket),延迟更高、带宽更低。
二、整机与进程视角
bash
numastat # 全机各节点统计
# Node 0 Node 1
# Numa_Hit ... ... # 在期望节点分配/访问命中
# Numa_Miss ... # 期望本节点却要从别处取
# Numa_Foreign ... # 被重定向到其它节点
# Numa_Interleave ...
# Other_Node ... # 在“非分配节点”上发生的访问(跨节点迹象)
numastat -p <pid> # 某进程在各节点的内存页分布(MB)
# Node 0 Node 1 Total
# Heap 40000 800 40800 ← 严重倾斜到 Node0
# Private 40050 820
numastat -s Mem # 按内存占用排序进程(部分版本 -s 字段名不同)判读:
- 进程内存几乎全在一个节点,但线程跑在两颗 CPU 上 → 另一颗上的核大量远端访问。
Numa_Foreign/Other_Node持续走高 → 跨节点访问多,是可优化信号。- 理想:进程的内存页和它运行的 CPU 在同一节点。
三、根因:首触分配
Linux 默认策略是「首次触碰(first-touch)分配」:物理页在执行首次写操作的那个 CPU 所在节点分配。常见倾斜:
- 初始化数据的线程只跑在 node0(如单线程预热),结果整个大堆都在 node0,运行期工作线程在两颗 CPU → node1 远程访问。
- 容器/虚拟机未做 NUMA 对齐,内存与 vCPU 跨节点。
四、配合 numactl 验证改进
bash
# 让进程只用 node0 的 CPU 和内存(消除跨节点,前提是单机内存够用)
numactl --cpunodebind=0 --membind=0 ./server
# 或在两节点交错分配(带宽均衡、无单点过热)
numactl --interleave=all ./server改完再用 numastat -p <pid> 复查分布是否符合预期,用 perf bench / 业务指标对比延迟。
常见坑
- 只看总内存够不够:多路机器上总量够但单节点内存不足,会触发跨节点甚至回收,导致尾延迟。
- 单线程初始化大堆:首触导致全堆在一个节点;可并行初始化或用
--interleave=all。 - 绑了 CPU 没绑内存(或反之):CPU 与内存策略要一致,否则照样跨节点访问。
- JVM 大堆/大页:堆跨节点时 GC 线程远程访问放大,可用 numactl 绑定或按节点拆分实例。
- 统计字段误读:
numastat无参数是累计计数器,需看变化率/对比基线,单次快照不代表速率。 - 云/NUMA 不透明:部分云实例 vCPU 与物理节点映射不固定,numactl 看到的拓扑可能是虚拟化结果,效果以实测为准。
总结
多路服务器的本地内存访问快于跨 socket 的远端访问,numastat 先用 numactl --hardware 确认节点、CPU 与 distance(10 本地/20 远端),再用 numastat -p 看进程内存页在各节点的分布、用整机的 Numa_Hit/Numa_Foreign/Other_Node 判断跨节点访问是否偏高;最常见根因是 first-touch 首触分配——单线程在 node0 预热把整个大堆分到 node0,导致运行在 node1 的核远程取数,修法是让初始化并行、用 numactl --cpunodebind 配 --membind 把 CPU 和内存绑同一节点或用 --interleave=all 平衡带宽,改后再用 numastat 复查;注意 numastat 是累计计数要看趋势、绑 CPU 必须同时绑内存、JVM 大堆和云环境的 NUMA 拓扑要以实测为准。