《What every programmer should know about memory》-NUMA Support译

Posted fanchenxinok

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了《What every programmer should know about memory》-NUMA Support译相关的知识,希望对你有一定的参考价值。

原文PDF: http://futuretech.blinkenlights.nl/misc/cpumemory.pdf

一二章参考博文:https://www.oschina.net/translate/what-every-programmer-should-know-about-memory-part1?lang=chs&p=6

目的在边学习边翻译让自己理解的更加深刻。

5. NUMA支持

在第二节中我们看到,在有些机器上访问特定区域的物理内存的开销因访问的起始位置不同而不同。这种类型的硬件需要操作系统和应用程序的特殊关注。我们将从NUMA硬件的一些细节开始,然后介绍Linux内核为NUMA提供的一些支持。

5.1 NUMA硬件

非一致内存体系结构(Non-uniform memory architectures)变得越来越普遍。在最简单的NUMA形式中,处理器可以拥有本地内存(参见图2.3),这比访问其他处理器的本地内存开销更小。这类NUMA系统的成本差别不大,即NUMA系数很低。

        NUMA也特别被用于大型机器。我们已经描述了让多个处理器访问相同内存的问题。对于普通的硬件,所有的处理器将共享同一个北桥(暂时不考虑AMD Opteron NUMA节点,它们有自己的问题)。这使得北桥成为一个严重的瓶颈,因为所有的内存流量都要经过它。当然,大型计算机可以使用定制的硬件来代替北桥,但是,除非使用的内存芯片有多个端口,即它们可以被多个总线使用,否则仍然存在瓶颈。多端口RAM的构建和支持既复杂又昂贵,因此很少使用。

        下一个复杂的地方是模型AMD使用的互连机制为没有直接连接到RAM的处理器提供访问接口。通过这种方式形成的结构的大小是有限的,除非可以任意增加直径(????)(即,任意两个节点之间的最大距离)。

                                                               图5.1  超立方体 

连接节点有效的拓扑结构是超立方体。超立方体限制节点的数量为2的C次方个,其中C表示内部每个节点之间互联的个数。对于有2^n个CPU的n个内部连接的系统,超立方体的直径是最小的。图5.1展示了前三个超立方体。每个超立方体C条最短直径。AMD第一代Opteron处理器的每个处理器有三个超传输连接。至少一个处理器必须有一个链路连接到的南桥,这意味着目前可以直接有效地实现C = 2的超立方体。下一代将在某一时刻有四个链接,此时C = 3个超立方体将成为可能。

        但这并不意味着不能支持更大的处理器集群。已经有公司研发出了crossbars允许更大的处理器集被使用。但是这些crossbars会增加NUMA系数,并且在某些数量的处理器上是无效的。

        下一步要做的是连接CPU组并为它们分配内存。所有这些系统都需要专门的硬件,绝不是普通的商用系统。这样的设计存在不同层次的复杂度。IBM x445和类似的机器是一个非常接近于商用的系统。它们可以像普通的带有x86和x86-64处理器4U、8路机器一样购买。然后,这些机器中的两台(有时多达四台)可以连接起来作为具有共享内存的单个机器工作。操作系统和应用程序必须考虑使用互连而引入的重要的NUMA系数。

        在光谱的另一端,像SGI s Altix这样的机器是专门为互连而设计的。SGI的NUMAlink互连结构速度快,同时具有低延迟;这两个属性都是高性能计算(HPC)的需求,特别是在使用消息传递接口(MPI)时。当然,缺点是这样的复杂性和定制化是非常昂贵的。它们使NUMA系数可能相当低,但是由于这些机器可能有(数千个)很多个cpu,而且互连的容量是有限的,因此NUMA系数实际上是动态的,根据工作负载可能达到不可接受的水平。

        更常用的解决方案是使用高速网络连接许多普通机器以形成集群。不过,这些不是NUMA机器;它们不实现共享地址空间,因此不属于这里讨论的任何类别。

5.2 OS支持NUMA

