在实际工作中,经常遇到服务器是否开启NUMA、NUMA绑定几颗Core、跨NUMA节点访问的性能下降等等话题。那么NUMA作为非一致性内存访问的多处理器架构,在架构上有什么特性、与SMP架构有哪些不同,调优策略等,本文将作简要介绍。


1、CPU处理器NUMA架构简介

服务器CPU是计算机服务器的大脑,作为服务器的运算和控制核心,是信息处理、程序运行的最终执行单元,负责读取指令、对指令译码并执行。在服务器中,CPU与GPU、内存、硬盘和网卡间并不能直接通信,需要通过内存控制芯片、PCIe控制芯片和I/O处理芯片等实现,这些芯片通过不同总线(PCIe总线、USB总线和SPI总线等)与CPU相连。

CPU处理器的架构从最初简单的架构到现在复杂的架构经历了多年的发展,其中SMP(Symmetric Multi-Processing)架构和NUMA(Non-Uniform Memory Access)架构是两种非常重要的多处理器架构。

  1. SMP架构的出现是在多核处理器和并行处理技术发展的背景下。由于单个处理器的性能提升受限于物理极限,为了提高整体计算能力,将多个处理器集成到一个系统中,并让它们共享内存和总线等资源。
  2. NUMA架构则是在SMP架构的基础上进一步发展而来的。随着处理器数量的增加,SMP架构中的共享内存和总线成为了性能瓶颈。为了解决这个问题,NUMA架构将内存划分为多个节点(Node),每个节点都有自己的处理器和本地内存。处理器访问自己节点的内存速度较快(本地访问),而访问其他节点的内存速度较慢(远程访问)。这种设计有助于减少内存访问冲突,提高并行处理性能。
1.1 SMP架构

SMP(Symmetric Multi-Processing)即对称多处理架构,在这种系统架构中,服务器不再由单个CPU组成,而是同时由多个处理器运行操作系统的单一复本,各CPU之间共享总线,内存和I/O系统等服务器资源进行并行处理。由于所有处理器共享同样的内存访问地址空间和总线结构,因此从管理的角度来看,它们是“对称”的,即无主从之分,工作负载可以均匀地分配到所有可用处理器上,从而显著提高整个系统的数据处理能力。因此SMP也被称为一致存储器访问结构 (UMA:Uniform Memory Access)。

SMP的主要特征是共享,但是由于所有处理器共享相同的内存和总线,当处理器数量增加时,共享内存和总线可能成为性能瓶颈,因此SMP架构在处理器数量扩展性方面受到一定的限制。其中最受限制的则是内存,由于每个CPU必须通过相同的内存总线访问相同的内存资源,因此随着CPU数量的增加,内存访问冲突将迅速增加,最终会造成CPU资源的浪费,使CPU性能的有效性大大降低。实验证明,SMP服务器CPU利用率最好的情况是 2至4个CPU 。此外,为了保证共享存储器的数据一致性,需要软硬件实现加锁机制来解决多个处理器同时访问共享资源时的资源竞态问题。

1.2 NUMA架构

在最开始的CPU处理器架构中,CPU通过前端总线(FSB,Front Side Bus)连接到北桥芯片(NorthBridge),然后北桥芯片连接到内存(内存控制器集成在北桥芯片里面)。

上述架构也称为UMA架构(Uniform Memory Access),在 UMA 架构下,CPU 和内存之间的通信全部都要通过前端总线。但是随着CPU性能的提升由CPU主频向多核CPU发展,越来越多的CPU对前端总线FSB的争用,前端总线逐渐称为瓶颈。NUMA(Non-Uniform Memory Access)架构在此背景下发展起来。

NUMA架构由多个CPU模块组成,每个CPU模块又由多个CPU组成,并且有独立的本地内存、IO controller等。各个CPU节点之间通过互联模块进行访问,在AMD处理器称为Hyper-Transport,Intel在Nehalem-EX系统处理器上引入Quick-Path Interconnect(QPI)。

与UMA/SMP架构对比,内存访问最大的不同之处是出现了本地内存和远程内存。

  • 每个CPU独立连接到一部分内存,这部分CPU通过内部总线直连的内存称为“本地内存”;
  • CPU之间互联模块(QPI总线)访问不和自己直连的内存称为“远程内存”。

访问本地内存的速度要远快于远程内存,NUMA(非一致性存储器访问)因此而得名。

1.2.1 QPI介绍

QPI(QuickPath Interconnect)是Intel推出的一种高速总线技术,用于连接多处理器系统中的处理器、内存和其他扩展模块,取代了之前的 FSB(Front Side Bus)总线架构。QPI 主要用于连接以下模块:

  • 处理器(CPU):QPI用于连接多个处理器,构建多处理器系统。每个处理器都会有自己的QPI接口,通过QPI总线与其他处理器进行通信和数据交换。
  • 内存控制器:QPI还用于连接处理器与内存控制器,将内存访问请求从处理器传输到内存模块,并返回请求的数据。通过QPI总线,处理器可以直接与内存控制器通信,实现快速的内存访问和数据交换。
  • I/O协处理器:在某些系统中,QPI还可以用于连接处理器与I/O协处理器或其他扩展模块,以实现更多的功能扩展和协同计算能力。这些模块可以通过QPI总线与处理器进行高速数据传输和协作计算。

