libnuma详解(A NUMA API for LINUX)

白皮书
numa3手册
numa3手册

一、什么是NUMA

  1. 关于这个很多博客都有,基本都是这个白皮书的翻译或者扩充。

    在传统的SMP(对称多处理)系统中,计算机有一个由所有cpu共享的内存控制器。当所有处理器同时访问内存时,这种单一内存连接常常成为瓶颈。对于具有更多cpu的大型系统,它也不能很好地扩展。由于这个原因,越来越多的现代系统正在使用CC/NUMA(缓存一致性/非一致性内存访问)架构。例如AMD* Opteron*、IBM* Power5*、HP* Superdome和SGI* Altix*。

    在SMP系统上,所有cpu都可以平等地访问连接到所有内存芯片(DIMMs)的同一个共享内存控制器。cpu之间的通信也要经过这个共享资源,这可能会造成拥塞。可以由单一控制器管理的内存芯片的数量也是有限的,这就限制了系统可以支持多少内存。此外,通过这个单一流量集线器访问内存的延迟相对较高。

    NUMA体系结构被设计为超越SMP体系结构的可伸缩性限制。系统被分割成多个节点,而不是每台计算机有一个单一的内存控制器。每个节点都有处理器和自己的内存。处理器可以非常快地访问节点中的本地内存。系统中的所有节点都通过快速互连连接起来。添加到系统中的每个新节点都为系统提供了更多的聚合内存带宽和容量,从而提供了出色的可伸缩性。

    一个节点中的所有处理器都对该节点中的内存具有同等的访问权。在CPU集成了内存控制器(如AMD Opteron)的系统上,节点通常由单个CPU组成,可能带有多个核心或虚拟线程。在其他更传统的NUMA系统上,如SGI Altix或HP Superdome,具有2到4个cpu的较大节点(类似于小型SMP系统)共享内存。

    在NUMA系统中,每个CPU都可以访问本地和远程内存。本地内存位于与CPU相同的节点上,提供了非常低的内存访问延迟。远程内存位于不同的节点,必须通过互连访问。从软件的角度来看,这个远程内存可以用相同的方式使用本地内存;它是完全缓存相干的。访问它需要更长的时间,因为互连比节点的本地内存总线增加了更多的延迟。

    理论上,NUMA系统可以被视为一个SMP系统,只要忽略软件中本地和远程内存之间的差异。事实上,这是经常发生的。但为了获得最佳性能,应该考虑到这些差异。

    NUMA架构的一个巨大优势是,即使在一个有许多cpu的大系统中,本地内存的延迟也可能非常低。因为现代的CPU比内存芯片快得多,所以CPU在从内存读取数据时通常要花费相当长的时间等待。因此,最小化内存延迟可以提高软件性能。

    NUMA策略关心的是将内存分配放在特定的节点上,以让程序尽可能快地访问它们。实现这一点的主要方法是为其本地节点上的线程分配内存,并保持线程在那里运行(节点关联)。这为内存提供了最佳的延迟,并将通过全局互连的流量最小化。

    在SMP系统上,有一个与之有点类似的常见优化,称为缓存关联。缓存关联试图将数据保存在CPU的缓存中,而不是在处理器之间频繁地弹跳数据。这通常是由操作系统中的调度器完成的,该调度器试图在将线程调度到另一个CPU之前,将线程在一个CPU上保持一段时间。
    但是,内存关联与节点关联有一个重要的区别:当SMP系统上的线程在cpu之间移动时,它的缓存内容最终会随之移动。一旦内存区域被提交到NUMA系统上的特定节点,它就会保留在那里。在访问它的不同节点上运行的线程总是向互连添加通信量,并导致更高的延迟。这就是NUMA系统需要比SMP系统更努力地归档节点关联的原因。当然,缓存关联本身在NUMA系统上也是值得优化的。要获得最佳性能,这是不够的。

    然而,操作系统中的调度器不能总是仅针对节点关联进行优化。问题是,在系统中不使用CPU会比使用远程内存并看到更高内存延迟的进程更糟糕。在内存性能甚至比系统中所有cpu的使用更重要的情况下,应用程序或系统管理员可以覆盖操作系统的默认决策。这允许更好地优化特定的工作负载。

    Linux传统上使用系统调用将线程绑定到特定的cpu(使用sched_set_affinity(2)系统调用和schedutils)。NUMA API扩展了这一点,允许程序指定应该在哪个节点上分配内存。

    为了使用户空间程序更容易优化NUMA配置,API可以导出拓扑信息,并允许使用用户指定的处理器和内存资源。还有一些内部内核api提供供内核子系统使用的NUMA拓扑信息。

    这里描述的NUMA API将线程到cpu的位置和内存的位置分开。它主要与内存的放置有关。此外,应用程序可以单独配置CPU关联。
    NUMA API目前可在SUSE®LINUX Enterprise Server 9上用于AMD64和Intel* Itanium*处理器
    族。

  2. 关于NUMA的一些其他理解
    上面白皮书其实写的已经很好了,但是没有啥细节,对我这种小白不友好。

    • socket node core cpu thread等概念解读
      socket是一个物理上的概念,指的是主板上的cpu插槽。
      node是一个逻辑上的概念,是相邻core的一个分组。
      core一般是一个物理cpu,一个独立的硬件执行单元。
      thread是逻辑的执行单元,一般对应 cpu 的核数。

    • NUMA的拓扑结构介绍
      2-socket Intel NUMA topology。CPU 0和CPU 1表示物理处理器包,而不是单个核心,并且作为内存模块的相应数量的NUMA内存节点被部署在对应的处理器的相邻的DIMM卡槽中。
      在这里插入图片描述
      4-socket Intel E5-4600 NUMA node architecture
      在这里插入图片描述
      4-socket AMD NUMA node architecture。每个物理处理器包都有两个NUMA节点。
      在这里插入图片描述
      图4说明了NUMA节点局部性的介绍(其中NUMA节点被认为是给定核心的本地节点)。在本例中,对于物理处理器CPU 0中的core 0,紧挨着该处理器socket的内存被认为是本地NUMA节点。对于core 1,它是物理处理器CPU 1的一部分,被认为是本地的NUMA节点是挂在CPU 1上的节点。每个物理处理器最多可以有8个物理内核和Intel Xeon E5-2600系列处理器,每个socket最多可以有16个逻辑处理器(启用超线程)。为了便于演示,只显示了每个处理器socket上的第一个物理核心。
      在这里插入图片描述
      图5表示了另一种可视化相对NUMA节点局部性的方法。在这里,我们来看另一个简化的示例,其中每个位于socket(表示为CPU 0和CPU 1)中的top-bin E5-2600系列物理处理器都有8个核。出于本示例的目的,每个处理器核都从1到8进行编号,尽管在启动后核心编号策略会发生变化。我们可以看到CPU 0上的第一个核(用绿色表示)是NUMA节点0的本地核。这意味着最靠近CPU 0填充的DIMM插槽是本地的,而最靠近CPU 1填充的DIMM插槽(红色的NUMA node 1)是远程的。这是因为要从CPU 0上的核心1到达NUMA节点1,内存请求必须遍历CPU间QPI链路,并使用CPU 1的内存控制器来访问这个远程节点。额外的跳跃增加了远程NUMA节点内存访问的延迟。
      在这里插入图片描述
      我们看到了相同的系统,但是现在Linux已经列出了内核。注意,每个核心现在都表示为一个不同的OS CPU,这是Linux引用逻辑处理器的方式。还需要注意的是,第一个OS CPU(0)是第一个物理处理器包上的第一个核心。现在注意,OS CPU 1是第二个物理处理器包上的第一个核心。这种模式在所有可用的内核上都延续,因此对于Intel 2P系统,所有偶数编号的OS cpu都在第一个物理处理器包上,而所有奇数编号的OS cpu都在第二个物理处理器包上。同样重要的是要注意,对于任何特定的NUMA节点,哪些内核是本地的,以及OS cpu和NUMA节点是从0开始编号的。
      在这里插入图片描述

      图9中表示的核心枚举模式显示了同一个启用超线程的2套接字处理器Intel xeon系统。字符前的第一个数字表示分配给实际内核的OS CPU号。字符后面的第二个数字表示分配给超线程同级线程的OS CPU数字。在Linux内核引导的核心枚举阶段,首先在填充的物理处理器包之间以循环的方式枚举所有真正的内核。一旦枚举了所有的真实核,超线程的兄弟核也会以类似的方式枚举,在填充的处理器包之间循环。对于一个带有Intel Xeon 8核处理器的2P 16核系统的示例,在启用超线程时总共枚举了32个逻辑处理器。
      在这里插入图片描述
      注意,每个物理处理器包都有两个与之关联的NUMA节点。此外,每个物理处理器被分成两组,每组8个核。与单个NUMA节点关联的核心是按顺序枚举的。在图11中,我们看到OS CPU 0 8是NUMA节点0的一部分。同一物理处理器包上编号为OS CPU 9 15的第二组核心对于NUMA节点1来说是本地的。需要注意的是,转移到下一个套接字需要从32开始枚举核心。第四个物理处理器包使用OS CPU 16启动核心枚举并继续进行在这里插入图片描述

