性能调优之NUMA
NUMA介绍
NUMA(Non-Uniform Memory Access,非统一内存访问)是一种内存访问架构。在这种架构中,不同CPU访问同一块内存的速度不同有所不同

那你可能会有一个疑问,服务器会在什么时候出现跨node访问内存的情况呢?
第一种场景:本地节点内存不足,或内存分配策略未绑定节点,导致CPU不得不访问其他节点的内存。
1、当某一节点的 CPU 进程占用内存超过其本地内存上限时,服务器会自动将超出部分的内存分配到其他 NUMA 节点的 “远程内存” 中,此时该 CPU 访问这部分内存就会触发跨 NUMA。
# 服务器有2个NUMA节点(节点0:CPU 0-7 + 128GB 本地内存;节点1:CPU 8-15 + 128GB 本地内存)。仅在节点0的CPU 0上运行一个需要150GB内存的数据库进程:节点0的128GB本地内存耗尽后,剩余22GB内存会被分配到节点1的本地内存中。此时CPU 0访问这22GB内存时,必须跨节点通过NUMA互联总线(如Intel UPI、AMD Infinity Fabric)访问节点1的内存,形成跨NUMA。
2、Linux/Unix系统默认的内存分配策略(如default策略)是 “优先分配本地内存,但本地不足时分配远程内存”;但如果未配置“强绑定策略”(如membind),即使本地内存充足,也可能因内核调度逻辑导致内存被分配到其他节点。
# 运维未通过工具(如numactl、taskset)绑定进程的内存节点,仅绑定了CPU核心。例如:将进程绑定到节点0的CPU 0,但内存分配未限制为节点0,内核可能因 “内存碎片优化” 将部分内存分配到节点1,导致跨NUMA。 # 虚拟化场景中,虚拟机(VM)的内存未与物理NUMA节点对齐:若VM的vCPU绑定到节点0,但VM的内存被KVM分配到节点1的物理内存,vCPU访问内存时会跨NUMA。
3、当多个NUMA节点的CPU需要访问同一块共享内存(如进程间通信的shm、分布式缓存的共享段)时,若共享内存被分配到某一节点(如节点 0),则其他节点(如节点1)的CPU访问该共享内存时,必然触发跨NUMA。
# 节点0的CPU 0创建一块10GB的共享内存,用于与节点1的CPU 8通信;节点1的CPU 8读取 / 写入该共享内存时,需跨节点访问节点0的本地内存,形成跨NUMA。
第二种场景:CPU访问非本地节点的PCIe设备
# 网卡场景:若网卡直连节点1的 PCIe 总线(归属节点1),但处理网卡流量的进程(如Nginx、DPDK 应用)被绑定到节点0的CPU,此时节点0的CPU需要跨节点访问节点1的网卡,导致数据传输延迟升高。 # SSD 场景:若 NVMe SSD归属节点0,但运行数据库的进程被绑定到节点1的CPU,CPU读取SSD数据时需跨NUMA,影响IO性能。
2. 多设备负载集中在单一节点,其他节点需跨节点调用
# 示例:某进程的内存分配在节点0,但其被调度器分配到节点1的CPU 8上运行。此时CPU 8访问该进程的内存(节点 0),就会触发跨 NUMA。 # 常见于默认调度策略(如Linux的CFS调度器):调度器优先考虑CPU负载均衡,而非NUMA节点亲和性,可能导致 “进程-内存-CPU” 跨节点。
# 若虚拟机的vCPU配置未与物理NUMA节点 “对齐”(如vCPU跨越物理节点0和1),KVM等虚拟化层可能将vCPU调度到不同物理节点的CPU上,导致vCPU访问VM内存时跨物理NUMA节点。 # 例如:VM配置4个vCPU,被分配到物理节点0的 CPU 0-2和节点1的CPU 8,此时vCPU 3(对应物理CPU8)访问VM内存(若分配到节点0)时,会跨NUMA。
第四章场景:NUMA节点配置错误或硬件限制
# 若服务器BIOS中未启用NUMA(或配置为 “UMA 模式”,即均匀内存访问),但实际硬件支持NUMA,可能导致系统误将不同节点的资源视为同一节点,或反之将同一节点拆分为多个,间接引发跨NUMA。 # 例如:BIOS中 “NUMA Node Interleaving”(NUMA节点交错)被启用,会强制内存跨节点均匀分配,导致所有CPU访问内存时都可能跨NUMA(除非进程强绑定)。
# 例如:服务器某一NUMA节点的CPU核心全部被占用(如节点0的8个CPU满负载),但仍有新进程需要运行,调度器只能将新进程分配到节点1的CPU,若新进程的内存/设备在节点0,则必然跨NUMA。
检查系统是否支持numa以及bios是否开启numa
- 系统开启/关闭numa系统
dmesg | grep -i numa # 检查系统是否开启numa # 开启numa # 1、编辑 /etc/default/grub 文件,加上:numa=on GRUB_CMDLINE_LINUX="crashkernel=auto numa=on rd.lvm.lv=centos/root rd.lvm.lv=centos/swap rhgb quiet" # 2、重新生成 /etc/grub2.cfg 配置文件: grub2-mkconfig -o /etc/grub2.cfg # 3. 重启操作系统 reboot
- 鲲鹏服务器开启numa
服务器重启,进入BIOS,依次选择”BIOS->Advanced->Memory Config -> NUMA”

