NUMA基本概念
NUMA
1 概述
NUMA(Non-Uniform Memory Access),非一致性内存访问,是一种关于多个CPU如何访问内存的架构模型。在早期计算机系统中,CPU是这样访问内存的:

在这个架构中,所有的CPU都是通过一条总线来访问内存,我们把这种架构叫做SMP架构(Symmetric Multi-Processor),也就是对此多处理器结构。然后SMP架构有下面4个特点:
- CPU与CPU以及CPU内存都是通过一条总线连接
- CPU都是平等的,没有主从关系
- 所有硬件资源都是共享的,即每个CPU都能访问到任何内存和外设
- 内存是统一的结构和统一寻址(UMA,Uniform Memory Architecture)
SMP架构在CPU核数不多的情况下,问题不明显。有实验证明,SMP服务器CPU利用率最好的情况是2至4个CPU:

但是随着CPU多核技术的发展,一颗物理CPU中集成了越来越多的core。导致SMP架构的性能瓶颈越来越明显,因为所有的处理器都通过一条总线连接。因此随着处理器的增加,系统总线成为了系统瓶颈,另外,处理器和内存之间通信延时也较大。
为了解决SMP架构下不断增多的CPU core导致性能瓶颈,NUMA架构应运而生。NUMA调整了CPU和内存的布局及访问关系。具体示意图如下:

在NUMA架构中,将CPU划分到多个NUMA Node中,每个Node都有自己独立的内存空间和PCIe总线系统。各个CPU间通过QPI总线进行互通。
CPU访问不同类型节点内存的速度是不相同的,访问本地节点速度最快,访问远端节点速度最慢。即访问速度与节点距离有关,距离越远,访问速度越慢。所以叫做非一致性内存访问。这个访问内存的距离我们称之为Node Distance。
虽然NUMA很好的解决了SMP架构下CPU大量扩展带来的性能瓶颈问题,但是其自身也存在不足,当Node节点本地内存不足时,需要跨节点访问内存,节点间的访问速度慢,从而也会带来性能下降。所以,我们在编写应用程序时,要充分利用NUMA系统的这个特点,尽量减少不同CPU模块间交互,避免远程访问资源,如果应用程序能有办法固定在一个CPU模块里,那么应用的性能将会有很大的提升。
2 NUMA架构下的CPU和内存分布

