内存分配中的NUMA

0    1066    8

Tags:

👉 本文共约6170个字,系统预计阅读时间或需24分钟。

简介

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结构,如下图所示:

img

为了缓解多核CPU读取同一块内存所遇到的通道瓶颈问题,芯片工程师又设计了NUMA结构,如下图所示:

img

如图所示,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之间的”内存隔离”,还需要软件层面两点支持:

  1. 内存分配需要在请求线程当前所处CPU的专属内存区域进行分配。如果分配到其他CPU专属内存区,势必隔离性会受到一定影响,并且跨越总线的内存访问性能必然会有一定程度降低。
  2. 一旦local内存(专属内存)不够用,优先淘汰local内存中的内存页,而不是去查看远程内存区是否会有空闲内存借用。

在NUMA架构(多CPU)下,每个node(物理CPU)都有自己的本地内存,在分析内存的时候需要分析每个node的情况:

内存分配中的NUMA

/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节点,如下图所示:

内存分配中的NUMA

如图为4核心CPU的架构,其中,CPU核心1、2在同一个Socket中,CPU核心3、4在另外一个Socket中。Socket之间,通过CPU总线来连接,每个Socket控制一块内存。

单节点物理CPU输出

多节点物理CPU输出

重点关注:

  • 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个逻辑线程。

画图更容易理解:

内存分配中的NUMA

再给出一个华为云裸金属上的cpu架构,如下所示:

大家可以自己分析。

为什么要关掉NUMA?

对于NUMA架构而言,每个CPU访问其对应的RAM时速度是很快的,但是如果需要访问其它node的RAM,那么就需要通过inter-connect通道访问,速度自然会有降低。但是你可能会纳闷就算这个速度再慢那也比直接从磁盘去访问快吧?

事实的确如此,因为NUMA的主要危害并不在此。真正让大家选择弃用NUMA的原因是常常会出现一个CPU NODE很忙,而其它的CPU NODE都十分空闲。因为在NUMA架构中,默认的内存分配方案就是:优先尝试在请求线程当前所处的CPU的Local内存上分配空间。如果local内存不足,优先淘汰local内存中无用的Page。

所以,对于小内存应用来讲,NUMA所带来的这种问题并不突出,相反,local内存所带来的性能提升相当可观。但是对于数据库这类内存大户来说,NUMA默认策略所带来的稳定性隐患是不可接受的。因此数据库们都强烈要求对NUMA的默认策略进行改进,有两个方面可以进行改进:

  1. 将内存分配策略由默认的亲和模式改为interleave模式,即会将内存page打散分配到不同的CPU zone中。对于MongoDB来说,在启动的时候就会提示使用interleave内存分配策略.
  2. 改进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,如下所示:

本人提供Oracle、MySQL、PG等数据库的培训和考证业务,私聊QQ646634621或微信db_bao,谢谢!

b、重新生成 /etc/grub2.cfg 配置文件:

c、重启操作系统

d、确认

设置内核参数方法:

  • 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

内存分配中的NUMA

available: 1 nodes (0) #如果是2或多个nodes就说明numa没关掉,如下所示:

  • 执行 numactl --hardware 可以查看硬件对 NUMA 的支持信息:

  1. CPU 被分成 node 0 和 node 1 两组(这台机器有两个 CPU Socket)。
  2. 每组 CPU 分配到 96 GB 的内存(这台机器总共有 192GB 内存)。
  3. node distances 是一个二维矩阵,node[i][j] 表示 node i 访问 node j 的内存的相对距离。比如 node 0 访问 node 0 的内存的距离是 10,而 node 0 访问 node 1 的内存的距离是 21。
  • 执行 numactl --show 显示当前的 NUMA 设置:

  • numactl 命令还有几个重要选项:
  1. --cpubind=0: 绑定到 node 0 的 CPU 上执行。
  2. --membind=1: 只在 node 1 上分配内存。
  3. --interleave=nodes:nodes 可以是 all、N,N,N 或 N-N,表示在 nodes 上轮循(round robin)分配内存。
  4. --physcpubind=cpus:cpus 是 /proc/cpuinfo 中的 processor(超线程) 字段,cpus 的格式与 --interleave=nodes 一样,表示绑定到 cpus 上运行。
  5. --preferred=1: 优先考虑从 node 1 上分配内存。

NUMA分配

NUMA Node 分配

内存分配中的NUMA

在这台机器中,有两个 NUMA Node,每个节点管理16GB内存。

对于以下案例:

image-20210804164312924

  • CPU 0上的空闲内存1.8G. CPU1上的空闲内存38G。严重不均衡
  • 当CPU 0上需要申请大于1.8G内存时,必然需要SWAP
  • 由于服务器硬件、系统设置不当,没有关闭NUMA,导致发生SWAP

img

当发现numa_miss数值比较高时,说明需要对分配策略进行调整。例如将指定进程关联绑定到指定的CPU上,从而提高内存命中率。

NUMA Node 绑定

Node 和 Node 之间进行通信的代价是不等的,同样是 Remote 节点,其代价可能不一样,这个信息在 node distances 中以一个矩阵的方式展现。

内存分配中的NUMA

我们可以将一个进程绑定在某个 CPU 或 NUMA Node 的内存上执行,如上图所示。

NUMA 状态

img

说明:

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将打印列,但不显示数据
语法:

参数选项:
  -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

标签:

头像

小麦苗

学习或考证,均可联系麦老师,请加微信db_bao或QQ646634621

您可能还喜欢...

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注

14 + 16 =

 

嘿,我是小麦,需要帮助随时找我哦
  • 18509239930
  • 个人微信

  • 麦老师QQ聊天
  • 个人邮箱
  • 点击加入QQ群
  • 个人微店

  • 回到顶部
返回顶部