为了支持NUMA机器,操作系统必须考虑内存的分布式特性。例如,如果一个进程在给定的处理器上运行,分配给进程地址空间的物理RAM最好来自本地内存。否则,每个指令必须访问远程内存中的代码和数据。有一些特殊情况需要考虑,这些情况只出现在NUMA机器中。DSOs的文本段通常只在机器的物理RAM中出现一次。但是,如果所有cpu上的进程和线程(例如,基本的运行时库libc)都使用DSO,这意味着除了少数处理器外,所有处理器都必须进行远程访问。理想的操作系统应该将这些DSO镜像到每个处理器的物理RAM中,并使用本地副本。这个优化不是必须的,通常也很难实现。它可能不被支持,或者只是以有限的方式支持。

        为了避免使情况变得更糟,操作系统不应该将进程或线程从一个节点迁移到另一个节点。操作系统应该尽量尝试避免在普通的多处理器机器上迁移进程,因为从一个处理器迁移到另一个意味着缓存内容的丢失。如果负载分配需要从处理器迁移一个进程或线程,那么操作系统通常可以选择一个具有足够容量的新处理器。在NUMA环境中,新处理器的选择比较有限。新选择的处理器对正在使用的内存的访问成本不应高于旧处理器;这限制了目标列表。如果没有符合该标准的空闲处理器可用,那么操作系统别无选择,只能将进程或线程迁移到内存访问更昂贵的处理器。

        在这种情况下,有两种可能。首先,可以希望这种情况是暂时的,可以将进程迁移回更适合的处理器。另外,操作系统还可以将进程的内存迁移到最近使用的处理器的物理页上。这是一个开销相当大的操作。可能需要复制大量的内存,但并不一定要一步完成。在此过程中,至少在短时间内进程必须停止,以便正确地迁移对旧页面修改。要使页面迁移高效、快速,还有许多其他要求。简而言之,除非真的需要,否则操作系统应该避免内存迁移。

        通常,我们不能假设NUMA机器上的所有进程使用相同数量的内存,正如,随着进程在处理器之间的分布,内存使用也会均匀分布。事实上,除非在机器上运行的应用程序非常特殊(在HPC世界中很常见,但在外部则不然),否则内存使用将非常不均衡。一些应用程序将使用大量内存,而其他应用程序几乎没有内存。如果内存总是分配给发出请求的处理器,这迟早会导致问题。系统最终将耗尽运行大型进程的节点的本地内存。

        为了解决这些严重的问题,内存在默认情况下不完全分配给本地节点。为了利用所有的系统内存,默认的策略是对内存进行分区。这保证了系统所有内存的平等使用。其副作用是,可以在处理器之间自由迁移进程,因为对所有使用的内存的访问平均的开销不会改变。对于较小的NUMA系数,分区化是可以接受的,但仍然不是最优的(参见第5.4节中的数据)。

        这种方式可以协助系统避免严重的问题,并使其在正常运行下更容易预测。但它确实会在某些情况下严重降低整个系统的性能。这就是为什么Linux允许每个进程选择内存分配规则。一个进程可以为它自己和它的子进程选择不同的策略。我们将在第6节中介绍可用于此目的的接口。

5.3 发布信息

内核通过pseudo文件系统sys发布关于处理器缓存的信息如下:

/sys/devices/system/cpu/cpu*/cache

在6.2.1小节我们将看到被用来获取各个缓存大小的接口。这里重要的是缓存的拓扑结构。上面的目录包含子目录(命名为index*),其中列出了关于CPU拥有的各种缓存的信息。就拓扑而言,type、level和shared_cpu_map是这些目录中的重要文件。对于Intel Core 2 QX6700,信息如表5.1所示。

                                                表5.1  Core2 CPU缓存的sysfs信息 

这些数据的意义如下:

  •  每个核有三个缓存: L1i,L1d,L2。
  •  L1d和L1i缓存不能和其他核共享,每个核拥有自己的一系列缓存集。这个由只有一个bit位的shared_cpu_map位映射表示。
  •  CPU0和CPU1的二级缓存是共享的,CPU2和CPU3也是。

如果CPU有更多级的缓存,就会有更多的index*目录。

                                            表5.2 OpternCPU缓存的sysfs信息

对于一个四套接字的双核Opteron计算机,缓存信息如表5.2所示。我们可以看到这些处理器也拥有三级缓存:L1i, L1d, L2。没有核共享任何级别的缓存。有意思的是这种系统的处理器拓扑结构。没有这些信息很难理解缓存数据。sys文件系统在以下的文件中暴露这些信息。

/sys/devices/system/cpu/cpu*/topology

表5.3显示了SMP Opteron机器在这个目录结构中的有趣文件。

                                            表5.3  OpternCPU拓扑结构的sysfs信息 

结合表5.2和5.3我们可以看出:

a) CPU都没有超线程(thread_siblings有一个位被置位)

b) 实际上系统总共有四个处理器 (physical_package_id从0到3)

c) 每个处理器有两个核

d) 没有核共享任何的缓存。