numactl工具使用
numactl工具可用于查看当前服务器的NUMA节点配置、状态,可通过该工具将进程绑定到指定CPU core,由指定CPU core来运行对应进程。
如果系统中没有这个命令可以通过yum -y install numactl numastat来进行安装
使用方法如下
numactl [ --interleave nodes ] [ --preferred node ] [ --membind nodes ] [ --cpunodebind nodes ] [ --physcpubind cpus ] [ --localalloc ] command {arguments ...} # 常用参数 # --hardware 显示系统上可用节点的清单,包括节点之间的相对距离。 # -N --cpunodebind 确保指定的命令及其子进程仅在指定节点上执行。 # -l --localalloc 指定始终从本地节点分配内存。 # -i --interleave=0,1|all:设置内存交错策略,内存将在指定的节点之间循环分配。 # --preferred=node:设置首选节点,如果可能,内存将被分配到这个节点上。 # -m --membind=nodes:设置内存绑定策略,只从指定的节点分配内存。 # --cpunodebind=nodes:设置CPU节点绑定,只在指定节点的CPU上执行命令。 # -C --physcpubind=cpus:设置物理CPU绑定,只在指定的CPU上执行进程。
1、检查NUMA节点的内存访问统计
[root@localhost ~]# numastat node0 node1 node2 node3 numa_hit 414 552466 414 55540 numa_miss 0 0 0 0 numa_foreign 0 0 0 0 interleave_hit 0 3005 0 2847 local_node 0 224468 0 15870 other_node 414 327998 414 39670 # numa_hit:该节点成功分配本地内存访问的内存大小 # numa_miss:内存访问分配到另一个node的大小,该值和另一个node的numa_foreign相对应 # numa_foreign 其他节点分配失败,由本节点代为分配的次数 # interleave_hit 通过交错策略在本节点分配的次数 # local_node:该节点的进程成功在本节点上分配内存访问的大小 # other_node:该节点进程在其它节点上分配的内存访问的大小 # 或者通过下面命令 [root@localhost ~]# cat /sys/devices/system/node/node*/numastat numa_hit 210079 numa_miss 0 numa_foreign 0 interleave_hit 1412 local_node 206615 other_node 3464 ...
2、确认硬件拓扑和NUMA架构
[root@localhost ~]# lscpu | grep -i numa NUMA 节点: 4 NUMA 节点0 CPU: 0-23 NUMA 节点1 CPU: 24-47 NUMA 节点2 CPU: 48-71 NUMA 节点3 CPU: 72-95
3、显示示当前进程的 NUMA 策略
[root@localhost ~]# numactl -s policy: default preferred node: current physcpubind: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 cpubind: 0 1 2 3 nodebind: 0 1 2 3 membind: 0 1 2 3 # 输出解释 # policy:当前内存分配策略,例如 default 表示默认策略。 # preferred node:优先分配内存的节点编号。 # physcpubind:绑定的物理 CPU 列表。 # cpubind:绑定的 NUMA 节点。 # nodebind:绑定的节点列表。 # membind:内存分配限制在指定节点。
4、这告诉你系统有4个NUMA节点,以及每个节点包含了哪些CPU核心。
[root@localhost ~]# numactl -H available: 4 nodes (0-3) node 0 cpus: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 node 0 size: 130333 MB node 0 free: 129064 MB node 1 cpus: 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 node 1 size: 130928 MB node 1 free: 130552 MB node 2 cpus: 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 node 2 size: 130898 MB node 2 free: 130319 MB node 3 cpus: 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 node 3 size: 129903 MB node 3 free: 128242 MB node distances: node 0 1 2 3 0: 10 12 20 22 1: 12 10 22 24 2: 20 22 10 12 3: 22 24 12 10
关键信息:
-
node distances:节点间的访问代价。node0访问node0的内存代价是10,访问node1的内存代价是12(越高越慢)。这清晰地显示了跨节点访问的性能 penalty。
5、显示每个节点的系统内存使用情况
[root@localhost ~]# numastat -m Per-node system memory usage (in MBs): Node 0 Node 1 Node 2 Node 3 --------------- --------------- --------------- --------------- MemTotal 130333.25 130928.94 130898.81 129903.81 # 该 NUMA 节点的 总物理内存大小 MemFree 129047.00 130573.25 130285.56 127070.00 # 该节点空闲的物理内存 MemUsed 1286.25 355.69 613.25 2833.81 # 该节点已使用的物理内存,计算公式:MemUsed = MemTotal - MemFree。 Active 172.69 23.50 178.69 1903.50 # 频繁访问的内存页总量 Inactive 127.25 33.44 79.62 450.00 # 近期较少访问的内存页总量 Active(anon) 144.38 5.56 154.00 1683.75 # 活跃状态的匿名内存页 Inactive(anon) 17.00 11.56 16.69 16.75 # 非活跃状态的匿名内存页,仍属于进程但近期未访问,可回收 Active(file) 28.31 17.94 24.69 219.75 # 活跃状态的文件关联内存页(如文件缓存、可执行程序代码段),访问频繁。 Inactive(file) 110.25 21.88 62.94 433.25 # 非活跃状态的文件关联内存页(如长时间未访问的文件缓存),优先回收(回收后可通过重读文件恢复) Unevictable 0.00 0.00 0.00 0.06 # 标记为不可回收的内存页总量(包含锁定内存和特殊匿名页,如进程正在使用的共享内存) Mlocked 0.00 0.00 0.00 0.06 # 被进程通过mlock()系统调用显式锁定的内存页(锁定后不会被交换到 swap,确保低延迟访问) Dirty 0.06 0.00 0.00 0.12 # 已修改但尚未写入磁盘的内存页(脏页),系统会定期通过pdflush进程刷盘 Writeback 0.00 0.00 0.00 0.00 # 正在被写入磁盘的内存页(处于刷盘过程中,暂时不可访问) FilePages 159.38 54.12 106.06 1696.88 # 与文件关联的内存页总量 Mapped 44.19 10.50 22.94 187.06 # 被进程映射到虚拟地址空间 的内存页总量 AnonPages 142.56 2.81 155.56 657.88 # 匿名内存页总量(不含共享匿名页),主要来自进程的堆、栈(进程私有,其他进程不可访问) Shmem 20.81 14.31 18.38 1043.81 # 共享内存页总量(包含 tmpfs、POSIX共享内存、System V共享内存),支持多进程间数据共享 KernelStack 5.81 3.19 4.53 8.53 # 所有进程的内核栈内存总和(每个进程在 kernel 态都有独立栈,默认大小为 8KB/16KB)。 PageTables 4.94 0.25 1.62 19.12 # 用于存储虚拟地址到物理地址映射关系的页表所占用的内存(进程越多、内存越大,页表占用越高) NFS_Unstable 0.00 0.00 0.00 0.00 Bounce 0.00 0.00 0.00 0.00 # 用于地址转换缓冲的内存页 WritebackTmp 0.00 0.00 0.00 0.00 # 临时用于文件系统(如 ext3)异步刷盘 的内存页(通常为 0,仅特定场景下非 0)。 Slab 221.50 145.12 139.06 251.00 # 内核 Slab 分配器 管理的内存总量 SReclaimable 33.75 17.12 15.69 53.50 # Slab 内存中 可回收 的部分 SUnreclaim 187.75 128.00 123.38 197.50 # Slab 内存中 不可回收 的部分 AnonHugePages 0.00 0.00 0.00 0.00 ShmemHugePages 0.00 0.00 0.00 0.00 # 以大页形式分配的共享内存 ShmemPmdMapped 0.00 0.00 0.00 0.00 # 通过PMD(页中间目录)映射的共享内存页 HugePages_Total 0.00 0.00 0.00 0.00 # 该节点预分配的大页总数(通过 /proc/sys/vm/nr_hugepages 配置) HugePages_Free 0.00 0.00 0.00 0.00 # 该节点空闲的大页数量(HugePages_Total - HugePages_Used)。 HugePages_Surp 0.00 0.00 0.00 0.00 # 超出预分配数量的临时大页数量(仅当开启 vm.nr_hugepages_mempolicy 时可能非 0,通常为 0) KReclaimable 33.75 17.12 15.69 53.50 Total --------------- MemTotal 522064.81 MemFree 516975.81 MemUsed 5089.00 Active 2278.38 Inactive 690.31 Active(anon) 1987.69 Inactive(anon) 62.00 Active(file) 290.69 Inactive(file) 628.31 Unevictable 0.06 Mlocked 0.06 Dirty 0.19 Writeback 0.00 FilePages 2016.44 Mapped 264.69 AnonPages 958.81 Shmem 1097.31 KernelStack 22.06 PageTables 25.94 NFS_Unstable 0.00 Bounce 0.00 WritebackTmp 0.00 Slab 756.69 SReclaimable 120.06 SUnreclaim 636.62 AnonHugePages 0.00 ShmemHugePages 0.00 ShmemPmdMapped 0.00 HugePages_Total 0.00 HugePages_Free 0.00 HugePages_Surp 0.00 KReclaimable 120.06
6、进程绑定到1个或者多个numa上
numactl -m 0 top # top进程内存绑定内存到numa0 numactl -m 0,1 top # top进程内存绑定内存到numa0和numa1
7、绑定进程优先使用本节点内存,不够时使用其它节点内存
numactl -l 0 top
8、通过 numactl -C 5-15 dd命令即是将进程“dd”绑定到5~15 CPU core上执行,同时将进程内存绑定到不同numa上查看
[root@localhost ~]# numactl -C 5-15 --membind=0 dd if=/dev/zero of=/dev/shm/A bs=1M count=1024 记录了1024+0 的读入 记录了1024+0 的写出 1073741824字节(1.1 GB,1.0 GiB)已复制,0.227962 s,4.7 GB/s [root@localhost ~]# numactl -C 5-15 --membind=1 dd if=/dev/zero of=/dev/shm/A bs=1M count=1024 记录了1024+0 的读入 记录了1024+0 的写出 1073741824字节(1.1 GB,1.0 GiB)已复制,0.227861 s,4.7 GB/s [root@localhost ~]# numactl -C 5-15 --membind=2 dd if=/dev/zero of=/dev/shm/A bs=1M count=1024 记录了1024+0 的读入 记录了1024+0 的写出 1073741824字节(1.1 GB,1.0 GiB)已复制,0.332766 s,3.2 GB/s [root@localhost ~]# numactl -C 5-15 --membind=3 dd if=/dev/zero of=/dev/shm/A bs=1M count=1024 记录了1024+0 的读入 记录了1024+0 的写出 1073741824字节(1.1 GB,1.0 GiB)已复制,0.348661 s,3.1 GB/s
可以看出如果出现跨NUMA后带宽会有明显下降..

浙公网安备 33010602011771号