CPU & Memory, Part 4: NUMA support

博文:https://chanjarster.github.io...node

原文:What every programmer should know about memory, Part 4: NUMA supportgit

5 NUMA Support

先回顾一下Section 2。github

5.1 NUMA Hardware

Figure 2.3是最简单的NUMA形式,处理器能够有本身的本地内存,访问本地内存和其余处理器的本地内存的开销区别不大,即NUMA factor比较低。多线程

Figure 2.3: Integrated Memory Controllerapp

对于商业机器来讲,全部处理器共享访问相同的内存,即全部处理器共享同一个北桥,致使全部流量都从北桥走,致使北桥成为瓶颈。虽然能够用定制化硬件代替北桥,可是内存芯片必须得支持多port才行,可是Multiport RAM很复杂很贵,因此几乎没有人会使用。socket

Figure 5.1: Hypercubespost

一个高效的节点(处理器)拓扑是超立方体,它限定了节点数量为2C,C为每一个节点的interconnect接口数量。超立方体拥有2n处理器系统的最小直径(两节点之间的最大距离)。看Figure 5.1,每一个处理器的直径为C,这个是最小值。性能

后面是已知的几种NUMA的硬件实现,这里就不写了。测试

5.2 OS Support for NUMA

操做系统在分配内存的时候必须将NUMA考虑进去。好比一个进程运行在一个处理器上,那么为其所分配的内存应该来自于本地内存,不然全部的code和data都必须访问远程内存才能够。有一些特殊状况只有在NUMA下才须要考虑。text segment of DSOs一般状况下在物理内存中只存有一份,但若是DSOs被全部CPU的线程或进程所使用(好比libc),这就意味着少数处理器以外的全部处理器都要发生远程访问。理想状况下,操做系统会将DSO在每一个处理器的本地物理RAM里作mirror,这是一种优化策略,不是一个要求,一般也很难实现。优化

操做系统不该该将一个进程或线程迁移到另外一个处理器上。不过操做系统应该已经作了相关防范,由于迁移发生就意味着cache就要从新抓。若是为了负载的均衡,必定要把进程或线程从一个处理器迁移到另外一个处理器,操做系统一般会随便选一个还剩有容量的处理器。不过在NUMA环境下,操做系统的选择有一点限制:被选择的新处理器内存访问开销不能比旧处理器大。若是找不到合适的处理器,那么只能使用开销更大的处理器。

针对上面的状况有两个可能的方法。第一个方法,咱们能够指望这个状况是暂时的,以后进程能够被迁移回开销更小的处理器。另外一个方法,能够把进程的内存页迁移到距离新处理器更近的地方。第二个方法的开销很大,由于要把大量内存从一个地方迁移到另外一个地方,并且迁移期间旧的内存区域是不能修改的,此外还有诸多限制。操做系统应该尽可能避免使用这种策略,只有当没办法的时候才能采用。

为了解决上述进程或线程迁移的状况,默认状况下,内存不是独占地分配到本地节点上的。会在全部节点上分配内存,这样一来进程或迁移就不会产生额外的内存访问开销。对于较小的NUMA factor(NUMA所带来的额外开销)这个策略是可接受的,但仍然不是最优的(见 Section 5.4),并且它实际上下降了性能。因此,Linux容许每一个进程本身设定内存分配规则。

5.3 Published Information

内核经过sys伪文件系统提供处理器的cache信息:/sys/devices/system/cpu/cpu*/cache

在Section 6.2.1会介绍查询各个cache尺寸的接口。在这里咱们关注cache的拓扑结构。上述每一个目录都有一堆index*子目录对应不一样的cache,里面有typelevelshared_cpu_map这些文件。下面是Intel Core 2 QX6700的信息:

clipboard.png

Table 5.1: sysfs Information for Core 2 CPU Caches

从上面的数据能够看出:

  • 每一个核(为啥cpu0到cpu3都是核是从另外一个地方知道的)拥有三个cache:L1d、L1i、L2
  • L1d和L1i是每一个核独占的,This is indicated by the bitmap in shared_cpu_map having only one set bit.
  • cpu0和cpu1共享L一、cpu2和cpu3共享L2。

