深入浅出NUMA架构

一:为什么要有NUMA架构

        计算机硬件经过几代的迭代发展,进入了多处理器多核心的时代。在经典的计算机系统中一般都有两个标准化的部分:北桥(North Bridge)和南桥(South Bridge)。它们是处理器和内存以及其他外设沟通的必经之路,处理器和内存系统通过前端总线(Front Side Bus, FSB)相连,当处理器需要读者读取或者写回数据时,就通过前端总线和内存控制通信。

深入浅出NUMA架构_第1张图片

计算机系统中的南北桥示意图

        在互联网上找了一张南北桥的实物图如下,可以看到北桥在物理位置上也更靠近CPU。

深入浅出NUMA架构_第2张图片

在这种系统中,所有的数据交互都需要通过北桥:

1,处理器访问内存需要通过北桥

2,处理器访问所有的外设都需要通过北桥

3,处理器直接的数据交互也需要通过北桥

4,挂在南桥的所有设备访问内存也需要通过北桥

        可以看到,这种系统的瓶颈就在北桥中。当北桥出现了拥堵,所有的设备和处理器都要瘫痪。这种设计的另外一个瓶颈体现在对内存的访问上,不管是处理器还是显卡,还是南桥的硬盘网卡等,都需要频繁的访问内存,当这些设备都争相访问内存时,增大了对北桥带宽的竞争,而且北桥到内存之间也只有一条总线。为了改善对内存的访问瓶颈,中间也迭代了另外的系统设计,但是只要不改变南北桥的集中设计,瓶颈依然存在。

在这里得提到一个概念SMP,SMP系统最初是在20世纪90年代由Unisys、Convex Computer(后来的HP)、Honeywell、IBM等公司开发的一款商用系统,该系统被广泛应用于Unix类的操作系统,后来又扩展到Windows NT中,该系统有如下特点:
1,所有的硬件资源都是共享的。即每个处理器都能访问到任何内存、外设等。
2,所有的处理器都是平等的,没有主从关系。
3,内存是统一结构、统一寻址的(UMA,Uniform Memory Architecture)。
4,处理器和内存,处理器和处理器都通过一条总线连接起来。

        SMP的问题也很明显,因为所有的处理器都通过一条总线连接起来,因此随着处理器的增加,系统总线成为了系统瓶颈,另外,处理器和内存之间的通信延迟也较大。为了克服以上的缺点,才应运而生了NUMA(Non-Uniform Memory Architecture,非一致性内存架构)架构

二:什么是NUMA架构

NUMA是起源于AMD Opteron的微架构,同时被英特尔Nehalem架构采用。首先来看一下NUMA系统的示意图,这样会更好理解一些

深入浅出NUMA架构_第3张图片

        在这种架构下,一个配有四CPU的机器中,不需要一个复杂的北桥就能将内存带宽增加到以前的四倍。在这个架构中,处理器和本地内存之间拥有更小的延迟和更大的带宽,而整个内存仍然可作为一个整体,任何处理器都能够访问,只不过跨处理器的内存访问的速度相对较慢一点。同时,每个处理器都可以拥有本地的总线,如PCIE、SATA、USB等。和内存一样,处理器访问本地的总线延迟低,吞吐率高;访问远程资源,则延迟高,并且要和其他处理器共享一条总线。

        有必要详细解释一下NUMA架构下的内存访问缺点,这样我们才能在实际的开发中更好的理解相关的设计来屏蔽这种缺点,在以上的示意图中,访问内存所花的时间假如没有做任何设计的话存在很大的不确定性,和处理器强相关。因为该架构下每个处理器都有自己的本地内存,访问本地内存的时间很短,但是访问远程内存,即其他处理器的本地内存,需要通过额外的总线。比如CPU1要访问的内存在CPU4的本地内存,则要通过CPU2和CPU4或者CPU3和CPU4,怎么也得跨两个CPU,才能访问到目的内存。这样在时间的消耗上可能比本地内存要多出几百上千个时钟周期。

三:两种架构比较

        和SMP系统相比,NUMA系统访问本地内存的带宽更大,延迟更小,但是访问远程的内存成本相对就高多了。因此,我们要充分利用NUMA系统的这个特点,避免远程访问资源。

四:DPDK在NUMA架构下的设计举例

        以下是DPDK在NUMA系统中的一些实例。
1,Per-core memory。一个处理器上有多个核(core),per-core memory是指每个核都有属于自己的内存,即对于经常访问的数据结构,每个核都有自己的备份。这样做一方面是为了本地内存的需要,另外一方面也是因为上文提到的Cache一致性的需要,避免多个核访问同一个Cache行。
2,本地设备本地处理。即用本地的处理器、本地的内存来处理本地的设备上产生的数据。如果有一个PCI设备在node0上,就用node0上的核来处理该设备,处理该设备用到的数据结构和数据缓冲区都从node0上分配。以下是一个分配本地内存的例子:

/* allocate memory for the queue structure */
q = rte_zmalloc_socket("fm10k", sizeof(*q),  RTE_CACHE_LINE_SIZE, socket_id);

你可能感兴趣的:(Others,numa,DPDK)