在Linux系统中,可以查看NUMA架构下CPU和内存分布情况。不过在这之前,先搞清几个概念:
- Socket:表示一颗物理CPU的封装(物理CPU插槽),简称插槽。为了避免逻辑处理器和物理处理器混淆,Intel将物理处理器称为插槽,Socket表示可以看到的真实CPU内核
- Core:物理CPU封装内的独立一组程序执行的硬件单元,比如寄存器、计算单元等。core表示在同一个物理内核 中逻辑层面的内核。同一个物理CPU的多个Core,有自己独立的L1和L2 Cache,共享L3 Cache
- Thread:使用超线程技术虚拟出来的逻辑core,需要CPU支持。为了方便区分,逻辑core一般被写作Processor。在具有Intel超线程技术的处理器上,每个内核可以具有两个逻辑处理单元。这两个逻辑处理单元共享大多数内核资源(比如缓存和功能单元)。此类逻辑处理器通常称为Thread。超线程可以在一个逻辑内核等待执行的间隔(等待从cache或内存中获取下一条指令),把时间片分配到另一个逻辑内核。在这两个逻辑内核之间切回,让应用程序感知不到这个间隔,误认为自己是独占了一个内核。对于每个逻辑线程,拥有完整独立的寄存器集合和本地中断逻辑,共享执行单元和L1、L2、L3 Cache。超线程技术可以带来20%~30%的性能提升。
- Node:即NUMA Node,包含有若干个CPU core的组。
如上图所示NUMA架构将CPU和内存划分到不同的NUMA node中,CPU访问自己的所属node内的内存时,通过内部的内存总线就可以直接访问。但是,访问其它node的内存时,则需要通过node间的互联总线(如QPI总线,Quick Path Interconnect)连接到目标node的内存总线,才能访问到。这样一来,一方面不同node的内存可以并行访问,提高了系统内存的总访问带宽,另一方面,CPU访问自己node内的内存比跨node访问内存时性能要高。在NUMA架构中,表示CPU访问内存的性能差异有一个专门的术语:distance。distance越大,表示CPU距离内存越远,访问成本越高,性能越低,延时越大。
2.1 服务器跨node访问内存场景
- 本地节点内存不足,或内存分配策略未绑定节点,导致CPU不得不访问其它节点内存
当某个节点的CPU经常占用内存超过其本地内存上限时,服务器回自动将超出部分的内存分配到其它NUMA节点的“远程内存”中,此时该CPU访问这部分内存就会触发跨NUMA.
比如:服务器有2个NUMA节点(节点0:CPU 0~7+128GB本地内存;节点1:CPU8~15+128GB本地内存)。在节点0的CPU0上运行一个需要128GB内存的数据库进程,在节点0上的128GB本地内存耗尽后,剩余22GB内存会被分配到节点1的本地内存中。此时CPU0访问这22GB内存时,必须跨节点通过NUMA互联总线(如 Intel UPI、AMD Infinity Fabric)访问节点1的内存,形成跨NUMA
Linux/Unix系统默认的内存分配策略(如default策略)是“优先分配本地内存,本地内存不足时分配远程内存”;但如果未配置“强绑定策略”(如membind),即使本地内存充足,也可能因内核调度逻辑导致内存被分配到其它节点
运维未通过工具(如numactl、taskset)绑定进程的内存节点,仅绑定了CPU核心。例如:将进程绑定到节点0的CPU0,但内存分配未限制为节点0,内核可能因 “内存碎片优化” 将部分内存分配到节点1,导致跨NUMA。
虚拟化场景中,虚拟机(VM)的内存未与物理NUMA节点对齐:若VM的vCPU绑定到节点0,但VM的内存被KVM分配到节点1的物理内存,vCPU访问内存时会跨NUMA。
当多个NUMA节点的CPU需要访问同一块共享内存(如进程间的shm、分布式缓存的共享段)时,若共享内存被分配到某一节点(如节点0),则其它节点的CPU访问该共享内存时,必然触发跨NUMA。
节点0的CPU0创建一块10GB的共享内存,用于与节点1的CPU8通信;节点1的CPU8读取 / 写入该共享内存时,需跨节点访问节点0的本地内存,形成跨NUMA。
- CPU访问非本地节点的PCIe设备
设备与CPU未绑定到同一个NUMA节点
网卡场景:若网卡直连节点1的 PCIe 总线(归属节点1),但处理网卡流量的进程(如Nginx、DPDK 应用)被绑定到节点0的CPU,此时节点0的CPU需要跨节点访问节点1的网卡,导致数据传输延迟升高。 SSD 场景:若 NVMe SSD归属节点0,但运行数据库的进程被绑定到节点1的CPU,CPU读取SSD数据时需跨NUMA,影响IO性能。
多设备负载集在单一节点,其它节点需跨节点调用
例如:服务器有2个NUMA节点,仅节点0连接了2块高速SSD,节点1无本地SSD。当节点1的CPU需要读取数据时,必须跨节点访问节点0的SSD,触发跨NUMA。
- CPU核心与进程/线程的节点不匹配
3 NUMACTL工具使用
命令:
numactl --hardware(numactl -H)
输出
available: 1 nodes (0) node 0 cpus: 0 1 node 0 size: 1756 MB node 0 free: 147 MB node distances: node 0 0: 10
或者
node distances: node 0 1 2 3 0: 10 20 30 40 1: 20 10 50 60 2: 30 50 10 70 3: 40 60 70 10 可以得到如下图 +----------------+ +----------------+ | Node 0 | | Node 1 | | CPUs: 0-3 |<----->| CPUs: 4-7 | | Mem: 32GB | 20 | Mem: 32GB | +----------------+ +----------------+ | 30 | 50 v v +----------------+ +----------------+ | Node 2 | | Node 3 | | CPUs: 8-11 |<----->| CPUs: 12-15 | | Mem: 32GB | 70 | Mem: 32GB | +----------------+ +----------------+ 其中访问本地内存的延迟为10,数值代表访问延时
浙公网安备 33010602011771号