内存分配中的NUMA
简介
numa是一种关于多个cpu如何访问内存的架构模型,现在的cpu基本都是numa架构,linux内核2.5开始支持numa。
内存访问分为两种体系结构:一致性内存访问(UMA)和非一致性内存访问(NUMA)。NUMA指CPU对不同内存单元的访问时间可能不一样,因而这些物理内存被划分为几个节点,每个节点里的内存访问时间一致,NUMA体系结构主要存在大型机器、alpha等,嵌入式的基本都是UMA。UMA也使用了节点概念,只是永远都只有1个节点。
NUMA(Non-Uniform Memory Access)是相对UMA来说的,两者都是CPU的设计架构,早期CPU设计为UMA结构,如下图所示:
为了缓解多核CPU读取同一块内存所遇到的通道瓶颈问题,芯片工程师又设计了NUMA结构,如下图所示:
如图所示,NUMA使得每个CPU都有自己专属的内存区域。 NUMA 服务器的基本特征是 Linux 将系统的硬件资源划分为多个节点(Node),每个节点上有单独的 CPU、内存和 I/O 槽口等。CPU 访问自身 Node 内存的速度将远远高于访问远地内存(系统内其它节点的内存)的速度,这也是非一致内存访问 NUMA 的由来。
只有当CPU访问自身直接attach内存对应的物理地址时,才会有较短的响应时间(Local Access)。而如果需要访问其他CPU attach的内存的数据时,就需要通过inter-connect通道访问,响应时间就相比之前变慢了(Remote Access)。NUMA这种特性可能会导致CPU内存使用不均衡,部分CPU的local内存不够使用,频繁需要回收,进而可能发生大量swap,系统响应延迟会严重抖动。而与此同时其他部分CPU的local内存可能都很空闲。这就会产生一种怪现象:使用free命令查看当前系统还有部分空闲物理内存,系统却不断发生swap,导致某些应用性能急剧下降。
NUMA这种架构可以很好解决UMA的问题,即不同CPU有专属内存区,为了实现CPU之间的”内存隔离”,还需要软件层面两点支持:
- 内存分配需要在请求线程当前所处CPU的专属内存区域进行分配。如果分配到其他CPU专属内存区,势必隔离性会受到一定影响,并且跨越总线的内存访问性能必然会有一定程度降低。
- 一旦local内存(专属内存)不够用,优先淘汰local内存中的内存页,而不是去查看远程内存区是否会有空闲内存借用。
在NUMA架构(多CPU)下,每个node(物理CPU)都有自己的本地内存,在分析内存的时候需要分析每个node的情况:
/proc/sys/vm/zone_reclaim_mode设置NUMA本地内存的回收策略,当node本地内存不足时,默认可以从其它node寻找空闲内存,也可以从本地回收。
numa架构简单点儿说就是,一个物理cpu(一般包含多个逻辑cpu或者说多个核心)构成一个node,这个node不仅包括cpu,还包括一组内存插槽,也就是说一个物理cpu以及一块内存构成了一个node。每个cpu可以访问自己node下的内存,也可以访问其他node的内存,但是访问速度是不一样的,自己node下的更快。
numactl --hardware
命令可以查看node状况。通过numactl启动程序,可以指定node绑定规则和内存使用规则。可以通过cpunodebind参数使进程使用固定node上的cpu,使用localalloc参数指定进程只使用cpu所在node上分配的内存。如果分配的node上的内存足够用,这样可以减少抖动,提供性能。如果内存紧张,则应该使用interleave参数,否则进程会因为只能使用部分内存而out of memory或者使用swap区造成性能下降。
NUMA的内存分配策略有localalloc、preferred、membind、interleave。
- localalloc规定进程从当前node上请求分配内存;
- preferred比较宽松地指定了一个推荐的node来获取内存,如果被推荐的node上没有足够内存,进程可以尝试别的node。
- membind可以指定若干个node,进程只能从这些指定的node上请求分配内存。
- interleave规定进程从指定的若干个node上以RR(Round Robin 轮询调度)算法交织地请求分配内存。
在目前主流服务器上,一般都拥有多个CPU节点,而一个CPU节点,会拥有10~20个物理核心。如果一个服务器拥有多个CPU节点,那么我们也称这个服务器拥有多个CPU Socket,Socket简单理解,就是一个CPU节点,如下图所示:
如图为4核心CPU的架构,其中,CPU核心1、2在同一个Socket中,CPU核心3、4在另外一个Socket中。Socket之间,通过CPU总线来连接,每个Socket控制一块内存。
单节点物理CPU输出
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 | [root@oracle-rac1 ~]# lscpu Architecture: x86_64 CPU op-mode(s): 32-bit, 64-bit Byte Order: Little Endian CPU(s): 8 On-line CPU(s) list: 0-7 Thread(s) per core: 2 Core(s) per socket: 4 Socket(s): 1 NUMA node(s): 1 Vendor ID: GenuineIntel CPU family: 6 Model: 85 Model name: Intel(R) Xeon(R) Gold 6278C CPU @ 2.60GHz Stepping: 7 CPU MHz: 2600.000 BogoMIPS: 5200.00 Hypervisor vendor: KVM Virtualization type: full L1d cache: 32K L1i cache: 32K L2 cache: 1024K L3 cache: 36608K NUMA node0 CPU(s): 0-7 Flags: fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 ss ht syscall nx pdpe1gb rdtscp lm constant_tsc rep_good nopl xtopology nonstop_tsc eagerfpu pni pclmulqdq ssse3 fma cx16 pcid sse4_1 sse4_2 x2apic movbe popcnt tsc_deadline_timer aes xsave avx f16c rdrand hypervisor lahf_lm abm 3dnowprefetch invpcid_single ssbd ibrs ibpb stibp ibrs_enhanced fsgsbase tsc_adjust bmi1 hle avx2 smep bmi2 erms invpcid rtm mpx avx512f avx512dq rdseed adx smap clflushopt clwb avx512cd avx512bw avx512vl xsaveopt xsavec xgetbv1 arat avx512_vnni md_clear spec_ctrl intel_stibp flush_l1d arch_capabilities [root@oracle-rac1 ~]# [root@oracle-rac1 ~]# lscpu -e CPU NODE SOCKET CORE L1d:L1i:L2:L3 ONLINE 0 0 0 0 0:0:0:0 yes 1 0 0 0 0:0:0:0 yes 2 0 0 1 1:1:1:0 yes 3 0 0 1 1:1:1:0 yes 4 0 0 2 2:2:2:0 yes 5 0 0 2 2:2:2:0 yes 6 0 0 3 3:3:3:0 yes 7 0 0 3 3:3:3:0 yes |
多节点物理CPU输出
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 | [root ~]# 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: 2 Core(s) per socket: 6 Socket(s): 2 NUMA node(s): 2 Vendor ID: GenuineIntel CPU family: 6 Model: 62 Model name: Intel(R) Xeon(R) CPU E5-2620 v2 @ 2.10GHz Stepping: 4 CPU MHz: 2399.926 CPU max MHz: 2600.0000 CPU min MHz: 1200.0000 BogoMIPS: 4190.02 Virtualization: VT-x L1d cache: 32K L1i cache: 32K L2 cache: 256K L3 cache: 15360K NUMA node0 CPU(s): 0-5,12-17 NUMA node1 CPU(s): 6-11,18-23 |
重点关注:
CPU(s): 24
Socket(s): 2
Thread(s) per core: 2 Core(s) per socket: 6
NUMA node(s): 2
NUMA node0 CPU(s): 0-5,12-17 NUMA node1 CPU(s): 6-11,18-23
使用lscpu查看当前服务器的CPU情况,可以看出:
1、当前服务器有2个CPU处理器(2个物理CPU),Sockets=2
2、每个socket上有6个物理核心,Cores per socket=6
3、每个物理核心有2个逻辑线程(即逻辑CPU),Threads per core=2
4、累计2*6*2
=24个逻辑线程(即逻辑CPU),其中
NUMA node0的CPU核编号是0~5,12~17
NUMA node1的CPU核编号是6~11,18~23
5、上述NUMA编号中,0和12、1和13、...5和17,分别为一个物理核心上的2个逻辑线程。
画图更容易理解:
再给出一个华为云裸金属上的cpu架构,如下所示:
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 | [root@dg ~]# lscpu Architecture: x86_64 CPU op-mode(s): 32-bit, 64-bit Byte Order: Little Endian CPU(s): 56 On-line CPU(s) list: 0-55 Thread(s) per core: 2 Core(s) per socket: 14 Socket(s): 2 NUMA node(s): 2 Vendor ID: GenuineIntel CPU family: 6 Model: 85 Model name: Intel(R) Xeon(R) Gold 5120 CPU @ 2.20GHz Stepping: 4 CPU MHz: 2200.000 BogoMIPS: 4405.34 Virtualization: VT-x L1d cache: 32K L1i cache: 32K L2 cache: 1024K L3 cache: 19712K NUMA node0 CPU(s): 0-13,28-41 NUMA node1 CPU(s): 14-27,42-55 [root@dg ~]# lscpu -e CPU NODE SOCKET CORE L1d:L1i:L2:L3 ONLINE 0 0 0 0 0:0:0:0 yes 1 0 0 1 1:1:1:0 yes 2 0 0 2 2:2:2:0 yes 3 0 0 3 3:3:3:0 yes 4 0 0 4 4:4:4:0 yes 5 0 0 5 5:5:5:0 yes 6 0 0 6 6:6:6:0 yes 7 0 0 7 7:7:7:0 yes 8 0 0 8 8:8:8:0 yes 9 0 0 9 9:9:9:0 yes 10 0 0 10 10:10:10:0 yes 11 0 0 11 11:11:11:0 yes 12 0 0 12 12:12:12:0 yes 13 0 0 13 13:13:13:0 yes 14 1 1 14 14:14:14:1 yes 15 1 1 15 15:15:15:1 yes 16 1 1 16 16:16:16:1 yes 17 1 1 17 17:17:17:1 yes 18 1 1 18 18:18:18:1 yes 19 1 1 19 19:19:19:1 yes 20 1 1 20 20:20:20:1 yes 21 1 1 21 21:21:21:1 yes 22 1 1 22 22:22:22:1 yes 23 1 1 23 23:23:23:1 yes 24 1 1 24 24:24:24:1 yes 25 1 1 25 25:25:25:1 yes 26 1 1 26 26:26:26:1 yes 27 1 1 27 27:27:27:1 yes 28 0 0 0 0:0:0:0 yes 29 0 0 1 1:1:1:0 yes 30 0 0 2 2:2:2:0 yes 31 0 0 3 3:3:3:0 yes 32 0 0 4 4:4:4:0 yes 33 0 0 5 5:5:5:0 yes 34 0 0 6 6:6:6:0 yes 35 0 0 7 7:7:7:0 yes 36 0 0 8 8:8:8:0 yes 37 0 0 9 9:9:9:0 yes 38 0 0 10 10:10:10:0 yes 39 0 0 11 11:11:11:0 yes 40 0 0 12 12:12:12:0 yes 41 0 0 13 13:13:13:0 yes 42 1 1 14 14:14:14:1 yes 43 1 1 15 15:15:15:1 yes 44 1 1 16 16:16:16:1 yes 45 1 1 17 17:17:17:1 yes 46 1 1 18 18:18:18:1 yes 47 1 1 19 19:19:19:1 yes 48 1 1 20 20:20:20:1 yes 49 1 1 21 21:21:21:1 yes 50 1 1 22 22:22:22:1 yes 51 1 1 23 23:23:23:1 yes 52 1 1 24 24:24:24:1 yes 53 1 1 25 25:25:25:1 yes 54 1 1 26 26:26:26:1 yes 55 1 1 27 27:27:27:1 yes |
大家可以自己分析。
为什么要关掉NUMA?
对于NUMA架构而言,每个CPU访问其对应的RAM时速度是很快的,但是如果需要访问其它node的RAM,那么就需要通过inter-connect通道访问,速度自然会有降低。但是你可能会纳闷就算这个速度再慢那也比直接从磁盘去访问快吧?
事实的确如此,因为NUMA的主要危害并不在此。真正让大家选择弃用NUMA的原因是常常会出现一个CPU NODE很忙,而其它的CPU NODE都十分空闲。因为在NUMA架构中,默认的内存分配方案就是:优先尝试在请求线程当前所处的CPU的Local内存上分配空间。如果local内存不足,优先淘汰local内存中无用的Page。
所以,对于小内存应用来讲,NUMA所带来的这种问题并不突出,相反,local内存所带来的性能提升相当可观。但是对于数据库这类内存大户来说,NUMA默认策略所带来的稳定性隐患是不可接受的。因此数据库们都强烈要求对NUMA的默认策略进行改进,有两个方面可以进行改进:
- 将内存分配策略由默认的亲和模式改为interleave模式,即会将内存page打散分配到不同的CPU zone中。对于MongoDB来说,在启动的时候就会提示使用interleave内存分配策略.
- 改进NUMA的内存回收策略,即vm.zone_reclaim_mode。这个参数可以取值0/1/3/4。其中0表示在local内存不够用的情况下可以去其他的内存区域分配内存;1表示在local内存不够用的情况下本地先回收再分配;3表示本地回收尽可能先回收文件缓存对象;4表示本地回收优先使用swap回收匿名内存。由此可见,将其设为0可以降低swap发生的概率。
既然NUMA的问题这么多,那么为什么还会出现呢?其实NUMA本身并没有问题,甚至这是多CPU发展的必然趋势,而真正导致出问题的是因为操作系统内存访问不均匀。
Linux 识别到 NUMA 架构后,默认的内存分配方案是:优先从本地分配内存。如果本地内存不足,优先淘汰本地内存中无用的内存。使内存页尽可能地和调用线程处在同一个 node。这种默认策略在不需要分配大量内存的应用上一般没什么问题。但是对于数据库这种可能分配超过一个 NUMA node 的内存量的应用来说,可能会引起一些奇怪的性能问题。
那么对于Oracle、pg这类数据库应用为什么都要关掉呢?究其原因还是因为数据库这种大规模内存使用的应用场景,并且外部的连接大多是随机的,这往往会导致出现大量的Remote Access,当我们是用NUMA避免了BUS的性能瓶颈,而Remote Access又成了新的瓶颈,有点拆了东墙补西墙的感觉。
关闭NUMA
关闭NUMA建议方案:
1.在BIOS设置层面关闭NUMA,缺点是需要重启OS。BIOS:interleave = Disable / Enable
2.修改GRUB配置文件,缺点也是要重启OS。
a、在/etc/grub.conf的GRUB_CMDLINE_LINUX行添加numa=off
,如下所示:
1 | GRUB_CMDLINE_LINUX="crashkernel=auto numa=off rd.lvm.lv=centos/root rd.lvm.lv=centos/swap rhgb quiet" |
b、重新生成 /etc/grub2.cfg 配置文件:
1 | grub2-mkconfig -o /etc/grub2.cfg |
c、重启操作系统
d、确认
1 | cat /proc/cmdline |
设置内核参数方法:
echo 0 > /proc/sys/vm/zone_reclaim_mode
,或sysctl -w vm.zone_reclaim_mode=0
,或- 编辑/etc/sysctl.conf文件,加入
vm.zone_reclaim_mode=0
检查OS是否开启NUMA
1 2 3 4 | yum install -y numactl numactl --hardware numactl --show |
available: 1 nodes (0) #如果是2或多个nodes就说明numa没关掉,如下所示:
- 执行
numactl --hardware
可以查看硬件对 NUMA 的支持信息:
1 2 3 4 5 6 7 8 9 10 11 12 | # numactl --hardware available: 2 nodes (0-1) 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 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 0 size: 96920 MB node 0 free: 2951 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 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 1 size: 98304 MB node 1 free: 33 MB node distances: node 0 1 0: 10 21 1: 21 10 |
- CPU 被分成 node 0 和 node 1 两组(这台机器有两个 CPU Socket)。
- 每组 CPU 分配到 96 GB 的内存(这台机器总共有 192GB 内存)。
- node distances 是一个二维矩阵,
node[i][j]
表示 node i 访问 node j 的内存的相对距离。比如 node 0 访问 node 0 的内存的距离是 10,而 node 0 访问 node 1 的内存的距离是 21。
- 执行
numactl --show
显示当前的 NUMA 设置:
1 2 3 4 5 6 7 | # numactl --show 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 nodebind: 0 1 membind: 0 1 |
- numactl 命令还有几个重要选项:
--cpubind=0
: 绑定到 node 0 的 CPU 上执行。--membind=1
: 只在 node 1 上分配内存。--interleave=nodes
:nodes 可以是 all、N,N,N 或 N-N,表示在 nodes 上轮循(round robin)分配内存。--physcpubind=cpus
:cpus 是 /proc/cpuinfo 中的 processor(超线程) 字段,cpus 的格式与 --interleave=nodes 一样,表示绑定到 cpus 上运行。--preferred=1
: 优先考虑从 node 1 上分配内存。
NUMA分配
NUMA Node 分配
在这台机器中,有两个 NUMA Node,每个节点管理16GB内存。
对于以下案例:
- CPU 0上的空闲内存1.8G. CPU1上的空闲内存38G。严重不均衡
- 当CPU 0上需要申请大于1.8G内存时,必然需要SWAP
- 由于服务器硬件、系统设置不当,没有关闭NUMA,导致发生SWAP
当发现numa_miss数值比较高时,说明需要对分配策略进行调整。例如将指定进程关联绑定到指定的CPU上,从而提高内存命中率。
NUMA Node 绑定
Node 和 Node 之间进行通信的代价是不等的,同样是 Remote 节点,其代价可能不一样,这个信息在 node distances 中以一个矩阵的方式展现。
我们可以将一个进程绑定在某个 CPU 或 NUMA Node 的内存上执行,如上图所示。
NUMA 状态
说明:
numa_hit—命中的,也就是为这个节点成功分配本地内存访问的内存大小
numa_miss—把内存访问分配到另一个node节点的内存大小,这个值和另一个node的numa_foreign相对应。
numa_foreign–另一个Node访问我的内存大小,与对方node的numa_miss相对应
local_node----这个节点的进程成功在这个节点上分配内存访问的大小
other_node----这个节点的进程 在其它节点上分配的内存访问大小
很明显,miss值和foreign值越高,就要考虑绑定的问题。
MySQL建议关闭NUMA
升级MySQL版本到5.6.27及以后,新增了参数 innodb_numa_interleave,只需要重启mysqld实例,无需重启OS,推荐此方案。
配置 innodb_numa_interleave 参数,将其设置为ON
lscpu命令
描述:
此命令用来显示cpu的相关信息
lscpu从sysfs和/proc/cpuinfo收集cpu体系结构信息,命令的输出比较易读
命令输出的信息包含cpu数量,线程,核数,套接字和Nom-Uniform Memeor Access(NUMA),缓存等
不是所有的列都支持所有的架构,如果指定了不支持的列,那么lscpu将打印列,但不显示数据
语法:
1 2 | lscpu [-a|-b|-c] [-x] [-s directory] [-e [=list]|-p [=list]] lscpu -h|-V |
参数选项:
-a, –all: 包含上线和下线的cpu的数量,此选项只能与选项e或-p一起指定
-b, –online: 只显示出上线的cpu数量,此选项只能与选项e或者-p一起指定
-c, –offline: 只显示出离线的cpu数量,此选项只能与选项e或者-p一起指定
-e, –extended [=list]: 以人性化的格式显示cpu信息,如果list参数省略,输出所有可用数据的列,在指定了list参数时,选项的字符串、等号(=)和列表必须不包含任何空格或其他空白。比如:’-e=cpu,node’ or ’–extended=cpu,node’
-h, –help:帮助
-p, –parse [=list]: 优化命令输出,便于分析.如果省略list,则命令的输出与早期版本的lscpu兼容,兼容格式以两个逗号用于分隔cpu缓存列,如果没有发现cpu缓存,则省略缓存列,如果使用list参数,则缓存列以冒号(:)分隔。在指定了list参数时,选项的字符串、等号(=)和列表必须不包含空格或者其它空白。比如:’-e=cpu,node’ or ’–extended=cpu,node’
-s, –sysroot directory: 为一个Linux实例收集CPU数据,而不是发出lscpu命令的实例。指定的目录是要检查Linux实例的系统根
-x, –hex:使用十六进制来表示cpu集合,默认情况是打印列表格式的集合(例如:0,1)
显示格式:
- Architecture: #架构
- CPU(s): #逻辑cpu颗数
- Thread(s) per core: #每个核心线程
- Core(s) per socket: #每个cpu插槽核数/每个物理cpu核数
- CPU socket(s): #cpu插槽数 ,物理CPU个数
- Vendor ID: #cpu厂商ID
- CPU family: #cpu系列
- Model: #型号
- Stepping: #步进
- CPU MHz: #cpu主频
- Virtualization: #cpu支持的虚拟化技术
- L1d cache: #一级缓存(google了下,这具体表示表示cpu的L1数据缓存)
- L1i cache: #一级缓存(具体为L1指令缓存)
- L2 cache: #二级缓存
CPU基本概念
请参考:https://www.xmmup.com/wulicpuluojicpucpuhexincpuxianchengdeng.html