二、带宽优化

程序的内存访问性能可以根据延迟或带宽进行优化。大多数程序似乎更喜欢低延迟,但也有一些例外需要带宽。

使用节点本地内存具有最佳的延迟。为了获得更大的带宽,可以并行使用多个节点的存储控制器。这类似于RAID如何通过将I/O操作分散到多个硬盘上来提高磁盘I/O性能。NUMA API可以使用CPU中的MMU(内存管理单元)来交错来自不同内存控制器的内存块。这意味着这样一个映射中的每个连续的page都来自不同的节点。

当一个应用程序对这样的交错区域做一个大的流内存访问时,多个节点的内存控制器的带宽被合并。它工作的好坏取决于NUMA体系结构,特别是取决于互连的性能以及本地和远程内存之间的延迟差异。
在某些系统上,它只对相邻节点的子集有效。

一些NUMA系统,如AMD Opteron,可以通过固件配置,在页面基础上交错所有节点的所有内存。这叫做节点交错。节点交错类似于NUMA API提供的交错模式,但它们在重要方面有所不同。节点交错适用于所有内存。可以为每个进程或线程单独配置NUMA API交错。如果固件启用了节点交织,则NUMA策略将被禁用。要使用NUMA策略,必须在BIOS或固件中始终禁用节点交错。