这正是早期Opterons系统的样子。

到目前为止,所提供的数据中完全没有关于这台机器上NUMA的性质的信息。任何SMP Opteron机器都是NUMA机器。对于这些数据,我们必须查看存在于NUMA机器上的sys文件系统的另一部分,即下面的目录:

/sys/devices/system/node
此目录包含系统上每个NUMA节点的子目录。在特定节点的目录下有许多文件。前两个表中Opteron机器的重要文件及其内容如表5.4所示。

                                                                              表5.4  Opteron sysfs节点信息 

        这些信息把所有其他的处理器联系在一起;现在我们对这台机器的结构有了一个完整的了解。我们已经知道这台机器有四个处理器。每个处理器构成它自己的节点,可以从node*目录中的cpumap文件的值中设置的位看到这一点。这些目录中的distance文件包含一组值,每个节点对应一个值,表示在各个节点上访问内存的成本。在这个例子中所有本地内存访问成本为10,所有远程访问其他节点成本为20。这意味着,即使处理器被组织为一个二维超立方体(见图5.1),在没有互相连接的处理器之间访问开销也不是特别大。成本的相对值应该可用来估计访问时间的实际差异。所有这些信息的准确性是另一个问题。

5.4 远程访问开销

                                            图5.2  /proc/PID/numa_maps下的内容 

                                                图5.3  多个节点的读写性能

在AMD文件记录的在一台四socket机器的写操作的NUMA开销,数值如图5.3所示。从图中可以看出写比读慢,这并不稀奇,有意思的是1-Hop和2-Hop的开销。前两个Hop的开销确实有差别。从这个图表中我们需要记住的事实是,2 Hop读和写分别比0 Hop读分别慢30%和49%。2-Hop写比0 Hop写慢32%,比1 Hop写慢17%。处理器和内存节点的相对位置会造成很大的差异。AMD的下一代处理器将为每个处理器提供4个耦合的超传输链接。在这种情况下,一个四socket机器的直径是1。对于8 socket,同样的问题会以极大的程度返回,因为有8个节点的超立方体的直径是3。

        所有这些信息都是可用的,但使用起来很麻烦。在第6.5节中,我们将看到一个有助于更容易地访问和使用这些信息的接口。系统提供的最后一条信息是进程本身的状态。可以确定内存映射文件、写时拷贝页和匿名内存是如何分布在系统节点上的。对于每个进程,内核都提供了一个pseudo文件/proc/PID/numa_maps,其中PID是进程的ID,如图5.2所示。该文件中的重要信息是N0到N3的值,这些值表示为节点0到3上的内存区域分配的页面数。可以猜出程序是在节点3的核上执行的。程序本身和脏页是在该节点上分配的。例如ld-2.4.so和libc - 2.4.so和lcoale-archive在其他节点上分配的内存是只读映射。

        正如我们在图5.3中所看到的,当跨节点读时对于1 hop 和 2 hop的性能分别下降了9%和30%。对于执行来说,这样的读取是必须的,如果L2缓存丢失,每条缓存行都会增加额外的开销。如果内存距离处理器较远,那么对于超出缓存大小的大型工作负载所测量的所有成本都必须增加9%/30%。

                                                                                    图5.4 远程内存的操作        

为了在现实世界中看到效果,我们可以像第3.5.1节中那样测量带宽,但这次内存在远程节点上,只需要一跳的距离。将该测试结果与使用本地内存的数据进行比较,结果如图5.4所示。这些数字在两个方向上都有一些大的峰值,这是测量多线程代码问题的结果,可以忽略不计。该图中的重要信息是,读操作总是慢20%。这比图5.3中的9%慢得多,图5.3中的9%很可能不是不间断读/写操作的数字,可能指的是较早的处理器修订版本。对于适合缓存的工作集大小,写和复制操作的性能也降低了20%。对于超过缓存大小的工作集,写性能并不比本地节点上的操作慢多少。互连的速度快得足以跟上存储器的速度。最主要的因素是等待主存的时间。

有翻译不对的地方麻烦之处,谢谢。

以上是关于《What every programmer should know about memory》-NUMA Support译的主要内容,如果未能解决你的问题,请参考以下文章

《What every programmer should know about memory》-What Programmers Can Do译

《What every programmer should know about memory》-What Programmers Can Do译

《What every programmer should know about memory》-Virtual Memory译

《What every programmer should know about memory》-NUMA Support译

《What every programmer should know about memory》-CPU Caches译

8 Traits of an Experienced Programmer that every beginner programmer should know