1. 实验背景
本文基于一台 Dell T430 双路服务器,围绕 Linux NUMA(Non-Uniform Memory Access,非一致内存访问)机制进行了基础验证实验。实验目标主要包括:
- 识别并确认服务器的 NUMA 拓扑。
- 理解 CPU 绑核与内存绑定之间的区别。
- 验证本地内存访问(local access)与远端内存访问(remote access)的性能差异。
- 验证 Linux 默认的 first-touch 内存分配行为。
- 初步观察 automatic NUMA balancing 与 interleave 分配策略的影响。
本文“基于实测数据得出结论”。
2. 实验平台
2.1 服务器硬件概况
- 服务器型号:Dell T430
- CPU:2 × Intel Xeon E5-2682 v4 @ 2.50GHz
- 架构:x86_64
- 每颗 CPU:
- 16 个物理核
- 32 个逻辑 CPU(开启超线程)
- 全机总逻辑 CPU 数:64
- 内存总量:约 62 GiB
- NUMA node 数量:2
2.2 NUMA 拓扑
从 numactl --hardware 可见:
- node 0:
- CPU:0 2 4 … 62
- 内存:31703 MB
- node 1:
- CPU:1 3 5 … 63
- 内存:32202 MB
节点距离矩阵如下:
| Node | 0 | 1 |
|---|---|---|
| 0 | 10 | 21 |
| 1 | 21 | 10 |
这说明:
- 本地访问代价较低(distance=10)
- 跨节点访问代价明显更高(distance=21)
这正是 NUMA 的典型特征:内存访问代价取决于 CPU 与内存是否处于同一 NUMA node。
3. 拓扑识别实验
3.1 numactl --hardware
通过 numactl --hardware,可以快速确认:
- 系统存在 2 个 NUMA node
- 每个 node 对应一组独立 CPU
- 每个 node 具备独立本地内存容量
- 节点间存在本地/远端距离差异
这是 NUMA 实验的第一步,也是后续绑核与绑内存实验的基础。
3.2 lstopo-no-graphics
lstopo-no-graphics 进一步揭示了更细粒度的硬件结构:
- 每个 Package 对应一个 NUMA node
- 每个 Package 下有独立的 L3 cache(40 MB)
- 每个 Core 下挂两个 PU(Processing Unit),对应超线程
- NUMA node 与 CPU package 高度对应
由此可确认本机属于典型双路双 NUMA 服务器,非常适合做本地/远端访存差异实验。
3.3 sysfs 验证
通过以下目录可直接从内核视角观察 NUMA 结构:
/sys/devices/system/node/node0/sys/devices/system/node/node1
其中:
cpulist可查看 node 内 CPU 列表meminfo可查看 node 内存总量与剩余量distance可查看 node 距离numastat/vmstat可查看 node 级别统计信息
这说明 Linux 内核已经完整暴露 NUMA 拓扑,用户态可以方便地基于 sysfs 做自动化识别。
4. 内存硬件分布观察
4.1 按 node 统计的内存容量
实验记录显示:
- node0:32464060 kB
- node1:32975788 kB
两边容量接近,各自约 31 GiB,符合双路均匀分布预期。
4.2 内存 block 大小
/sys/devices/system/memory/block_size_bytes 的值为:
1
80000000
即十六进制 0x80000000,折合 2 GiB。
这表示 Linux 内核在该平台上以内存 section / block 的粒度管理热插拔与节点归属信息。对于本文主题而言,这不是性能核心因素,但有助于理解 /sys/devices/system/node/nodeX/memory* 目录为何按离散 block 展示。
4.3 DIMM 信息
dmidecode -t memory 显示:
- A1:32 GB DDR4 RDIMM,2400 MT/s
- B1:32 GB DDR4 RDIMM,2400 MT/s
其余插槽为空。
这意味着当前服务器总内存由两条 32 GB 内存条组成,分布在两个 CPU 对应的内存通道侧。虽然详细通道平衡信息未进一步展开,但从 node0 / node1 内存容量接近可以看出,OS 已将其识别为两侧近似对称的 NUMA 内存。
5. CPU 绑核与内存绑定实验
本节核心目的,是区分以下两个概念:
- CPU affinity / cpubind:进程在哪些 CPU 上运行
- Memory policy / membind:进程从哪些 NUMA node 分配内存
5.1 仅绑 CPU,不绑内存
执行:
1
numactl --cpunodebind=0 --show
输出显示:
cpubind: 0nodebind: 0membind: 0 1policy: default
这说明:
- 进程运行位置被限制在 node0 的 CPU 上
- 但内存仍可从 node0 和 node1 分配
- 仅绑 CPU,并不等于内存自动绑定到同一 node
同样地,执行:
1
2
numactl --cpunodebind=1 bash
numactl --show
可观察到:
cpubind: 1membind: 0 1
结论完全一致。
5.2 同时绑定 CPU 与本地内存
执行:
1
2
numactl --cpunodebind=0 --membind=0 bash
numactl --show
可见:
policy: bindcpubind: 0membind: 0
这表示:
- 进程只在 node0 的 CPU 上运行
- 进程内存只从 node0 分配
这是最标准的“本地执行、本地分配”。
5.3 故意制造远端访存
执行:
1
2
numactl --cpunodebind=0 --membind=1 bash
numactl --show
可见:
cpubind: 0membind: 1
这意味着:
- 进程跑在 node0 CPU
- 但内存强制从 node1 分配
这类配置是构造 NUMA 反例实验的常见方法,也正是后续性能对比的基础。
6. Benchmark 程序设计
实验中编译了一个自定义测试程序 numa_walk.c,并生成可执行文件 numa_walk。
程序逻辑概括如下:
- 分配指定大小的内存(本实验使用 16 GiB)。
- 通过按页写入完成 first-touch,确保页真正落地到物理内存。
- 顺序遍历缓冲区并累加,统计总耗时。
- 计算带宽(GiB/s)。
这种设计非常适合 NUMA 入门验证,因为它能放大以下影响:
- 内存页实际落在哪个 node
- 当前线程跑在哪个 node
- 本地访问与远端访问之间的差异
7. 本地访问与远端访问性能对比
7.1 实验命令
共测试了四组:
1
2
3
4
numactl --cpunodebind=0 --membind=0 ./numa_walk 16 20
numactl --cpunodebind=0 --membind=1 ./numa_walk 16 20
numactl --cpunodebind=1 --membind=1 ./numa_walk 16 20
numactl --cpunodebind=1 --membind=0 ./numa_walk 16 20
7.2 实测结果
| 编号 | CPU 所在 node | 内存所在 node | 类型 | 耗时(s) | 带宽(GiB/s) |
|---|---|---|---|---|---|
| A | 0 | 0 | 本地访问 | 27.989 | 11.43 |
| B | 0 | 1 | 远端访问 | 46.580 | 6.87 |
| C | 1 | 1 | 本地访问 | 28.039 | 11.41 |
| D | 1 | 0 | 远端访问 | 45.313 | 7.06 |
7.3 结果分析
7.3.1 本地访问性能明显更好
两组本地访问结果非常接近:
- node0 本地:11.43 GiB/s
- node1 本地:11.41 GiB/s
说明两侧拓扑较为对称,且在该工作负载下,本地内存读取性能稳定。
7.3.2 远端访问性能明显下降
两组远端访问分别为:
- node0 CPU 访问 node1 内存:6.87 GiB/s
- node1 CPU 访问 node0 内存:7.06 GiB/s
相比本地访问,带宽下降约:
- B 相对 A:下降约 39.9%
- D 相对 C:下降约 38.1%
可见在该机器上,远端访存带来的性能损失接近 40%。
7.3.3 结论
对该类流式内存遍历负载而言:
NUMA locality 是决定性能的关键因素之一。
只要 CPU 与内存不在同一 NUMA node,即使代码完全相同,也会出现显著性能下滑。
8. first-touch 验证实验
Linux 默认内存分配策略中,一个非常关键的原则是:
页通常会被分配到“首次真正写入该页的 CPU 所属 node”。
这就是 first-touch。
8.1 在 node0 上 first-touch
执行:
1
2
3
numactl --cpunodebind=0 ./numa_walk 16 5 10 &
numastat -p $PID
cat /proc/$PID/numa_maps | head -n 30
在程序完成 first-touch 后,可见:
numastat -p显示约 16385 MB Private 内存位于 Node 0- Node 1 仅有极少量页
numa_maps中大块匿名内存显示为N0=4194305
这说明 16 GiB 主体数据页几乎全部分配在 node0。
8.2 在 node1 上 first-touch
执行:
1
2
3
numactl --cpunodebind=1 ./numa_walk 16 5 10 &
numastat -p $PID
cat /proc/$PID/numa_maps | head -n 30
在程序完成 first-touch 后,可见:
numastat -p显示约 16384 MB Private 内存位于 Node 1- Node 0 仅有约 1.41 MB 零星页
numa_maps中大块匿名内存显示为N1=4194305
这说明 16 GiB 主体数据页几乎全部落在 node1。
8.3 结论
该实验直接验证了:
在默认策略下,Linux 会将绝大多数匿名页分配到首次触碰它们的 CPU 所属 NUMA node。
这也是 NUMA 优化中最常见、最重要的基础知识之一。很多应用没有显式使用 numactl 或 NUMA API,但其实际页布局已经深受 first-touch 影响。
9. automatic NUMA balancing 初步观察
9.1 当前状态
实验前查看:
1
cat /proc/sys/kernel/numa_balancing
结果为:
1
1
说明 automatic NUMA balancing 默认开启。
随后执行:
1
echo 0 > /proc/sys/kernel/numa_balancing
将其关闭。
9.2 实验过程
先关闭 automatic NUMA balancing:
1
echo 0 > /proc/sys/kernel/numa_balancing
随后执行:
1
2
3
4
5
taskset -c 0 ./numa_walk 16 50 15 &
PID=$!
sleep 3
taskset -cp 1,3,5,7 $PID
watch -n 1 "numastat -p $PID"
实验含义如下:
- 先让程序在 CPU0 上运行并完成 first-touch。
- 由于 first-touch 发生在 node0,主体数据页优先落在 node0。
- 随后将进程的 CPU affinity 迁移到 node1 的 CPU 集合。
- 在 automatic NUMA balancing 关闭的情况下,观察页是否仍主要停留在 node0。
在 automatic NUMA balancing 关闭时,numastat -p 观察到:
1
2
3
4
5
6
7
8
9
Per-node process memory usage (in MBs) for PID 598730 (numa_walk)
Node 0 Node 1 Total
--------------- --------------- ---------------
Huge 0.00 0.00 0.00
Heap 0.00 0.00 0.00
Stack 0.02 0.00 0.02
Private 16384.66 0.83 16385.49
---------------- --------------- --------------- ---------------
Total 16384.68 0.83 16385.51
这说明:
- 进程执行位置已经迁移到 node1 的 CPU 集合;
- 但绝大多数 Private 匿名页仍停留在 node0;
- 因而形成了非常典型的 CPU 在 node1、内存在 node0 的 remote access 场景。
随后,在另一个终端重新开启 automatic NUMA balancing:
1
echo 1 > /proc/sys/kernel/numa_balancing
继续观察 numastat -p,可见页分布逐步迁移到 node1。实验记录中的一个观察点如下:
1
2
3
4
5
6
7
8
9
Per-node process memory usage (in MBs) for PID 598730 (numa_walk)
Node 0 Node 1 Total
--------------- --------------- ---------------
Huge 0.00 0.00 0.00
Heap 0.00 0.00 0.00
Stack 0.01 0.00 0.02
Private 0.66 16384.83 16385.49
---------------- --------------- --------------- ---------------
Total 0.67 16384.84 16385.51
这说明在重新开启 automatic NUMA balancing 后:
- 大量原先位于 node0 的匿名页被逐步迁移到 node1;
- 迁移后的页分布已经从“几乎全在 node0”变为“几乎全在 node1”;
- Linux 内核确实在尝试恢复线程执行位置与页位置之间的 locality。
9.3 结果分析与结论
这一组实验已经形成较完整的证据链:
- 进程最初在 node0 上 first-touch,因此大部分页首先落在 node0。
- 进程随后被迁移到 node1 的 CPU 上运行。
- 当 automatic NUMA balancing 关闭时,
numastat -p明确显示页仍主要停留在 node0,形成 remote access。 - 当重新开启 automatic NUMA balancing 后,
numastat -p明确显示页从 node0 逐步迁移到 node1。
因此,本实验可以得出较强结论:
automatic NUMA balancing 能够根据任务的实际执行位置,对匿名页进行跨 NUMA node 迁移,以改善内存 locality。
同时,这一实验也进一步说明:
- NUMA 性能问题并不只取决于“程序起初在哪分配内存”,
- 还取决于“程序运行过程中线程是否迁移、内核是否允许并完成页迁移”。
从性能结果看,迁移场景下的测得带宽为:
time=94.216 sbandwidth=8.49 GiB/s
该值低于严格本地访问(约 11.4 GiB/s),但高于严格远端绑定(约 6.9~7.1 GiB/s)。这与实验现象是一致的:任务执行过程中 locality 先变差,随后在 automatic NUMA balancing 开启后逐步恢复,因此整体性能处于二者之间。
9.4 本节小结
本节实验清楚展示了三种状态:
- first-touch 后页落在 node0;
- CPU 迁移到 node1、但页仍留在 node0 时,形成 remote access;
- 重新开启 automatic NUMA balancing 后,页会逐步迁移到 node1,恢复 locality。
这使得 NUMA 实验不再只是“静态绑核/绑内存”的验证,而是进一步观察到了 Linux 内核在动态运行阶段对 NUMA locality 的修正能力。
10. interleave 策略实验
10.1 实验命令
执行:
1
numactl --cpunodebind=0 --interleave=all ./numa_walk 16 20
得到结果:
- 耗时:37.229 s
- 带宽:8.60 GiB/s
随后再次复测对照组:
1
2
numactl --cpunodebind=0 --membind=0 ./numa_walk 16 20
numactl --cpunodebind=0 --membind=1 ./numa_walk 16 20
结果为:
- 本地:11.31 GiB/s
- 远端:6.89 GiB/s
10.2 对比结果
| 策略 | CPU node | 内存策略 | 带宽(GiB/s) |
|---|---|---|---|
| 本地绑定 | 0 | --membind=0 |
11.31 |
| 交错分配 | 0 | --interleave=all |
8.60 |
| 远端绑定 | 0 | --membind=1 |
6.89 |
10.3 结论
interleave=all 的性能落在本地与远端之间,符合预期。
其原因在于:
- 一部分页落在 node0,本地访问较快
- 另一部分页落在 node1,访问时需要跨 node
- 因此整体性能被“平均化”
这类策略的价值不在于追求单线程极致性能,而更适用于:
- 需要跨 node 均匀消耗内存容量
- 希望降低单 node 内存压力
- 负载对绝对延迟不那么敏感的场景
11. 实验结论
基于本次实测,可以得到以下结论。
11.1 本机是典型双路双 NUMA 服务器
该 Dell T430 具备:
- 2 个 CPU package
- 2 个 NUMA node
- 每个 node 约 31 GiB 本地内存
- 本地/远端 node distance 分别为 10 / 21
这为 NUMA locality 研究提供了非常清晰的平台。
11.2 仅绑 CPU 不等于绑内存
--cpunodebind 只能限制进程在哪些 CPU 上运行,默认并不会将内存同步绑定到同一 node。若要保证内存本地化,需要显式指定 --membind。
11.3 本地访问显著优于远端访问
在本实验的 16 GiB 顺序遍历负载中:
- 本地访问约 11.4 GiB/s
- 远端访问约 6.9~7.1 GiB/s
远端访存性能下降约 38%~40%。
这说明 NUMA 亲和性对内存密集型程序影响非常显著。
11.4 first-touch 行为被明确验证
在默认策略下:
- 进程在 node0 上 first-touch,则大页主要落在 node0
- 进程在 node1 上 first-touch,则大页主要落在 node1
这说明很多程序即便没有主动做 NUMA 优化,其实际页布局也已经被初始化阶段的线程位置决定。
11.5 interleave 策略是折中方案
--interleave=all 的性能处于本地绑定与远端绑定之间,更适合容量均衡,而非极致性能。
11.6 automatic NUMA balancing 可在运行期修正 NUMA locality
本次实验已经验证:
- automatic NUMA balancing 默认开启;
- 关闭该机制后,进程从 node0 迁移到 node1 运行时,主体页仍停留在 node0;
- 重新开启该机制后,
numastat -p明确显示主体页从 node0 逐步迁移到 node1。
这说明 Linux 内核能够根据线程的实际执行位置,对匿名页执行跨 node 迁移,从而改善 locality。
更准确地说:
当线程运行位置与页位置发生偏离时,automatic NUMA balancing 可以在运行期逐步把热点页迁移到更合适的 NUMA node。
12. 对 EDA / HPC 场景的启发
对于 EDA、仿真、版图、签核、数据库、HPC 这类大内存工作负载,NUMA 往往不是“理论知识”,而是直接影响生产性能的关键因素。
12.1 对单进程大内存程序
若线程主要运行在一个 socket,但数据页落在另一个 socket,则会长期承受 remote access penalty,表现为:
- 带宽下降
- 延迟上升
- CPU 利用率看似正常但任务耗时偏长
12.2 对多线程程序
如果初始化阶段全部线程集中在一个 node 完成 first-touch,而后续计算线程分布到两个 node,则很容易造成一半线程长期访问远端页。
12.3 对调度系统
在 LSF / Slurm 等调度环境中,若调度器只关心“给了多少核”和“给了多少内存”,而不关心 CPU 与内存的 node 亲和关系,则用户侧会看到:
- 资源配额足够
- 但任务性能不稳定或偏低
因此 NUMA 感知调度、绑核与内存本地化策略,都是高性能计算环境中的基础能力。
13. 后续建议实验
为了进一步掌握 NUMA,建议继续补做以下实验:
13.1 多线程版本 benchmark
当前 numa_walk 更接近单线程流式访问模型。下一步可扩展为:
- 2 线程 / 4 线程 / 8 线程版本
- 分别比较“线程与数据局部性良好”和“线程与数据局部性打乱”的差异
13.2 自动页迁移量化实验
本次已经观察到:
numa_balancing=0时,CPU 迁到 node1 后,页仍主要停留在 node0;numa_balancing=1重新开启后,页会逐步迁移到 node1。
后续可进一步做量化增强:
- 固定采样时间点(例如 0s / 5s / 10s / 20s)
- 每个时间点记录
numastat -p - 同时保留
/proc/<pid>/numa_maps中热点匿名段的 node 分布 - 统计迁移完成时间与性能恢复速度
这样可以把“观察到迁移”进一步提升为“定量分析迁移速度与收益”。
13.3 使用 perf 观察硬件事件
可进一步引入:
1
perf stat -d -d -d <command>
观察:
- cache misses
- stalled cycles
- instructions per cycle
这样能把 NUMA 带来的性能退化与微架构指标联系起来。
13.4 使用 numastat 观察系统级行为
除了 numastat -p PID,还可以直接运行:
1
numastat
观察系统层面的:
- local_node
- other_node
- numa_hit
- numa_miss
- interleave_hit
从全局视角理解 NUMA 访问情况。
14. 总结
本次实验已经完成了 NUMA 学习中最核心的一步:把抽象概念转化为可重复、可量化、可解释的实测结果。
最重要的收获有三点:
- NUMA 不是“有或没有”的概念,而是“线程与页是否匹配”的问题。
- first-touch 会直接影响页落点,是理解 NUMA 的核心入口。
- 本地访存与远端访存的性能差异是真实且显著的,在本机上可达到约 40% 的带宽差距。
- automatic NUMA balancing 不只是一个开关名词,而是能够在运行过程中把热点页从远端 node 迁回当前执行 node 的实际机制。
对于后续的性能优化、EDA 平台调优、HPC 调度设计而言,这些结论已经足以作为第一层实践基础。
15. 附:关键实验命令清单
查看 NUMA 拓扑
1
2
3
4
5
6
7
numactl --hardware
lstopo-no-graphics
for n in /sys/devices/system/node/node*; do
echo "===== $n ====="
cat $n/cpulist
egrep 'MemTotal|MemFree' $n/meminfo
done
查看当前进程 NUMA 策略
1
2
3
4
numactl --show
numactl --cpunodebind=0 --show
numactl --cpunodebind=0 --membind=0 --show
numactl --cpunodebind=0 --membind=1 --show
运行本地 / 远端对比实验
1
2
3
4
numactl --cpunodebind=0 --membind=0 ./numa_walk 16 20
numactl --cpunodebind=0 --membind=1 ./numa_walk 16 20
numactl --cpunodebind=1 --membind=1 ./numa_walk 16 20
numactl --cpunodebind=1 --membind=0 ./numa_walk 16 20
观察 first-touch
1
2
3
numactl --cpunodebind=0 ./numa_walk 16 5 10 &
numastat -p $PID
cat /proc/$PID/numa_maps | head -n 30
观察 automatic NUMA balancing
1
2
3
4
5
cat /proc/sys/kernel/numa_balancing
echo 0 > /proc/sys/kernel/numa_balancing
taskset -c 0 ./numa_walk 16 50 15 &
taskset -cp 1,3,5,7 $PID
watch -n 1 "numastat -p $PID"
interleave 策略实验
1
numactl --cpunodebind=0 --interleave=all ./numa_walk 16 20