使用NUMA API,每个应用程序都可以单独调整内存区域用于延迟或带宽的策略。

三、NUMA的实现

NUMA策略是由几个子系统共同努力提供的。内核管理进程或特定内存映射的内存策略。这个内核可以通过三个新的系统调用来控制。可以从应用程序中调用一个名为libnuma的用户空间共享库。推荐使用libnuma API 来使程序实现NUMA策略。它提供了比直接使用系统调用更友好和抽象的接口。本文仅描述这个高级接口。

当不应该修改应用程序时,管理员可以使用numactl命令行实用程序设置一些策略。这不如直接从应用程序控制策略灵活

用户库和应用程序包含在numactl RPM中,它是SUSE LINUX Enterprise Server 92的一部分。此外,包中还有一些实用程序,如numastat,用于收集关于内存分配的统计信息,numademo用于显示不同策略对系统的影响。这个包还包含所有函数和程序的手册页。

四、NUMA POLICIES

NUMA内存策略是 NUMA-aware(NUMA感知)应用程序 可以利用的编程接口。

NUMA API的主要任务是管理策略。策略可以应用于进程或内存区域。
NUMA API目前支持四种策略:

策略名字描述
default在本地节点(当前线程运行的节点)上进行分配
bind在特定的节点集上分配
interleave在一组节点上交错分配内存
preferred试着先在一个节点上分配

bind和preferred之间的区别是,当不能在指定的节点上分配内存时,bind会失败;而preferred则返回到其他节点。使用bind可能会导致更早的内存短缺和由于交换而导致的延迟。在libnuma中,preferred和bind是结合在一起的,可以通过numa_set_strict libnuma函数对每个线程进行更改。默认的是更灵活的preferred分配。

