NUMA 笔记
基础概念
socket、core、processor 的概念
在 NUMA 架构下,CPU 的概念从大到小依次是:socket
、core
、processor
。
socket
(CPU插槽
)
将多个CPU核
封装在一起,该封装称为socket
。即我们平常肉眼所见的CPU插槽
所对应的物理CPU封装芯片。
工具:
命令
lscpu
输出结果中的Socket(s)
字段,可以看到socket
的个数。
路径/proc/cpuinfo
中的physical id
字段,表示的就是一个CPU插槽
的标识号。
路径/sys/devices/system/cpu/cpu${processor_index}/topology/physical_package_id
,表示某个逻辑核${processor_index}
所在在的CPU插槽
的标识号。其中${processor_index}是逻辑核
序号。
core
(CPU核
)
socket
中的每个物理CPU核
被称为core
。
工具:
命令
lscpu
输出结果中的Core(s) per socket
字段,可以看到一个socket
的上core
的个数。
processor
(逻辑核
)
为了进一步提升core
的处理能力,Intel 引入了 HT(Hyper-Threading,超线程)技术。
一个core
打开HT之后,在 OS 看来就是两个核。
由于该核只是逻辑上的概念,所以也被称为logical processor
,简称为processor
。
工具:
命令
lscpu
输出结果中的CPU(s)
字段,可以看到逻辑核
的个数。
路径/proc/cpuinfo
中的processor
字段,表示的就是一个逻辑核
的序号。该序号是从零开始的,并且是连续的。
路径/proc/cpuinfo
中的core id
字段,表示的就是一个逻辑核
的标识号。注意该标识号不一定连续,而且与硬件相关。
路径/sys/devices/system/cpu/cpu${processor_index}/topology/core_id
,表示某个逻辑核${processor_index}
的标识号。其中${processor_index}是逻辑核
序号。
SMP(Symmetric Multi-Processing) & UMA(Uniform Memory Access)
SMP 将多个CPU插槽
与一个集中的存储器相连。
在 SMP 下,所有CPU插槽
都可以访问同一个系统物理存储器,SMP系统只运行操作系统的一个拷贝。
SMP系统有时也被称为一致存储器访问(UMA)结构体系,因为处理器只能为内存的每个数据保持或共享唯一一个数值。
问题:
随着
CPU插槽
的增加,共享内存可能会导致内存访问冲突越来越厉害,且如果内存访问达到瓶颈的时候,性能就不能随之增加。
实验证明,SMP 服务器 CPU 利用率最好的情况是 2 至 4 个CPU插槽
。
NUMA(Non-Uniform Memory Access)
NUMA 中内存被平均分配在了各个CPU插槽
上。
CPU插槽
与直连的内存块形成一个 NUMA node。
NUMA node
CPU插槽
和直连的内存块合起来,称为一个 NUMA node。
NUMA distance
distance 为各个 NUMA node 之间调用资源的开销。
distance 为资源调度优化提供数据支持。
工具:
使用 numactl --hardware 可以打印 distance。
socket
间的连接和内存访问
- inter-connect
socket
之间的连接,称为 inter-connect。
- local access
CPU 访问本
socket
的内存时,称为 local access。
由于同一socket
内 CPU 和内存的物理距离最短,local access 有最短的响应时间。注意:
尽量保证 local access,其效率是最高的。
- remote access
CPU 访问其他
socket
的内存时,称为 remote access。
由于需要通过 inter-connect 通道访问,remote access 响应时间相对变慢。
注意:
remote access 比 local access 慢是由于电路板物理结构所决定的。
当需要跨 socket
访问的时候,就会相对变慢。
BIOS 层 和 OS 层 NUMA 的相互关系
- 在 BIOS 层的 NUMA 开启时,如果 OS 层的 NUMA 关闭,性能会下降。
- 在 BIOS 层的 NUMA 关闭时,无论 OS 层面的 NUMA 是否打开,都不会影响性能。
NUMA 与 SWAP 问题 (SWAP Insanity)
如果一个进程限制它只能使用自己的 NUMA node 的内存。
当自身 NUMA node 内存使用光之后,就不会去使用其他 NUMA node 的内存,而是开始使用 SWAP ,造成业务停滞。
甚至机器在没有设置 SWAP 的时候,可能会直接死机!
处理方法:
使用 numactl --interleave=all 可以取消 NUMA node 的限制。
NUMA 的性能调优建议
- 如果程序会占用大内存的,因为很有可能碰到
SWAP 问题
。
1.1. 考虑从 BIOS 层 关闭 NUMA。
1.2. 考虑从 OS 层 关闭 NUMA。
1.3. 设置 NUMA 的 内存 分配策略 为 interleave=all。
1.4. 设置 内核参数 vm.zone_reclaim_mode=0。 - 如果程序并不会占用大内存,而是要求更快的程序运行时间。
2.1. 应该选择限制只访问本 NUMA node。
工具应用
BIOS 层 的 NUMA 设置
查看 BIOS 层是否开启 NUMA
grep -i numa /var/log/dmesg
> 1. 如果为 "No NUMA configuration found"
> 则说明 NUMA 为 disable。
>
> 2. 如果不是 "No NUMA configuration found"
> 则说明 NUMA 为 enable。
修改 BIOS interleave
注意,由于 BIOS 种类繁多,请以实际情况为准。
参数路径:
BIOS: interleave
设定值:
Disable # interleave 关闭,开启 NUMA。
Enable # interleave 开启,关闭 NUMA。
OS 层 的 NUMA 设置
linux 的 NUMA 设置
- 查询 当前内核启动参数
cat /porc/cmdline
- 修改 内核启动参数
2.1. 直接修改启动参数
vim /etc/grub.conf
> 修改 kernel 或 linuxe 或 linuxefi 行
> 添加 `numa=off`,关闭 NUMA。
> 去除 `numa=off`,开启 NUMA。
>
> 例子:
> linuxefi /vmlinuz-3.10.0-123.el7.x86_64 ... numa=off
2.1. 修改默认启动参数
# 修改默认启动参数
vim /etc/default/grub
> 修改 GRUB_CMDLINE_LINUX 行
> 添加 `numa=off`,关闭 NUMA。
> 去除 `numa=off`,开启 NUMA。
>
> 例子:
> GRUB_CMDLINE_LINUX="... numa=off"
# 重新生成配置文件:
grub2-mkconfig -o /etc/grub.conf
安装 numactl 工具
yum install numactl -y
查看 CPU 信息
# NUMA 关闭时:
lscpu
> Architecture: x86_64
> CPU op-mode(s): 32-bit, 64-bit
> Byte Order: Little Endian
> CPU(s): 24 # 主板上`逻辑核`个数
> On-line CPU(s) list: 0-23
> Thread(s) per core: 1 # 每个`CPU核`的 线程个数
> Core(s) per socket: 12 # 每个`CPU插槽`的`CPU核`数
> Socket(s): 2 # 主板上`CPU插槽`个数
> NUMA node(s): 1 # 主板上`NUMA node`个数
> Vendor ID: GenuineIntel
> CPU family: 6
> Model: 62
> Model name: Intel(R) Xeon(R) CPU E5-2697 v2 @ 2.70GHz
> Stepping: 4
> CPU MHz: 1346.625
> BogoMIPS: 5406.12
> Virtualization: VT-x
> L1d cache: 32K
> L1i cache: 32K
> L2 cache: 256K
> L3 cache: 30720K
> NUMA node0 CPU(s): 0-23 # node 0 上`逻辑核`序号
# NUMA 开启时:
lscpu
> Architecture: x86_64
> CPU op-mode(s): 32-bit, 64-bit
> Byte Order: Little Endian
> CPU(s): 24 # 主板上`逻辑核`个数
> On-line CPU(s) list: 0-23
> Thread(s) per core: 1 # 每个`CPU核`的 线程个数
> Core(s) per socket: 12 # 每个`CPU插槽`的`CPU核`数
> Socket(s): 2 # 主板上`CPU插槽`个数
> NUMA node(s): 2 # 主板上`NUMA node`个数
> Vendor ID: GenuineIntel
> CPU family: 6
> Model: 62
> Model name: Intel(R) Xeon(R) CPU E5-2697 v2 @ 2.70GHz
> Stepping: 4
> CPU MHz: 1364.871
> BogoMIPS: 5405.73
> Virtualization: VT-x
> L1d cache: 32K
> L1i cache: 32K
> L2 cache: 256K
> L3 cache: 30720K
> NUMA node0 CPU(s): 0-11 # node 0 上`逻辑核`序号
> NUMA node1 CPU(s): 12-23 # node 1 上`逻辑核`序号
注意:
Thread(s) per core
为1。表示没有开启超线程
。Thread(s) per core
大于1。表示开启了超线程
。
NUMA 开启后,NUMA node
个数增多,但是因为逻辑核
总数固定不变,所以每一个NUMA node
上的逻辑核
反而减少。
查看 NUMA node 信息
# NUMA 关闭时:
numactl --hardware
> available: 1 nodes (0) # node 列表
> node 0 cpus: 0 1 .. 23 # node0 `逻辑核`列表
> node 0 size: 65523 MB # node0 总内存大小
> node 0 free: 51796 MB # node0 空闲内存大小
> node distances: # node 之间 distances 大小
> node 0 # distances 会由矩阵来表示
> 0: 10
# NUMA 开启时:
numactl --hardware
> available: 2 nodes (0-1) # node 列表
> node 0 cpus: 0 1 .. 11 # node0 `逻辑核`列表
> node 0 size: 32755 MB # node0 总内存大小
> node 0 free: 30836 MB # node0 空闲内存大小
> node 1 cpus: 12 13 .. 23 # node1 `逻辑核`列表
> node 1 size: 32768 MB # node1 总内存大小
> node 1 free: 31827 MB # node1 空闲内存大小
> node distances: # distances 会由矩阵来表示
> node 0 1
> 0: 10 20 # local access(distance = 10)
> 1: 20 10 # romote access(distance = 20)
注意:
当 NUMA 开启后,查看node distances
字段,可见 romote access(20) 是 local access(10) 的 2 倍。
查看 NUMA 绑定信息
# NUMA 关闭时:
numactl --show
> policy: default
> preferred node: current
> physcpubind: 0 1 .. 23 # `逻辑核`绑定
> cpubind: 0 # CPU 绑定
> nodebind: 0 # node 绑定
> membind: 0 # 内存绑定
# NUMA 开启时:
numactl --show
> policy: default
> preferred node: current
> physcpubind: 0 1 .. 23 # `逻辑核`绑定
> cpubind: 0 1 # CPU 绑定
> nodebind: 0 1 # node 绑定
> membind: 0 1 # 内存绑定
查看 /porc/cpuinfo
使用以下的指令可以查看 CPU 的信息。
cat /proc/cpuinfo
Linux | 描述 |
---|---|
processor | 逻辑核 序号 |
physical id | CPU插槽 识号 |
core id | 逻辑核 标识号 |
注意:
- 无论 是否开启 NUMA 所观察到的
cpuinfo
都是一样的。 core id
并不一定是连续的。该数字与硬件相关。- 需要同时考虑
physical id
和core id
,才能唯一决定出一个逻辑核
。
例子:
cpuinfo processor | cpuinfo physical id | cpuinfo core id |
---|---|---|
0 | 0 | 0 |
1 | 0 | 1 |
2 | 0 | 2 |
3 | 0 | 3 |
4 | 0 | 4 |
5 | 0 | 5 |
6 | 0 | 8 |
7 | 0 | 9 |
8 | 0 | 10 |
9 | 0 | 11 |
10 | 0 | 12 |
11 | 0 | 13 |
12 | 1 | 0 |
13 | 1 | 1 |
14 | 1 | 2 |
15 | 1 | 3 |
16 | 1 | 4 |
17 | 1 | 5 |
18 | 1 | 8 |
19 | 1 | 9 |
20 | 1 | 10 |
21 | 1 | 11 |
22 | 1 | 12 |
23 | 1 | 13 |
查看 NUMA/cpu 相关的文件系统
在 Linux 中。查看文件系统,可以观察基础的 NUMA/cpu 信息。
查看文件系统/sys/devices/system/cpu/
,可以得知逻辑核
的信息。
查看文件系统/sys/devices/system/node/
,可以得知NUMA
的信息。
相关的文件系统目录树如下:
/
+-> sys/
+-> devices/
+-> system/
+-> cpu/ # <== `逻辑核`
| +-> cpu0/
| | +-> node0 # 软链接
| | +-> topology/
| | +-> core_id
| | +-> physical_package_id
| |
| +-> cpu1/
| ...
| +-> cpu#/
|
+-> node/ # <== `NUMA node`
+-> node0/
| +-> cpu0 # 软链接
| +-> cpu1 # 软链接
| ...
| +-> cpu# # 软链接
| +-> numastat
|
+-> node1/
...
+-> node#/
- NUMA 关闭时
# 主板上所有的`逻辑核`
ll /sys/devices/system/cpu | grep cpu
> drwxr-xr-x. 9 root root 0 Jan 25 11:41 cpu0
> drwxr-xr-x. 9 root root 0 Jan 25 11:41 cpu1
> drwxr-xr-x. 9 root root 0 Jan 25 11:41 cpu2
> ...
> drwxr-xr-x. 9 root root 0 Jan 25 11:41 cpu23
# 某一个`逻辑核` 所在的 NUMA node id
ll /sys/devices/system/cpu/cpu23 | grep node
> lrwxrwxrwx. 1 root root 0 Jan 30 13:35 node0 -> ../../node/node0
# 某一个`逻辑核` 的`逻辑核`标识号
cat /sys/devices/system/cpu/cpu23/topology/core_id
> 13
# 某一个`逻辑核` 所在的`CPU插槽`标识号
cat /sys/devices/system/cpu/cpu23/topology/physical_package_id
> 1
# 主板上所有的`NUMA node`
ll /sys/devices/system/node/ | grep node
> rwxr-xr-x. 4 root root 0 Jan 25 11:41 node0
# `NUMA node0`下的 `逻辑核` 列表
ll /sys/devices/system/node/node0 | grep "cpu"
> lrwxrwxrwx. 1 root root 0 Jan 25 13:14 cpu0 -> ../../cpu/cpu0
> lrwxrwxrwx. 1 root root 0 Jan 25 13:14 cpu1 -> ../../cpu/cpu1
> lrwxrwxrwx. 1 root root 0 Jan 25 13:14 cpu2 -> ../../cpu/cpu2
> ...
> lrwxrwxrwx. 1 root root 0 Jan 25 13:14 cpu23 -> ../../cpu/cpu23
- NUMA 开启时:
# 主板上所有的`逻辑核`
ll /sys/devices/system/cpu | grep cpu
> drwxr-xr-x. 9 root root 0 Jan 30 10:11 cpu0
> drwxr-xr-x. 9 root root 0 Jan 30 10:11 cpu1
> drwxr-xr-x. 9 root root 0 Jan 30 10:11 cpu2
> ...
> drwxr-xr-x. 9 root root 0 Jan 30 10:11 cpu23
# 某一个`逻辑核` 所在的 NUMA node id
ll /sys/devices/system/cpu/cpu23 | grep node
> lrwxrwxrwx. 1 root root 0 Jan 30 13:35 node1 -> ../../node/node1
# 某一个`逻辑核` 的`逻辑核`标识号
cat /sys/devices/system/cpu/cpu23/topology/core_id
> 13
# 某一个`逻辑核` 所在的`CPU插槽`标识号
cat /sys/devices/system/cpu/cpu23/topology/physical_package_id
> 1
# 主板上所有的`NUMA node`
ll /sys/devices/system/node/ | grep node
> drwxr-xr-x. 4 root root 0 Jan 30 10:11 node0
> drwxr-xr-x. 4 root root 0 Jan 30 10:11 node1
# `NUMA node0`下的 `逻辑核` 列表
ll /sys/devices/system/node/node0 | grep "cpu"
> lrwxrwxrwx. 1 root root 0 Jan 30 11:17 cpu0 -> ../../cpu/cpu0
> lrwxrwxrwx. 1 root root 0 Jan 30 11:17 cpu2 -> ../../cpu/cpu2
> lrwxrwxrwx. 1 root root 0 Jan 30 11:17 cpu3 -> ../../cpu/cpu3
> ...
> lrwxrwxrwx. 1 root root 0 Jan 30 11:17 cpu11 -> ../../cpu/cpu11
# `NUMA node1`下的 `逻辑核` 列表
ll /sys/devices/system/node/node1 | grep "cpu"
> lrwxrwxrwx. 1 root root 0 Jan 30 11:19 cpu12 -> ../../cpu/cpu12
> lrwxrwxrwx. 1 root root 0 Jan 30 11:19 cpu13 -> ../../cpu/cpu13
> lrwxrwxrwx. 1 root root 0 Jan 30 11:19 cpu14 -> ../../cpu/cpu14
> ...
> lrwxrwxrwx. 1 root root 0 Jan 30 11:19 cpu23 -> ../../cpu/cpu23
查看 NUMA 统计信息
/sys/devices/system/node/
文件夹中记录系统中的所有内存节点的相关详细信息。
numastat 等同于 cat /sys/devices/system/node/node[0…n]/numastat。
项目 | 备注 |
---|---|
numa_hit | 打算在本节点上分配内存,最后从本节点分配的次数 |
num_miss | 打算在本节点分配内存,最后却从其他节点分配的次数 |
num_foregin | 打算在其他节点分配内存,最后却从这个节点分配的次数 |
interleave_hit | 采用interleave策略最后从本节点分配的次数 |
local_node | 本节点上的进程,在本节点上分配的次数 |
other_node | 其他节点进程,在本节点上分配的次数 |
# NUMA 关闭时:
numastat
> node0
> numa_hit 5571187
> numa_miss 0
> numa_foreign 0
> interleave_hit 53465
> local_node 5571187
> other_node 0
# NUMA 开启时:
numastat
> node0 node1
> numa_hit 1628912 1134271
> numa_miss 0 0
> numa_foreign 0 0
> interleave_hit 19496 19299
> local_node 1624846 1112228
> other_node 4066 22043
注意:
如果发现 numa_miss 数值比较高时,说明需要对分配策略进行调整。
例如将指定进程关联绑定到指定的CPU上,从而提高内存命中率。
numactl 中的 nodes 和 cpus 的表示方法
在后续的 NUMA 的 CPU 分配策略
和 NUMA 的 内存 分配策略
中需要用到以下方法,表示 node 和 cpu 的集合。
- numactl 中的 nodes 的 表示方法
项目 | 备注 |
---|---|
all | 表示所有的 node 的集合 |
same | 表示与之前设置一样 |
N,N,N ,N-N ,N,N-N ,N-N,N-N 等 | 表示 node 的集合 |
+N,N,N ,+N-N ,+N,N-N 等 | 表示相对于进程当前的设置的 node 的集合 |
!N-N 等 | 表示 非 N-N 的 node 的集合 |
!+N-N 等 | 表示 非 +N-N 的 node 的集合 |
- numactl 中的 cpus 的 表示方法
项目 | 备注 |
---|---|
all | 表示所有的 cpu 的集合 |
N,N,N ,N-N ,N,N-N ,N-N,N-N 等 | 表示 cpu 的集合 |
+N,N,N ,+N-N ,+N,N-N 等 | 表示相对于进程当前的设置的 cpu 的集合 |
!N-N 等 | 表示 非 N-N 的 cpu 的集合 |
!+N-N 等 | 表示 非 +N-N 的 cpu 的集合 |
NUMA 的 CPU 分配策略
注意:
每个进程(或线程)都会从父进程继承 NUMA CPU 分配策略,并分配有一个优先 node。
选项:
–all, -a
重置所有的 CPU 分配策略。进程可以使用所有可能的核。
–cpunodebind=nodes, -N nodes
规定进程在指定的 NUMA node 上运行。
注意一个 NUMA node 可能由多个逻辑核
组成。
–physcpubind=cpus, -C cpus
规定进程在指定的
逻辑核
上运行。
NUMA 的 内存 分配策略
选项:
–localalloc, -l
规定进程从当前 node 上请求分配内存。
–membind=nodes, -m nodes
规定进程只能从指定的 nodes 上请求分配内存。
–preferred=node
指定了
一个
推荐的 node 来获取内存,如果失败,则尝试别的 node。
–interleave=nodes, -i nodes
规定进程从指定的 nodes 上,以 round robin 算法
交织地
请求分配内存。
NUMA 的 分配策略例子
# Unset default cpuset awareness.
numactl --all
# Run ${process} on node 0 with memory allocated on node 0 and 1.
numactl --cpunodebind=0 --membind=0,1 ${process} ${process_arguments}
# Run ${process} on cpus 0-4 and 8-12 of the current cpuset.
numactl --physcpubind=+0-4,8-12 ${process} ${process_arguments}
# Run ${process} with its memory interleaved on all CPUs
numactl --interleave=all ${process} ${process_arguments}
# Run process as above, but with an option (-l) that would be confused with a numactl option.
numactl --cpunodebind=0 --membind=0,1 -- ${process} -l
# Run ${network-server} on the node of network device eth0 with its memory also in the same node.
numactl --cpunodebind=netdev:eth0 --membind=netdev:eth0 ${network-server}
# Set preferred node 1 and show the resulting state.
numactl --preferred=1 numactl --show
# Interleave all of the sysv shared memory region specified by /tmp/shmkey over all nodes.
numactl --interleave=all --shm /tmp/shmkey
# Place a tmpfs file on 2 nodes.
numactl --membind=2 dd if=/dev/zero of=/dev/shm/A bs=1M count=1024
numactl --membind=3 dd if=/dev/zero of=/dev/shm/A seek=1024 bs=1M count=1024
# Reset the policy for the shared memory file file to the default localalloc policy.
numactl --localalloc /dev/shm/file
vm 内核参数
overcommit_memory
Memory Overcommit 表示:在内存申请的时候,承诺给进程的内存大小,可以超过了实际可用的内存。
因为 UNIX/Linux 的算法中,物理内存页的分配发生在使用的瞬间,而不是在申请的时候。
而且往往实际使用到的内存比申请的少,所以 Linux 允许 memory overcommit。一旦出现内存不足(Out-Of-Memory),Linux 有两种方法处理:
- 使用 OOM killer,不断挑选进程出来杀死,直到腾出内存。
- 通过设置内核参数 vm.panic_on_oom,自动重启系统。
因为以上的 OOM 机制,都有可能造成业务中断。
所以自从 Linux 2.6 之后,可以通过内核参数 vm.overcommit_memory 禁止 memory overcommit。
参数路径:
/proc/sys/vm/overcommit_memory
设定值:
0
(默认值)
表示内核将检查是否有足够的可用内存供应用进程使用。
- 如果有足够的可用内存,内存申请允许;
- 否则,内存申请失败,并把错误返回给应用进程。
1
表示内核允许分配所有的物理内存,而不管当前的内存状态如何。
2
表示内核不允许分配超过所有物理内存和交换空间总和的内存
例子:
echo 0 > /proc/sys/vm/overcommit_memory
# 写入到 `/etc/sysctl.conf`
sysctl -w vm.overcommit_memory=0
zone_reclaim_mode
内存回收策略
参数路径:
/proc/sys/vm/zone_reclaim_mode
设定值:
0
(默认值)
当某个节点可用内存不足时,系统会从其他节点分配内存。
1
当某个节点可用内存不足时,系统会从本地节点回收 cache 内存
例子:
# 多数时候 cache 对性能很重要,所以 0 是一个更好的选择。
echo 0 > /proc/sys/vm/zone_reclaim_mode
# 写入到 `/etc/sysctl.conf`
sysctl -w vm.zone_reclaim_mode=0
实例分析
mongodb 在 NUMA 的问题
mongodb日志显示如下:
WARNING: You are running on a NUMA machine.
We suggest launching mongod like this to avoid performance problems:
numactl –interleave=all mongod [other options]
解决方案:
- 修改 NUAM 内存分配策略为
交织分配
。
在原启动命令前面加 numactl –interleave=all
numactl --interleave=all ${MONGODB_HOME}/bin/mongod --config conf/mongodb.conf
2.修改内核内存回收策略为 从其他节点分配内存
。
echo 0 > /proc/sys/vm/zone_reclaim_mode ;
echo "vm.zone_reclaim_mode = 0" >> /etc/sysctl.conf;
极端解决方案:
- 从 BIOS 层关闭 NUMA。
- 从 OS 层关闭 NUMA。
参考资料
NUMA的取舍与优化设置
DPDK内存大页在NUMA架构重分配的问题
DPDK优化点和NUMA架构
直接内存访问 DMA
Node Interleaving: Enable or Disable?
SMP、NUMA、MPP体系结构介绍
SWAP的罪与罚
玩转cpu-topology