下面是一个four-socket, dual-core Opteron机器的cache信息:

clipboard.png

Table 5.2: sysfs Information for Opteron CPU Caches

从下面这个路径能够看处处理器的拓扑结构:/sys/devices/system/cpu/cpu*/topology

能够看到每一个核拥有本身的L1d、L1i、L2。

下表是SMP Opteron的处理器拓扑结构:

clipboard.png

Table 5.3: sysfs Information for Opteron CPU Topology

结合Table 5.2和Table 5.3,能够看到没有hyper-thread(the thread_siblings bitmaps have one bit set)。并且其实是4个处理器(physical_package_id0-3),每一个处理器有两个核。

任何SMP Opteron机器都是一个NUMA机器,咱们来看看NUMA信息/sys/devices/system/node。每一个NUMA节点都有对应的子目录,子目录里有一些文件。下面是前面提到的机器的结果:

clipboard.png

Table 5.4: sysfs Information for Opteron Nodes

因此咱们能够看到这个机器的全貌:

  • 这个机器有4个处理器。
  • 每一个处理器自成一个node,能够从cpumap文件里的bit看出来。
  • distance文件描述了访问每一个node的开销。本地开销为10,远程开销都是20。(不过这里的信息并不许确,至少有一个处理器要链接到南桥,因此至少有一对处理器的开销比20大)
  • 全部处理器构成一个二维超立方体(Figure 5.1)

5.4 Remote Access Costs

AMD文档里写了4插口机器的NUMA开销:

Figure 5.3: Read/Write Performance with Multiple Nodes

能够0 Hop、两个1 Hop、2 Hop的读写性能差别。不过这个信息不太容易使用,Section 6.5 会将更简单好用的方法。

咱们有可能知道memory-mapped files, Copy-On-Write (COW) pages and anonymous memory是如何分配在各个节点上的。每一个进程有本身的NUMA相关的文件/proc/<PID>/numa_maps,看Figure 5.2:

00400000 default file=/bin/cat mapped=3 N3=3
00504000 default file=/bin/cat anon=1 dirty=1 mapped=2 N3=2
00506000 default heap anon=3 dirty=3 active=0 N3=3
38a9000000 default file=/lib64/ld-2.4.so mapped=22 mapmax=47 N1=22
38a9119000 default file=/lib64/ld-2.4.so anon=1 dirty=1 N3=1
38a911a000 default file=/lib64/ld-2.4.so anon=1 dirty=1 N3=1
38a9200000 default file=/lib64/libc-2.4.so mapped=53 mapmax=52 N1=51 N2=2
38a933f000 default file=/lib64/libc-2.4.so
38a943f000 default file=/lib64/libc-2.4.so anon=1 dirty=1 mapped=3 mapmax=32 N1=2 N3=1
38a9443000 default file=/lib64/libc-2.4.so anon=1 dirty=1 N3=1
38a9444000 default anon=4 dirty=4 active=0 N3=4
2b2bbcdce000 default anon=1 dirty=1 N3=1
2b2bbcde4000 default anon=2 dirty=2 N3=2
2b2bbcde6000 default file=/usr/lib/locale/locale-archive mapped=11 mapmax=8 N0=11
7fffedcc7000 default stack anon=2 dirty=2 N3=2

Figure 5.2: Content of /proc/PID/numa_maps

主要看N0和N3的值,这个是分配到node 0和3的页的数量。因此能够大概猜到进程执行在node 3的核心上。read-only mapping,好比第一个ld-2.4.solibc-2.4.solocale-archive则分配在别的node上。

下面是真实的测试,和Figure 5.3的数据作比较,不过测试的是1 hop远程访问:

Figure 5.4: Operating on Remote Memory

能够看到read老是比本地访问慢20%,这个和Figure 5.3的数据不符合,到底为啥只有AMD本身知道了。图里的几个尖刺能够忽略,这是由于测量多线程代码自己的问题。

看write的比较,当working set size可以放进cache的时候,也是慢20%。当working set size超出cache的的时候,write和local访问差很少,这是由于此时会直接访问RAM,访问RAM的开销占大头,interconnect开销占小头。

相关文章
相关标签/搜索