可以为每个进程(进程策略)或每个内存区域设置策略。子进程继承fork上父进程的进程策略。进程策略应用于进程上下文中进行的所有内存分配。这包括在系统调用和文件缓存中进行的内部内核分配。中断总是在当前节点上分配。当内核分配内存页时,进程策略总是适用。

为每个内存区域设置策略,也称为VMA策略3,允许进程为其地址空间中的内存块设置策略。内存区域策略比进程策略具有更高的优先级。内存区域策略的主要优点是可以在分配发生之前设置它们。目前只支持匿名进程内存、SYSV共享内存、shmem和tmpfs映射以及大型tlbfs文件。共享内存的区域策略一直持续到删除共享内存段或文件为止。





NUMACTL的部分跳过。



五、Libnuma编程

1. 基础:检查NUMA

libnuma是一个可以链接到程序的共享库,它为NUMA策略提供了一个稳定的API。它提供了比直接使用NUMA API系统调用更高级别的接口,是程序推荐的接口。libnuma是numactl RPM的一部分。

应用程序链接libnuma如下:
CC …… -lnuma

NUMA API函数和宏在 “numa.h” 包含文件中声明。

#include <numa.h>
   ……
   if(numa_available() < 0) {
   	printf("Your system does not support NUMA API\n");
   	...
   }
   ……

在使用任何NUMA API函数之前,程序必须调用numa_available()。当该函数返回一个负值时,系统上不支持NUMA策略。在这种情况下,所有其他NUMA API函数的行为都是未定义的,不应该调用它们。

下一步通常是调用numa_max_node()。这个函数发现并返回系统中的节点数。节点的数量通常需要在程序中设置和验证内存策略。所有的程序都应该动态地发现这个问题,而不是硬编码一个特定的系统拓扑。

每个线程都在本地保存所有libnuma状态。更改一个线程中的策略不会影响进程中的其他线程。

下面几节通过一些示例概述各种libnuma函数。一些不常见的功能没有提到。要获得更详细的参考资料,请参阅numa(3)手册页。

2. nodemasks

libnuma以在numa.h中定义的称为nodemask_t的抽象数据类型管理节点集。nodemask_t是节点编号的固定大小位集。系统中的每个节点都有一个唯一的编号。最大的数字是numa_max_node()返回的数字。最高的节点 是根据 常数NUMA_NUM_NODES 的实现定义的。nodemask通过引用传参数给许多NUMA API函数。

numa.h中nodemask_t的定义:

#if defined(__x86_64__) || defined(__i386__)
#define NUMA_NUM_NODES  128
#else
#define NUMA_NUM_NODES  2048
#endif

typedef struct {
        unsigned long n[NUMA_NUM_NODES/(sizeof(unsigned long)*8)];
} nodemask_t;

可以看到 nodemask_t 的大小是 NUMA_NUM_NODES 除 unsigned long 的位数。

NUMA_NUM_NODES 的宏定义逻辑我没看懂,再说。

nodemask用nodemask_zero()初始化为空。

nodemask_t mask;
nodemask_zero(&mask);

单个节点可以用nodemask_set设置,用nodemask_clr清除。nodemask_equal比较两个节点。nodemask_isset测试是否在nodemask中设置了位。

nodemask_set(&mask, maxnode); /* set node highest */
if (nodemask_isset(&mask, 1)) { /* is node 1 set? */
...
}
nodemask_clr(&mask, maxnode); /* clear highest node again */

有两个预定义的节点 : numa_all_nodes 表示系统中的所有节点,numa_no_nodes 是空集。

3. 简单内存空间分配

libnuma提供了使用指定策略分配内存的函数。这些分配函数将所有分配分配到页面(在AMD64系统上为4 KB),并且相对较慢。它们应该仅用于分配超出CPU缓存大小的大型内存对象,以及NUMA策略可能会提供帮助的地方。当不能分配内存时,它们返回NULL。所有由numa_alloc函数族分配的内存都应该通过numa_free释放。

numa_alloc_onnode在特定节点上分配内存:

void *mem = numa_alloc_onnode(MEMSIZE\_IN\_BYTES, 1);
if (mem == NULL)
/* report out of memory error */
... pass mem to a thread bound to node 1 ...