1.2.2 NUMA结构

在Intel处理器的NUMA架构中,包括Processor Node、内存、互联模块(QPI)和I/O Subsystem。其中CPU又分为以下结构:

  • Socket:表示一颗物理CPU的封装(物理CPU插槽),简称插槽。为了避免将逻辑处理器和物理处理器混淆,Intel将物理处理器称为插槽。
  • Core:物理CPU封装内的独立的一组程序执行的硬件单元,比如寄存器,计算单元等。
  • Thread:使用超线程技术虚拟出来的逻辑Core,需要CPU支持。为了便于区分,逻辑Core一般被写作Processor。在具有Intel超线程技术的处理器上,每个内核可以具有两个逻辑处理器,这两个逻辑处理器共享大多数内核资源(如内存缓存和功能单元)。此类逻辑处理器通常称为Thread 。
  • Node:在NUMA架构中,处理器被组织成多个节点,每个节点包含一组处理器。

NUMA最大的特点是引入了node和distance的概念:node内部有多个CPU,以及绑定的内存;distance这个概念是用来定义各个node之间调用资源的开销。通过命令numactl –hardware可以查看NUMA信息:

available: 2 nodes (0-1)node 0 cpus: 0 1 2 3node 0 size: 16325 MBnode 0 free: 12196 MBnode 1 cpus: 4 5 6 7node 1 size: 16384 MBnode 1 free: 14782 MBnode distances:node 0 1 0:1021 1:2110
  • available:2 nodes (0-1) 表示系统中有两个 NUMA 节点,编号分别为 0 和 1。
  • node 0 cpus:0 1 2 3 和 node 1 cpus: 4 5 6 7 分别列出了每个节点上的 CPU 列表。在这个例子中,节点 0 包含 CPU 0 到 3,而节点 1 包含 CPU 4 到 7。
  • node 0 size:16325 MB 和 node 1 size: 16384 MB 分别列出了每个节点的总内存大小。
  • node 0 free:12196 MB 和 node 1 free: 14782 MB 分别列出了每个节点的当前空闲内存大小。
  • node distances:显示了每对节点之间的距离,这是NUMA系统中内存访问成本的一个指标。在上例中,节点0访问自己的内存(距离为10)比访问节点1的内存(距离为21)要快。同样,节点1访问自己的内存也比访问节点0的内存要快。
1.2.3 SMP架构和NUMA架构对比

NUMA(Non-Uniform Memory Access)和SMP(Symmetric Multi-Processing)是两种常见的多处理器架构,它们在处理器如何访问内存以及内存如何分布方面有所不同。以下是它们之间的主要区别和各自的优缺点:

  • 内存访问方式:
    • NUMA架构中,内存被划分到多个节点(Node)上,每个节点都有自己的本地内存和处理器。处理器访问自己节点的内存速度较快(本地访问),而访问其他节点的内存速度较慢(远程访问)。这种设计有助于减少内存访问冲突,提高并行处理性能。
    • SMP架构中,所有处理器共享同一块内存,并通过相同的总线或互联结构进行访问。因此,所有处理器访问内存的速度是相同的。这种设计简化了内存管理,但可能导致处理器之间的内存访问冲突。
  • 扩展性:
    • NUMA架构具有较好的扩展性,因为可以通过添加更多的节点来增加处理器和内存容量。每个节点可以独立地处理任务,从而提高了系统的整体性能。
    • SMP架构的扩展性相对较差。当处理器数量增加时,共享内存和总线可能成为性能瓶颈。此外,由于所有处理器共享相同的内存空间,因此内存容量也可能成为限制因素。
  • 成本和复杂性:
    • NUMA架构通常需要更复杂的硬件和软件支持来实现节点之间的内存访问和缓存一致性。这可能导致更高的成本。
    • SMP架构相对简单且成本较低,因为所有处理器共享相同的内存和总线结构。
  • 适用场景:
    • NUMA架构适用于需要大量内存和处理器资源的高性能计算和科学计算场景。它允许系统通过添加更多的节点来扩展性能,从而满足不断增长的计算需求。
    • SMP架构适用于对成本敏感且处理器数量相对较少的应用场景。它提供了简化的内存管理和较低的成本,适用于许多常见的服务器和工作站环境。
1.3 NUMA配置及调优
1.3.1 CPU分配策略和内存分配策略

1)CPU分配策略

