NUMA为何物?对系统性能有什么影响?
NUMA(全称是Non-Uniform Memory Access,就是非统一内存访问)的诞生背景可以追溯到上世纪90年代,当时计算机系统逐渐向多处理器、多线程、多任务的方向发展,传统的UMA内存设计已经无法满足多处理器系统的需求。在UMA中,所有的处理器共享同一个内存资源,随着处理器数量的增加,内存访问延迟也会增加,这限制了系统的性能扩展。

为了解决这个问题,NUMA设计被提出。它打破了传统的统一内存访问模式,将处理器和内存分组到一起,每个组成为一个节点。每个节点内的处理器可以快速访问自己节点内的内存(Local Access),但需要经过互联访问其他节点的内存(Remote Acess)。这种设计使得多处理器系统的内存访问效率更高,同时也可以更好地扩展。

简单一点,可以把NUMA想象成一个公司,里面有多个部门,每个部门有自己的办公室,大家都要去自己的办公室干活儿。各部门之间虽然有走廊互通,但是走到别的部门可得花点时间。
那么,在这样的公司里,如果一个部门的人要找另一个部门的人,就得先穿过走廊,走到对方的部门。这样一来,找不同部门的人所花的时间就不一样了。也就是说,距离越远,时间越长。
NUMA就是这样,每个处理器都有自己的内存区域,就像每个部门有自己的办公室一样。处理器要访问其他处理器的内存,就得通过一些“走廊”来互通,这样花费的时间就长了。
但是这样的设计也有好处,那就是可以减轻“交通堵塞”。因为每个处理器只能访问自己的内存区域,所以就不会像在统一内存访问(UMA)的设计中那样,所有的处理器都争抢同一块内存资源,造成拥堵。

所以呢,NUMA就像是有一个走廊的公司,虽然找不同部门的人要花点时间,但是可以避免大塞车。同时呢,这个走廊也有个好处,就是可以让不同部门的员工互相交流,保持公司内部的沟通畅通。

NUMA的原理主要基于以下几点:
- 局部性原理:在多处理器系统中,处理器倾向于访问相邻的内存地址,因为这样可以减少延迟。局部性原理使得处理器可以在访问一个内存地址时,预测到其邻近内存地址上的数据。
- 缓存层次结构:为了实现局部性原理,多处理器系统通常采用多层缓存结构。处理器访问的内存数据首先被加载到缓存中,然后由缓存中的高速存储器提供快速访问。这样可以确保处理器在访问某个内存地址时,所需的数据已经在缓存中。
- 非一致访问:由于内存访问速度受到带宽限制,不同处理器上的内存访问速度可能有很大差异。在NUMA架构中,每个处理器都拥有自己的本地内存,这意味着它们访问本地内存的速度要快于访问其他处理器的本地内存。这种现象被称为非一致访问。
- 跨节点通信:为了解决非一致访问带来的性能问题,NUMA引入了跨节点通信机制。当处理器需要访问其他处理器的本地内存时,它需要将数据发送到目标处理器所在的节点。这个过程会增加额外的延迟和开销,但可以确保数据的一致性访问。

NUMA的应用主要包括以下几个方面:
- 内存分区:为了实现非一致访问和跨节点通信,NUMA系统通常采用内存分区策略。在这种策略下,系统被划分为多个逻辑节点,每个逻辑节点包含一定数量的处理器和本地内存。处理器和本地内存被分配到不同的逻辑节点中,从而实现非一致访问和跨节点通信。
- 数据分片:为了减少跨节点通信的开销,NUMA系统通常采用数据分片策略。在这种策略下,大型数据集被分成多个较小的片段,每个片段存储在不同的逻辑节点上。当处理器需要访问某个数据片段时,它可以将其加载到本地缓存中,而无需进行跨节点通信。
- 负载均衡:为了提高系统的性能,NUMA系统通常采用负载均衡策略。在这种策略下,处理器根据其处理能力和任务需求被分配到不同的逻辑节点上。这样可以确保每个逻辑节点上的处理器都在正常工作负载下,从而提高整体性能。
- 优化算法:针对NUMA架构的特点,可以开发一系列优化算法来提高系统性能。这些算法主要包括数据局部性分析、缓存替换策略、预取策略等。通过应用这些优化算法,可以在NUMA架构下实现更高的性能水平。
此外,在linux系统性能中,Linux page cache的访问速度跟numa绑定策略也有关系。Linux内核中的page cache是一种用于管理内存的机制,它通过将数据缓存在本地内存中,可以减少对磁盘的访问次数,从而提高文件系统的性能。
在NUMA体系结构中,page cache的访问速度受到NUMA节点的影响。如果一个进程的内存被绑定到特定的NUMA节点上,那么该进程访问该节点内的本地内存会比访问其他节点上的远程内存更快。
想象一个城市,每个居民都有自己的房子。这些房子分布在城市的各个区域,每个区域都有一个管理者,称为NUMA节点。

现在想象一下,你是一个快递员,负责给这个城市的居民送包裹。你有一个智能的送货系统,叫做Linux page cache。这个系统非常智能,它会根据你的居民的分布情况,把包裹存储在离他们最近的仓库中,以便更快地送达。
在这个城市中,有些居民非常喜欢在网上购物,他们会不断地收到各种各样的包裹。由于包裹太多,你需要在城市中建立更多的仓库来存储这些包裹,以便更快地送达。
然而,有些居民住在城市的这个区域,而仓库建在另一个区域,那么你就要花更多的时间和精力,穿过整个城市去取包裹,这就像访问远程内存一样。而有些居民的房子就在仓库旁边,你就可以更快地把包裹送达给他们,这就像访问本地内存一样。
因此,NUMA节点和Linux page cache的访问速度之间存在一定的关联。在NUMA体系结构中,优化仓库的分布可以提高快递员取包裹的速度,从而更快地把包裹送达给居民。
NUMA Node查询/配置的命令:
- 查询NUMA节点信息:可以使用numactl --hardware命令查看系统的NUMA节点信息。例如:
numactl --hardware
输出结果中会显示系统的NUMA节点数量、CPU核数等信息。
- 设置NUMA策略:可以使用numactl命令设置进程的NUMA策略。例如,要将进程绑定到特定的NUMA节点上,可以使用以下命令:
numactl --cpunodebind=0 --membind=0 <command> <process_id>
其中
以下是一些具体的示例,演示如何使用Linux的NUMA节点绑定策略:
- 使用cpuset命令将进程绑定到特定的CPU核心上:
$ sudo taskset -c 0,1 <command>
上述命令将进程绑定到CPU核心0和1上。
- 使用memslot命令将进程绑定到特定的内存槽上:
$ sudo taskset -m 0:1 <command>
上述命令将进程绑定到内存槽0和1上。
- 使用nodemask命令将进程绑定到特定的NUMA节点上:
$ sudo taskset -N <node> <command>
上述命令将进程绑定到NUMA节点上。
- 使用affinity命令将进程绑定到特定的CPU核心或CPU集上,并指定内存分配策略:
$ sudo taskset -a 0,1 <command>
上述命令将进程绑定到CPU核心0和1上,并指定内存分配策略为memslot。
- 使用taskset命令将进程绑定到特定的CPU集上,并指定内存分配策略:
$ sudo taskset -t 0-11 <command>
上述命令将进程绑定到CPU集0到11上,并指定内存分配策略为memslot。
- 使用numactl命令进行更高级NUMA节点绑定和内存分配策略:
$ numactl --cpunodebind=0 --membind=0 <command>
上述命令将进程绑定到NUMA节点0上,并指定内存分配策略为memslot。