memsize应该小于节点大小。请记住,其他程序也可能在该节点上分配内存,而分配整个节点可能导致交换。下面描述的numa_node_size()函数可用于自动发现当前系统的节点大小限制。建议为管理员提供一种方法来覆盖程序的自动选择并限制内存消耗。

线程最终必须使用numa_free释放内存:

numa_free(mem, memsize);

默认情况下,numa_alloc_onnode首先尝试在指定的节点上分配内存,但是当内存不足时,就会返回到其他节点。当numa_set_strict(1) 首先执行时,当目标节点上没有足够的内存时,它不会后退并导致分配失败。在此之前,内核会尝试交换节点上的内存并清除其他缓存,这可能会导致延迟。



numa_alloc_interleaved在系统中的所有节点上交错分配内存。

void *mem = numa_alloc_interleaved(MEMSIZE\_IN\_BYTES);
if (mem == NULL)
/* report out of memory error */
... run memory bandwidth intensive algorithm on mem ...
numa\_free(mem, MEMSIZE_IN_BYTES);

在所有节点上交错使用内存并不总是性能上的优势。根据机器的NUMA架构,有时将程序交错在相邻节点的一个子集上可以获得更好的带宽。
numa_alloc_interleaved_子集函数只能用于交错特定的一组节点。

另一个函数是numa_alloc_local,它在本地节点上分配内存。这通常是所有分配的默认值,但是当进程有不同的进程策略时,显式地指定是有用的。numa_alloc使用当前进程策略分配内存。

4. 进程策略

每个线程都有一个从父线程继承的默认内存策略。除非用numactl进行了更改,否则该策略通常最好用于在当前节点上分配内存。当不能修改程序中的现有代码来直接使用前一节中描述的numa_alloc函数时,有时更改程序中的进程策略是有用的。通过这种方式,特定子函数可以使用非默认策略运行,而无需实际修改它们的代码。进程策略还可用于在启动子进程之前为其设置策略。

numa_set_interleave_mask允许对当前线程进行交错。所有将来的内存分配都是通过交错指定的节点请求来分配内存的。传递numa_all_nodes会将内存交错给所有节点。传 numa_no_nodes再次关闭交叉。numa_get_interleave_mask返回当前交错掩码。这对于在库中更改状态之前保存状态,以便稍后恢复状态非常有用。

numamask_t oldmask = numa_get_interleave_mask();
numa_set_interleave_mask(&numa_all_nodes);
/* run memory bandwidth intensive legacy library that allocates memory */
numa_set_interleave_mask(&oldmask);

numa_set_preferred设置当前线程的首选节点。内存分配器尝试首先在该节点上分配内存。如果没有足够的空闲内存,它将返回到其他节点。

numa_set_membind将严格的内存绑定掩码设置为nodemask。“严格”意味着必须在指定的节点上分配内存。当在交换后没有足够的内存可用时,分配失败。
numa_get_membind返回当前的内存绑定掩码。

numa_set_localalloc将进程策略设置为标准的本地分配策略。

5. 改变已分配内存区域策略

当使用共享内存时,通常不可能使用numa_alloc函数家族来分配内存。内存必须从shmat()或mmap获取。为了允许libnuma程序在这些区域上设置策略,有一些附加函数用于为已经存在的内存区域设置内存策略。

这些函数只影响指定区域的未来分配。Linux使用请求分页,并且只在CPU第一次访问某个页面时分配内存。

numa_interleave_memory使用一个交织掩码设置一个交织策略。将numa_all_nodes传递给系统中的所有节点。

void *mem = shmat( ... ); /* get shared memory */
numa_interleave_mask(mem, size, numa_all_nodes);

numa_tonode_memory分配特定节点上的内存。numa_tonodemask_memory将内存放到节点掩码中。numa_setlocal_memory为当前节点分配内存区域提供了一个策略。numa_police_memory使用当前策略来分配内存。这在以后更改内存策略时非常有用。

当先前执行numa_set_strict(1)时,这些调用会在内存区域中任何已经存在的页面不符合新策略时调用numa_error。否则,现有页面将被忽略。

6. 绑定到CPUs