在NUMA架构中,CPU分配策略主要关注如何将进程或线程分配给处理器,以最小化内存访问延迟。以下是几种常见的CPU分配策略:

  • cpunodebind:此策略将进程或线程绑定到特定的NUMA节点上的处理器。这可以确保进程在访问内存时具有最低的延迟,因为它主要访问与其绑定的处理器相同的节点上的内存。
  • physcpubind:与cpunodebind类似,但更精细。它允许将进程或线程绑定到特定的物理CPU核心上,而不仅仅是NUMA节点。这提供了更高级别的控制,但也需要更详细的系统知识来进行配置。
  • 自动平衡:一些操作系统和调度器会自动尝试在NUMA节点之间平衡负载,以最大化整体系统吞吐量。这可能会导致单个进程的内存访问延迟增加,但在多进程环境中可能会提高整体性能。

2)内存分配策略

内存分配策略决定了如何在NUMA架构中的不同节点上分配内存。以下是几种常见的内存分配策略:

  • localalloc:此策略优先在请求内存的处理器所在的NUMA节点上分配内存。这最小化了内存访问延迟,但可能导致某些节点上的内存过度使用,而其他节点上的内存则未充分利用。
  • preferred:此策略指定了一个“首选”节点来分配内存,但如果该节点上的内存不足,则允许在其他节点上分配内存。这提供了一定的灵活性,但仍然倾向于优先使用特定节点上的内存。
  • membind:此策略允许用户指定一个或多个NUMA节点,进程只能从这些指定的节点上分配内存。这提供了对内存放置的精确控制,但可能需要用户具有详细的系统知识。
  • interleave:此策略以轮询方式在不同的NUMA节点上分配内存。这有助于平衡节点之间的内存使用,但可能会增加内存访问延迟,因为处理器可能需要访问远离其所在节点的内存。
  • 自动优化:某些操作系统和内存管理器会自动尝试优化内存分配,以在延迟、吞吐量和内存利用率之间找到最佳平衡。这可能需要复杂的算法和持续的系统监控。

NUMA架构的CPU默认采用的是localalloc的内存分配策略,运行在本node内部CPU上的进程,会从本node内部的内存上分配内存,如果内存不足,则会导致swap的产生,严重影响性能。因此在系统配置时候关闭SWAP,避免产生SWAP影响性能,另外将内存分配策略设置为interleave; 这样设置的话,任何进程的内存分配,都会随机向各个node申请。虽然跨Node申请内存访问也会造成一定的性能损耗。

1.3.2 NUMA架构下进程与CPU绑定

在NUMA架构中,如果内存交叉访问较多、缓存命中率较低、需要控制进程运行位置或者避免NUMA效应等场景下,需要将进程绑定在特定的CPU中。

  • 内存交叉访问较多:当进程频繁地访问不同NUMA节点上的内存时,会导致较高的内存访问延迟。通过将进程与某个NUMA节点上的CPU绑定,可以减少跨节点的内存访问,从而降低延迟。
  • 缓存命中率较低:如果进程的数据局部性较差,导致缓存命中率较低,那么将进程与CPU绑定可以提高缓存利用率。进程在同一个CPU上运行时,更有可能重用该CPU的缓存中的数据。
  • 需要控制进程运行位置:在某些情况下,可能需要将特定进程运行在特定的CPU或CPU核心上,以满足特定的性能、隔离性或资源管理需求。例如,某些实时应用或高性能计算任务可能需要独占某些CPU核心以确保稳定的性能。
  • 避免NUMA效应:NUMA架构中,访问远地内存的成本高于访问本地内存。通过将进程与CPU绑定,可以尽量减少远地内存的访问,从而避免NUMA效应带来的性能下降。

通过命令numastat可以查看每个NUMA节点的内存分配的统计数据,包括内存分配的成功与失败情况。

#numastat node0 node1numa_hit 3620312 3672057numa_miss0 0numa_foreign 0 0interleave_hit 36004 35732local_node 3608692 3645179other_node 11620 26878
  • numa_hit:表示成功在本地节点上分配内存的次数;
  • numa_miss:表示本应在其他节点上分配内存但由于某些原因(如内存不足)而在本地节点上分配的次数;
  • numa_foreign:表示初始分配在本地但最后分配在其他节点的内存页数量
  • local_node和other_node:分别表示本地节点进程和其他节点进程在本节点上分配的内存页数量

当numa_miss和numa_foreign值越高,就要考虑绑定的问题了。通过命令numactl将进程绑定在某个Node或Cores上:

#将进程绑定到某节点上numactl --cpunodebind=nodes program#将进程绑定到某Core上numactl --physcpubind=1,2,3,4Program

参考资料:

  1. 详谈CPU处理器架构演进(Intel)
  2. https://zhuanlan.zhihu.com/p/677797033
  3. https://www.cnblogs.com/ml2018/articles/12747193.html
  4. https://blog.csdn.net/bandaoyu/article/details/122959097
  5. https://blog.csdn.net/HandsomeHong/article/details/128535279