到目前为止所讨论的函数在特定节点上分配内存。NUMA策略的另一部分是在正确节点的cpu上运行线程。这是由numa_run_on_node函数完成的,该函数将当前线程绑定到节点中的所有cpu。numa_run_on_node_mask将线程绑定到一个nodemask中的所有cpu。

在节点1上运行当前线程并分配内存:

numa_run_on_on_node(1);
numa_set_prefered(1);

使用libnuma的一种简单方法是numa_bind函数。它将将来分配给特定nodemask的进程的CPU和内存绑定在一起。它与前面的示例相同。

使用numa_bind将进程CPU和内存分配绑定到节点1:

nodemask_t mask;
nodemask_zero(&mask);
nodemask_set(&mask 1);
numa_bind(&mask);

通过将线程绑定到numa_all_nodes,可以允许线程再次在所有节点上执行:

numa_run_on_node_mask(&numa_all_nodes);

numa_get_run_node_mask函数返回允许当前线程运行的节点的nodemask。这可用于在运行子进程或启动线程之前保存和恢复调度器关联状态。

7. 关于环境的保证

numa_node_size返回一个节点的内存大小。返回参数是它的内存的总大小,这些内存不一定都是程序可用的。第二个参数是一个指针,可以用节点上的空闲内存填充它。程序可以在空闲内存(空闲内存通常很低,因为Linux使用空闲内存进行缓存)和最大内存大小之间分配节点内存。
在分配内存时,Linux释放缓存的文件数据,但是分配过多的内存可能导致交换。这个函数给出了每个节点上有多少内存可供分配的提示,但是应该只将其作为提示,最好是通过某种方式让管理员重写这个函数。一般来说,默认情况下建议永远不要分配超过节点总内存一半的内存,除非管理员指定了更多。

uma_node_to_cpus返回一个节点中CPUs的CPU数量。这可以用来确定一个节点中有多少个cpu。作为参数,它获取节点编号和数组指针。最后一个参数是数组的字节长度。该数组由CPU数字的位掩码填充。例如,稍后可以将这些CPU编号传递给sched_set_affinity系统调用。当数组不够长不能包含所有cpu时,函数返回-1并将errno设置为ERANGE。建议应用程序处理此错误或传递一个非常大的缓冲区,比如512字节。否则,在非常大的机器上可能会出现故障。Linux已经在1024台CPU机器上运行,预计将被转移到更大的机器上。

8. 错误处理

libnuma中的错误处理相对简单。造成这种情况的主要原因是设置NUMA策略时的错误通常可以忽略。一个错误的NUMA政策的最坏结果是程序运行得比它本可以运行的更慢。

当设置策略时发生错误时,将调用numa_error函数。默认情况下,它向stderr打印一个错误。当设置了numa_exit_on_error全局变量时,它将退出程序。函数被声明为弱函数,可以通过在主程序中定义替换函数来重写。例如,一个c++程序可以在那里抛出一个c++异常。

当没有可用内存时,内存分配函数总是返回NULL。

9. NUMASTAT工具统计NUMA内存分配数据

对于系统中的每个节点,内核在分配每个页面时维护一些与NUMA分配状态相关的统计信息。此信息可能对测试NUMA策略的有效性有用。

统计信息是通过numastat命令检索的。统计信息是按每个节点收集的。在每个节点有多个CPU内核的系统上,numastat聚合一个节点上所有内核的结果,从而形成整个节点的单个结果。numastat命令报告每个节点的以下统计信息:

统计信息种类解释
numa_hit当进程从特定节点请求页面并从被请求节点接收页面时,该特定节点的numa_hit将增加。这个进程可能在系统中的任何节点上运行,不一定在本节点运行。
numa_miss当进程从特定节点请求页面,但是却从其他节点接收页面时,numa_miss会在实际分配页面的节点上增加。 这个进程可能在系统中的任何节点上运行,不一定在本节点运行。
numa_foreign当进程从特定节点请求页面,但是却从其他节点接收页面时,numa_foreign在请求页面的原始节点上递增。这个进程可能在系统中的任何节点上运行,不一定在本节点运行。
interleave_hit当某个页面的分配遵循地址范围的interleave策略时,interleave_hit在分配页面的节点上递增。此外,numa_hit和local_node或other_node都会在分配页面的节点上增加。不保存根据交错策略分配的页面(除了保存在被请求节点上的,因为该节点缺少空闲页面)的统计信息。No statistics are kept for pages allocated according to the interleave policy but not on the requested node because of its lack of free pages.
local_node当进程请求一个页面,并且结果页面位于进程运行的相同节点时,local_node将在该特定节点上递增。
other_node当进程请求一个页面,并且结果页面位于与进程运行的节点不同的节点时,对于实际分配页面的节点,other_node会增加。

numa_miss和numa_hit以及local_node和foreign_node之间的区别是,前两个对NUMA策略的命中或未命中进行计数。后者计算分配是否与请求线程在同一个节点上。

为了更好理解这些 NUMASTAT 统计量,举几个例子:

  1. 运行在节点0上的进程请求节点0上的一个页面,并在节点0上分配该页面::
     node  3  node  2  node  1  node  0  numa_hit  + 1  numa_miss   numa_foreign   interleave_hit   local_node  + 1  other_node  \begin{array}{llllll} & \text { node } 3 & \text { node } 2 & \text { node } 1 & \text { node } 0\\ \text { numa\_hit } & & & & +1\\ \text { numa\_miss } & & & & \\ \text { numa\_foreign } & & & \\ \text { interleave\_hit } & & & \\ \text { local\_node } & & & & +1\\ \text { other\_node } & & & & \end{array}  numa_hit  numa_miss  numa_foreign  interleave_hit  local_node  other_node  node 3 node 2 node 1 node 0+1+1

  2. 运行在节点0上的进程请求节点0上的一个页面,但是由于节点0上的空闲页面不足,它被分配在节点1上.
     node  3  node  2  node  1  node  0  numa_hit   numa_miss  + 1  numa_foreign  + 1  interleave_hit   local_node   other_node  + 1 \begin{array}{llllll} & \text { node } 3 & \text { node } 2 & \text { node } 1 & \text { node } 0\\ \text { numa\_hit } & & & & \\ \text { numa\_miss } & & & +1& \\ \text { numa\_foreign } & & & &+1 \\ \text { interleave\_hit } & & & \\ \text { local\_node } & & & & \\ \text { other\_node } & & & +1& \end{array}  numa_hit  numa_miss  numa_foreign  interleave_hit  local_node  other_node  node 3 node 2 node 1+1+1 node 0+1

  3. 运行在节点0上的进程请求并接收节点1上的页面。请注意这个示例与第一个示例之间的区别。
     node  3  node  2  node  1  node  0  numa_hit  + 1  numa_miss   numa_foreign   interleave_hit   local_node   other_node  + 1 \begin{array}{llllll} & \text { node } 3 & \text { node } 2 & \text { node } 1 & \text { node } 0\\ \text { numa\_hit } & & & +1& \\ \text { numa\_miss } & & & & \\ \text { numa\_foreign } & & & & \\ \text { interleave\_hit } & & & \\ \text { local\_node } & & & & \\ \text { other\_node } & & & +1 & \end{array}  numa_hit  numa_miss  numa_foreign  interleave_hit  local_node  other_node  node 3 node 2 node 1+1+1 node 0

  4. 运行在节点0上的进程请求节点1上的一个页面,但是由于节点1上空闲页面不足,它被分配在节点0上
     node  3  node  2  node  1  node  0  numa_hit   numa_miss  + 1  numa_foreign  + 1  interleave_hit   local_node  + 1  other_node  \begin{array}{llllll} & \text { node } 3 & \text { node } 2 & \text { node } 1 & \text { node } 0\\ \text { numa\_hit } & & & & \\ \text { numa\_miss } & & & &+1 \\ \text { numa\_foreign } & & & +1& \\ \text { interleave\_hit } & & & \\ \text { local\_node } & & & &+1 \\ \text { other\_node } & & & & \end{array}  numa_hit  numa_miss  numa_foreign  interleave_hit  local_node  other_node  node 3 node 2 node 1+1 node 0+1+1

  5. 作为进一步的示例,考虑一个4节点的机器,每个节点有4 GB RAM。最初,numastat报告了这台机器的以下统计数据:
     node  3  node  2  node  1  node  0  numa_hit  58956 142758 424386 319127  numa_miss  0 0 0 0  numa_foreign  0 0 0 0  interleave_hit  19204 20238 19675 20576  local_node  43013 126715 409434 305254  other_node  15943 16043 14952 13873 \begin{array}{llllll} & \text { node } 3 & \text { node } 2 & \text { node } 1 & \text { node } 0\\ \text { numa\_hit } & 58956 & 142758 & 424386 & 319127\\ \text { numa\_miss } & 0 & 0 & 0 &0 \\ \text { numa\_foreign } & 0 & 0 & 0 &0 \\ \text { interleave\_hit } &19204& 20238 &19675& 20576 \\ \text { local\_node } & 43013 & 126715 & 409434 & 305254 \\ \text { other\_node } & 15943 & 16043 & 14952 & 13873 \end{array}  numa_hit  numa_miss  numa_foreign  interleave_hit  local_node  other_node  node 35895600192044301315943 node 2142758002023812671516043 node 1424386001967540943414952 node 0319127002057630525413873
    现在假设一个名为memhog的程序在节点1上运行,在执行期间分配了8 GB的RAM。memhog完成后,numastat报告以下统计数据:
     node  3  node  2  node  1  node  0  numa_hit  58956 142758 424386 320893  numa_miss  48365 1026046 0 0  numa_foreign  0 0 1074411 0  interleave_hit  19204 20238 19675 20577  local_node  43013 126856 1436403 307019  other_node  64308 1042089 14952 13873 \begin{array}{llllll} & \text { node } 3 & \text { node } 2 & \text { node } 1 & \text { node } 0\\ \text { numa\_hit } & 58956 & 142758 & 424386 & 320893\\ \text { numa\_miss }& 48365 & 1026046 & 0 & 0 \\ \text { numa\_foreign } & 0 & 0 & 1074411 &0 \\ \text { interleave\_hit } & 19204 & 20238 & 19675 & 20577 \\ \text { local\_node } & 43013 & 126856 & 1436403 & 307019 \\ \text { other\_node } & 64308 & 1042089 & 14952 & 13873 \end{array}  numa_hit  numa_miss  numa_foreign  interleave_hit  local_node  other_node  node 358956483650192044301364308 node 214275810260460202381268561042089 node 14243860107441119675143640314952 node 0320893002057730701913873

    从这里面的变化可以看出,memhog程序尝试分配来自节点1的1,074,411个页面,但无法这样做。相反,该进程 node 2分配了1,026,046个页面,node3 分配了48,365个页面。



     系统调用略过
    
  • 15
    点赞
  • 48
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
NUMA(Non-Uniform Memory Access)是一种计算机硬件架构,它通过将内存划分为多个节点(node)来提高系统性能。在 NUMA 架构中,每个节点都有自己的本地内存和处理器,同时还可以访问其他节点的内存和处理器。因此,NUMA 架构可以实现更高的可扩展性和性能。 在 Linux 系统中,NUMA 内存分配策略主要有两种:首选节点(Preferred Node)和本地节点(Local Node)。 首选节点策略指定一个节点作为内存分配的首选节点,如果该节点上没有足够的空闲内存,则会从其他节点中选择一个可用的节点进行分配。这种策略适用于需要在特定节点上运行的应用程序,例如数据库或虚拟机。 本地节点策略则优先在请求内存的进程所在的节点上分配内存。如果该节点上没有足够的内存,则会从其他节点中选择一个可用的节点进行分配。这种策略适用于需要快速访问本地内存的应用程序,例如科学计算或图形处理。 Linux 系统还提供了其他一些 NUMA 内存分配策略,例如交错(Interleave)和远程节点(Remote Node)等。交错策略将内存均匀地分配到所有节点上,而远程节点策略则将内存分配到远程节点上,以减少节点之间的数据传输。 可以使用 numactl 命令来管理 NUMA 内存分配策略,例如设置首选节点、查看节点信息、绑定进程等。在编写 NUMA 应用程序时,也可以使用一些库函数来控制内存分配策略,例如 numa_alloc_local() 和 numa_alloc_onnode() 等。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值