ffwd:delegation is much faster than you think 阅读笔记_GeorgeLuo

本文中的server,client应该是进程,在本文中用服务方、客户方来指代。

本文的socket和core应该是NUMA下的socket(插槽)和core(内核),尤其注意socket不是“套接字/口”的意思。

 

摘要:我们重新考察关于委托模式与同步访问共享内存(synchronizedaccess to shared memory)的性能比较的问题,(进程是资源分配和拥有的单位,同一个进程内的线程共享进程的资源。多进程(多线程)间进行数据共享的时候,共享内存是一种常见的选择。在传输大量数据时,其他inter-process communication进程间通信方式的许多模式,比如磁盘文件、信号、套接字(socket)、管道、消息队列等等,性能都不如共享内存。共享内存是一段可被多个进程共享的物理内存。各进程在使用之前,需要将这段物理内存映射到本进程的虚拟地址空间。多个进程同时读写共享内存时,由于读写顺序无法保证,可能导致数据错乱。因此要引入同步机制来保证读写以一定的顺序执行。一种方法是使用锁。使用互斥锁能可以实现进程间的互斥,可以用文件记录锁来锁定文件的部分或者全部,可以用自旋锁(6.1)(在抢不到锁时进入自旋等待,直到别的进程或者线程释放锁),使用无锁结构(6.3)可以避免同步操作(类似生产者-消费者模型中的无锁队列))通过分析和演示发现,在一系列常见环境下,相比上锁的模式,委托方式的速度更快。从首条准则开始,我们提议(使用)快速的,轻量级的委托(ffwd)。经过高度优化的对ffwd的设计使得它能够显著地比之前关于委托的工作表现得好,同时保持了可扩展性的优势。在用6个基准应用程序和6个共享的数据结构测试的实验中,我们将ffwd同上锁(lock)、结合(combining,后文中提到过)、无锁(lock-free)、软件事务内存(STM)、委托设计这些的选择组合进行比较,在4类不同的多插槽(multi-socket)系统运行至多128个硬件线程运行。(软件事务内存是一种并发控制机制,模拟数据库事务的机制,总之在并行计算时对共享内存的访问控制)总体上,我们发现,ffwd常常为现有的工作提供一个简单的,高度竞争化的选择。按照定义,一个完全委托的数据结构的系统的性能,是受到所述的数据结构的单线程吞吐量的限制的。然而,由于缓存效应(硬件在读数据时,先从高速缓存中找数据读取,如果找不到再从内存中找数据;在并发多线程时,可能会造成缓存命中率下降的问题),许多数据结构在被限制为单线程时表现最佳。(单/多线程应该是指服务方的单/多线程)通过高效的委托机制,我们在多线程设置中实现了这种单线程的性能。在应用程序级别的基准测试程序中,我们发现了相比之前测试了的最好的解决方案【RCL论文是最早的使用委托系统这个技术的】(ffwd)有着100%的性能提升;而且多个micro-benchmark(针对单个应用中的某部分代码,通过在相同负载或者环境下执行轻量级的性能测试,比较测试结果,根据性能评分得出结论的过程)https://gist.github.com/zhanhai/96890df0f3a794e5fda5)在X5-10 倍的范围内也有提升。 (委托模式在另一篇论文:Message Passing or Shared Memory: Evaluating the Delegation Abstraction for Multicores中提到。在委托模式中,访问数据结构需经过一至多个服务方线程,只有它们有权直接处理数据。即使所有的线程共享了内存,客户方和服务方线程也是通过一个报文传送协议来通信的,该实现则是利用了底层共享内存进行优化的。当客户方线程需要对数据结构进行操作时,它通过向服务方线程发送请求信息来委托这个操作;当服务方线程收到这个委托时,它直接在该数据结构上执行操作,并将返回的结果存入客户方分配的缓冲区中)。

 

1.介绍

随着处理器制造商越来越依赖于用多核设计提高系统性能和能源效率,安全高效地获取共享变量和数据结构的重要性在持续增长。最常见的解决方案是互斥或者锁定,这时,在进入一个重要的、共享内存中的数据结构能够被读取的区域之前,原子指令被用来获取一个锁定。有效的,可扩展的锁的设计有着很长的历史,并且至今是科研领域的一块活跃区域。

通过锁定整个共享数据结构这种别名粗粒度锁定(coarse-grained locking)的技术,性能上限是由数据结构的单线程吞吐量决定的。(粗粒度的上锁确保了线程安全(线程安全就是多线程访问时,采用了加锁机制,当一个线程访问该类的某个数据时,进行保护,其他线程不能进行访问直到该线程读取完,其他线程才可使用。不会出现数据不一致或者数据污染。线程不安全就是不提供数据访问保护,有可能出现多个线程先后更改数据造成所得到的数据是脏数据。),却无法受益于并发带来的速度提升)几种方式用来通过多线程为数据结构提供并发访问,包括细粒度的上锁方式,无锁的数据结构和软件事务储存器。此处,细粒度的锁定和无锁的方式需要对特定数据结构进行(有时是粗放的)修改,而软件事务存储器提供事务处理的语义,它能自动允许对任何共享的数据结构进行并发的独立的操作。(语义与语法的区别:The man is a dog.这句的语法是对的,主谓宾齐全,句法正确;但是语义上不成立)

通过委派,线程(服务方)代表多个客户端线程进行操作。Combining是一种委托形式:线程暂时担任服务器一角,将它们自己的临界区和其他一至多个等待着解除锁状态的线程结合,来提高效率(::减少了访问的次数嘛)。传统的委托方法使用一至多个专用的服务器线程。此处,服务器有专门的访问数据结构的路径,并且通过一个信息接口和客户端交互。将一个硬件线程专用于一个委托服务器是一个重大的牺牲。然而,较之结合(combining),这个选择也使得更多有效的执行得以实现。

一个有效的委托系统旨在,在多线程设置的状态下提供单线程数据结构的性能。因此对于委托,最有吸引力的数据结构就是那些在单线程上表现最好的结构了。相反的是,高度并行的数据结构通常并不会从委托中获益。我们的系统——快速、轻量级的委托(ffwd,发音为fast-forward)是一个的在低延迟、高吞吐率方面经过高度优化的精简的委托的实现。在轻量级性能测试方面,Ffwd提供相比目前委托方面最佳水平(RCL)至多x10倍的吞吐率;在应用层级的测试程序方面则至多能达到目前最佳水准。至于上锁及其他方式,对于那些适用于委托的数据结构,ffwd常能提高10倍或更多的性能。

Ffwd通过有效地隐藏处在服务器和客户线程之间的互联的高延迟来达到这种性能。这是通过将指令级并行和仔细管理内存访问相结合来实现的。(https://www.zhihu.com/question/21823699 关于指令级,数据级,线程级并行)在应用的技术中,我们避免在服务器(进程)使用原子指令使得指令能够重新排序,我们将请求与响应打包到基于客户(进程)间距的缓存行对中,缓冲区对最小化缓冲一致性流量(cache coherence traffic)(http://www.infoq.com/cn/articles/cache-coherency-primer缓存一致性入门)做出响应等等。本篇论文的主要贡献为:

(1)    ffwd的设计与实现,就我们所知目前为止最快的委托系统。在每个请求大约40个周期的服务器端额外的开销下,在多线程设置中,ffwd委托的数据结构非常接近单线程性能。

(2)    从架构角度对委托和锁定的性能界限进行低级分析,为进一步改进打下基础。

(3)    一个公开的ffwd实现,包括一个通用的api,以及一系列通向委托(系统)的基准测试程序,以便社区复现和改进我们的结果。

之后,我们在第二章中分析了限制委托系统的因素,并与一个有关粗粒度锁定的相似分析进行比较。我们接着在第三章中描述了ffwd的设计细节,并在第四章中进行了性能的比较和评估。在第五章汇总我们讨论了如何将现有程序端口接入ffwd,在第六章和第七章分别是

文献(相关工作)回顾与总结

 

2.锁定与委托的性能界限(从曲线在何处相交可以看出)

接下来我们讨论锁定与委托的性能限度。简略地讲,单锁性能受限于互联的延迟(::不断等待锁的释放,所以需要一直访问,互联的延迟就会有很大的影响——互联延迟+锁的时间)。同时,委托主要受限于服务方的处理能力(单线程吞吐量)。

(临界区:每个进程/线程中访问临界资源的那段程序;临界资源是一次仅允许一个进程/线程使用的共享资源。)


图一显示了随着临界区(critical section)长度(此例中,就是空的for循环的迭代)的变化,在几种竞争的方式下,每秒被执行的临界区的数目(Mops)。

在此,单线程的基准程序(::测试程序)由外层循环组成,这个外层循环重复地调用一个包含空的for循环的函数。这显示了在委托性能方面的上限,这个上限对于单次迭代的临界区高达320Mops(million operations per second,每秒百万次操作)。MUTEX和MCS程序使用128个硬件线程,竞争一个共享的(pthread mutex/MCS)锁定,在释放锁定之前执行一个临界区。Ffwd和RCL程序是基于委托的。Ffwd使用120个客户方线程,以及一个服务方线程。客户方将内层循环委托给服务方。

值得注意的是,委托(ffwd)的表现明显优于为短临界区上锁,但不及单线程程序的表现。单线程吞吐量限制了委托的吞吐量上限,同时有几个因素会限制委托系统的性能,它们包括互连带宽,互连延迟,存储缓冲区容量和消息解编组开销,下面分别进行讨论。

考虑一个理想的系统。假设没有背靠背采集,单锁吞吐量的最大值为(这里的表达式估计是仅仅反映一个反比关系),l是单向互联延迟的均值,而Clock是临界区的平均持续时间。当clock趋于0时,锁定的吞吐量由总线延时决定,总线延时在我们系统中则是一个近似值(?后几句表示这个延时是怎么估计出来的)。socket(套接口?应该是插槽的意思)之间大约为200ns。单个套接口的内核间为80ns。这分别对应于单锁的5Mops和12.5Mops吞吐量。只有通过增加锁的数目,一个锁定程序才能超过这个基本限制。锁定显式地序列化了临界区的协调和执行,委托序列化了委托函数(功能)的执行,但是将请求与响应放到了实现部分。在Intel/AMD架构上的互联带宽往往与总线延时有很高的相关性,差不多达到了25Gbyte/s,或者在单链接上每秒3.9亿缓存行(cache line)。因此,一个利用了这个的部分带宽的实现能够获得巨大的性能提升。我们现在更为细致地讨论一些在委托的性能方面的限制。(::讨论一些影响因素的值的大小)

(1)    互联带宽

在我们的评估系统中,单个连接的互联带宽为1.5-3.9亿缓存行/秒。对于下界,考虑我们最慢的互联的单个连接,在每个direction(流向?)每次请求一个(一次可以请求一至多个)缓存行(cache line:Cache Line可以简单的理解为CPU Cache中的最小缓存单位)。保守地说(不把插槽带宽计算在内),每个连接的带宽界限为75Mops。我们的系统在每个socket上有两个连接,总计150Mops。更高效的设计,更快的连接和使用多服务方(线程)能一道显著提高这一数值。

(2)    互联延迟

委托(系统)需要一个在互联总线上的往返:一个方向为请求,另一个为响应。因此,每个客户吞吐量的最大延迟为1/2l,或者对于套接口间的交流为2.5Mops。

(3)    互联并行:存储缓冲

为了突破单客户进程的吞吐量,多请求/响应对需要并行地遍历高延迟的互联。客户方自然地并行发送请求,但是单服务方是更为受限的。此处,一些存储缓冲区一直存在,它们对于缓存相关的存储区进行保存与排序,直到相关的缓存行变为本地可写的。在我们的Broadwell CPU上存储缓冲区支持42个并发的in-flight(completed but not-yet-retired stores完成而尚未回收的,https://en.wikipedia.org/wiki/Memory_disambiguation)存储器。因此,假设每个响应一个存储器,并且所有的in-flight存储区都用作响应,吞吐量极限为42X2.5=105Mops。

(4)    解编组开销

最后,服务方处理时的吞吐量是运行在委托服务方上的时候临界区长度(正比与临界区运行时间)Cdel的一个函数,而解编组开销为Odel。每个请求至少需要:加载请求,将参数读入寄存器,调用委托函数,写一个响应。因此,运行时吞吐量的最大值为。委托服务器究竟有多么有效还不得而知。然而,在一个2.2GHz 的CPU上我们现已达到55Mops,即每次请求40个循环,而锁定则在每次请求中需要超过450次循环。

总之,使用锁定的话,当运行在单个套接口上时,吞吐量局限于单次锁定5Mops,即12.5Mops。使用委托的话,性能主要受限于服务器的处理能力,以及在每个委托函数上花费的处理器循环的数目。

2.1临界区更大时的性能界限

更长的临界区自然更加地普遍。对于更长的临界区,在并行交流方面委托的优势就消退了,正如图1中所示。然而,图中未显示的是委托方式的潜在内存局部性的优势

图2显示了一个调查内存访问效果的相似实验的结果。

在此,临界区刷新了一些在1Mb静态分配的数组中可变数量的随机选择的元素。在此,元素缺乏竞争以及LLC缓存的有效使用使得ffwd实质上比其他方式的表现都要好。

2.2何时使用ffwd

基于以上的分析,我们希望:只要硬件线程的数目足够大,使得为服务方设置一个线程不是不合理的,委托能在多核系统上也能产生如一的结果——优于粗粒度锁定的表现。细粒度锁定能够在正确的环境下表现的比委托更好。尤其是长临界区由于提供了额外的并行性,更适于细粒度锁定。对于短临界区,委托由于单锁定吞吐量的界限(我们系统上约为5Mops)而似乎是更好的选择。除非数据结构能够被分得足够开来支持大量的独立锁定(比如哈希表)。

有时专门的数据结构设计能取得相当好的性能。在我们的评估中,我们将着重强调这样的几个数据结构。然而我们需要注意的是这些数据结构需要复杂的的工程上的努力付出,可是委托却常常能通过很小的代码改动提供高性能的一个选择。

目前在委托技术方面的最好成果RCL将重点放在了支持传统委托与相关的应用的重新设计。除去自动配置文件以及所述的代码重写功能不谈,RCL委托协议自身就是以重构为第一优先原则,性能为第二优先原则设计的。这在设计的一些细节中显而易见:RCL支持委托功能中的锁定和阻塞系统调用。RCL的名字,远程内核锁定(remote core locking)体现在:在委托一个临界区后,服务方仍获得了锁定,以便其他线程获得另一部分代码中的锁定时保持正确性。而且,请求上下文的使用对于自动重写是很便利的,但会导致较差的性能,因为服务方必须首先读请求,接着读指针传入的上下文。

Ffwd的设计更注重性能,而非重构传统的应用。因此,我们能取得较之RCL速度差不多10倍的提升,使得在更多程序运行时委托的性能都优于其他的方法。接下来我们描述的是更多的关于ffwd的细节。

 

3.快速,轻量级的委托(ffwd)

Ffwd给“把常规的c函数的执行,委托给远程服务方”这个功能,提供了API接口:客户方发送请求给服务方,指定一个函数及一系列参数值。它接着在服务方的响应中等待函数的返回值。

图3演示了在信息传递层级上ffwd的整个运行流程。

为了演示的目的,我们将讲述从我们的评估系统中(64B的缓存行,每个socket至多32个线程)选取出的系统常量方面的设计。然而,ffwd背后的设计原则在其他常量上也是通用的。

每个客户内核维护一个专用的128B的请求缓存行对。它们只能通过相应内核的硬件线程来写入,通过服务方读取。在写入它的请求行中之后,每个客户方线程在它的128B的响应行对中,在它的专门的响应槽中自旋。响应行对通过服务方专门写入,且通过给定插槽的多个内核共享来读入。

服务方在轮转调度、按插槽分批次的模式下处理请求,顺序询问所有的请求行并且在进入另一个插槽之前从这个插槽中处理任何新的请求。它局部地缓冲存储个体的返回值,直到现有响应所做的处理已经结束,接着将所有的响应写入组响应行对。(我理解的是在此如果某个插槽中所有请求均完成,那么双态位就会保持原值不变;否则要修改值,使得之后的请求被搁置)

每个请求包含一个双态位,一个函数指针,一个变量计数器和一个函数的至多6个变量。响应行对在一个单插槽上被至多15个客户方进程共享,它包含了每个客户方的双态位和8B长度的返回值。请求和共享的响应构成了一个服务方进程和单个插槽上客户方进程之间的至多15个独立的信道。双态位显示了每个独立的请求/响应信道的状态。如果对应于给定客户方的请求和响应的双态位的值不同,新的请求将被搁置。如果两者相等,那么响应已就绪。为处理请求,服务方加载提供到恰当寄存器的参数,并且调用指定的函数。

3.1设计的动机

Ffwd的几个核心方面罗列如下:随后是对于每个设计选择的讨论以及动机。

1)        分配为与128B对齐的行对的倍数。

2)        尽管多核可能会读入给定的行对,只有一个核一直向每个行对写入。

3)        在一个插槽上,几个客户方进程共享一个响应行对。

4)        服务方局部地缓冲响应,接着在一个连续的写的序列中,复制所有的响应到恰当的响应行对中。

5)        请求服务的开销达到最小:将参数加载到寄存器中,调用提供的函数指针,接着将返回值复制到局部响应缓冲区中。

6)        服务方执行委托函数之前不会得到任何的锁定。

在不失一般性的情况下,考虑由单个服务方和至多15个客户方硬件线程构成的系统的一部分。在高层级上,信息传送的高性能的核心在于在互联上最小化高速缓存一致性通信量。(缓存一致性:使用多组缓存,但使它们的行为看起来就像只有一组缓存那样。缓存一致性协议就是为了做到这一点而设计的。就像名称所暗示的那样,这类协议就是要使多组缓存的内容保持一致。 “窥探”背后的基本思想是,所有内存传输都发生在一条共享的总线上,而所有的处理器都能看到这条总线:缓存本身是独立的,但是内存是共享资源,所有的内存访问都要经过仲裁(arbitrate):同一个指令周期中,只有一个缓存可以读写内存。窥探协议的思想是,缓存不仅仅在做内存传输的时候才和总线打交道,而是不停地在窥探总线上发生的数据交换,跟踪其他缓存在做什么。所以当一个缓存代表它所属的处理器去读写内存时,其他处理器都会得到通知,它们以此来使自己的缓存保持同步。只要某个处理器一写内存,其他处理器马上就知道这块内存在它们自己的缓存中对应的段已经失效。在修改本地缓存之前,就要告知其他处理器。)

(1)消除错误的共享

第一步是为了消除错误共享的任何来源。在Intel的Xeon架构的家族中,缓存行是64B的,但是每个内核的L2高速缓存包括一个空间(对)预读取器,它将内存视为128字节的行对(是由2个64B的高速缓存行构成的?),并且在其中一行被重新获取时自动预读取另外的64B。因此,独立的,无错误共享的内存访问(除了高速缓存的关联性,这个在此处不起什么作用的影响)只有在Xeon上的128B的粒度下,才是可行的。

(2)独立的,每核的请求行

我们在每个内核中分配一个128B的行对,它在两个由我们的3个Xeon设备支持的硬件线程中被等分。分配一个完整的128B对消除了任何的请求上的写竞争(因为client请求的大小为64B,所以直接写进去了,不存在竞争):唯一的竞争是正在写的客户方和正在读的服务方之间的竞争。服务方的读请求将缓存行转换为共享(S)的状态并且将内容复制来覆盖互联,而客户方的连续写使得服务方的复制无效(缺少数据转换),并将缓存行转换为可修改(M)状态。

(3)缓冲的,共享的响应行

对比之下,我们分配128B的响应行对给一组常见套接口的至多15个硬件线程共享,包括双态位和8B长度的返回值。在此,服务方的写使得在响应的插槽上所有响应行对的复制无效(应该是通过设置双态位为M来实现的)。当这个无效化处在运行状态的时候,服务方的缓冲的响应被写入本地内核的存储缓冲区中,实际上保证了整个128字节对能被写入而不会引发进一步的相干流量。双态位在最后被复制。首个客户方的连续读将缓存行对转换为S状态并复制数据。在同一插槽上的其他客户方的连续读则由它们本地的最底层缓存提供服务。简言之,每轮单个套接字至多为15个客户方提供服务,至多引发17个缓存行数据转换以及17个相应的互联上的缓存行无效化。

(4)为请求和响应行注意NUMA的分配  

请求行被分配到客户方的NUMA节点上,响应行被分配到服务方的NUMA节点上,我们创建这些节点来提供实质的性能优势。尽管直接测试缓存一致性流量(除了缓存未命中)是本论文范围之外的内容,我们假定这个NUMA分配策略避免了无效处理中的额外的步骤,也即相关的缓存本地代理总是和写者共享插槽。

(https://www.cnblogs.com/yubo/archive/2010/04/23/1718810.html关于NUMA,SMP,MPP的区别)

(从NUMA到CPU Topology(CPU拓扑结构) http://kodango.com/cpu-topology

NUMA和SMP(symmetric multi-processor对称多处理器系统)是两种不同的CPU硬件体系架构。SMP的主要特征是共享,所有CPU共享使用全部资源,比如内存、总线、I/O,多个CPU对称工作,彼此之间没有主次之分,平等地访问共享的资源,这样势必引入资源的竞争问题,从而导致它的扩展内力非常有限。

NUMA技术将CPU划分成不同的组(Node),每个Node由多个CPU组成,并且有独立的本地内存、I/O等资源。Node之间通过互联模块连接和沟通,因此除了本地内存外,每个CPU仍可以访问远端Node的内存,只不过效率会比访问本地内存差一些,我们用Node之间的距离(Distance,抽象的概念)来定义各个Node之间互访资源的开销。

(http://blog.csdn.net/ustc_dylan/article/details/45667227)NUMA: non uniform memory access architecture非统一内存访问是一种用于多处理器的电脑内存设计,内存访问时间取决于处理器的内存位置。在NUMA下,处理器访问它自己的本地存储器的速度比非本地存储器快一些。

随着多核技术的发展,我们将多个CPU封装在一起,这个封装一般被称为Socket(插槽的意思,也有人称之为Packet,不知到哪个更加准确?),而Socket中的每个核心被称为Core。为了进一步提升CPU的处理能力,Intel又引入了HT(Hyper-Threading,超线程)的技术,一个Core打开HT之后,在OS看来就是两个核,当然这个核是逻辑上的概念,所以也被称为Logical Processor,本文简称为Processor。

综上所述,一个NUMA Node可以有一个或者多个Socket,一个多核Socket显然包含多个Core,一个Core如果打开HT则变成两个Logical Processor。Logical processor只是OS内部看到的,实际上两个Processor还是位于同一个Core上,所以频繁的调度仍可能导致资源竞争,影响性能。

http://blog.csdn.net/leader1989/article/details/28424811

1.    什么是处理器?

在现代的定义中,'Processor'和 'CPU'(central processing unit)的含义其实是同一个东西,更精确的说,它们是指'处理器包',因为没有一个标准定义这个包到底应该包含什么。15-20年前的老CPU只包含有执行任务所需要的最小的资源。它包括运算器、取指令和解码硬件,指令管道、中断处理硬件,和部分IO控制硬件,此后,cache内存加入到CPU中用来提高执行效率。它的功能主要是解释计算机指令以及处理计算机软件中的数据。

2.    什么是核?

再往后,执行任务的processor(处理器)数量开始加倍。运算器、取指令和解码硬件,指令管道以及一些cache内存被整合起来成为我们今天所说的 ‘CORE’。每个’核’都可以运行单个程序(当它支持硬件线程比如IntelCPU的超级线程时也可以运行多个程序),维护正确程序的状态、寄存器和正确的执行次序,并通过运算器(ALU)来执行操作,核是CPU的基本计算单元。IO读取控制、中断处理,等等资源在所有的’核’之间共享。

在给定的时间内,一颗CPU/Processor(处理器)可以有多颗核执行任务,这些任务通常是操作系统调度的软件进程和线程。记住操作系统可能有多个线程在运行,但CPU只可以在给定的时间内运行一定数量(X)的任务数,X=CPU的核数*每核的硬件线程数,剩余的线程必须等待操作系统的调度,要么抢占当前正在运行的任务线程,或者其他情况

最近内存控制单元也加入到处理器包中,它居于’核’的一侧但不属于它。因此内存控制单元是处理器包的一部分,或者Processor/CPU的一部分,但不是’核’的一部分。Intel公司习惯用“非计算内核”来特指它。CPU包含核与外部的连接的相互联系,通常是一个大的’末级’共享cache,你可能需要很多其他的要素来让CPU工作,比如上面的内存控制器来和内存进行交互---(这就是上面说的内存控制单元),IO控制器来和存储交互(display,PCIe,USB)等等。此外CPU可能还集成了GPU,CPU设计越来越像我们所称的“SOC”—片上系统。

NUMA中的node,socket,core,thread:

Socket是主板上的CPU插槽,core是socket里独立的一组程序执行的硬件单元,比如寄存器,计算单元等;thread是超线程(hyperthread)的概念,是一个逻辑cpu,共享core上的执行单元。NUMA体系结构中多了Node的概念,这个概念其实是用来解决core的分组的问题,具体参见下图来理解:


(图中的OS CPU可以理解thread,那么core就没有在图中画出),从图中可以看出每个Socket里有两个node,共有4个socket,每个socket 2个node,每个node中有8个thread,总共4(Socket)× 2(Node)× 8 (4core × 2 Thread) = 64个thread。

   另外每个node有自己的内部CPU,总线和内存,同时还可以访问其他node内的内存,NUMA的最大的优势就是可以方便的增加CPU的数量,因为Node内有自己内部总线,所以增加CPU数量可以通过增加Node的数目来实现,如果单纯的增加CPU的数量,会对总线造成很大的压力,所以UMA结构不可能支持很多的核。单个 socket 配多 node 应为AMD cpu 若环境为 Intel cpu 则只支持单个cpu 单个 node。)

(5)最小解编组(demarshalling)开销

除了信息传递之外,服务方的请求的处理相对于执行工作的客户方必须产生最小的负担,否则服务方处理将很快成为性能瓶颈。为此,我们的服务方通过从请求中读取特定数量的参数到参数传递寄存器中来处理一个请求。它接着调用指定的函数,并最终为后续的组合传输来缓冲最终的返回值。

(6)无原子指令(no atomic instructions

最后,服务方不会在其操作中获取任何的锁定(权限)。在x86处理器中,原子操作由于不会针对其他加载和存储进行重新排序,从而导致了性能的降低。比如,在我们的提取与添加的微测试程序中为每个委托函数维持一个局部的非竞争锁定将使吞吐率从55Mops(million operations per second)降至26Mops。不取得锁定权限会影响适用性,因为绝大多数情况下服务方只能对服务方线程本地的数据结构进行操作。然而,由于在所有的委托系统中服务方处理存在瓶颈,无论如何,性能方面的考虑都意味着服务方将无法访问与客户方共享的任何数据结构。

3.2 ffwd 委托API

使得服务方调用传入的指针结合了我们的高性能和易使用两大目标:请求中指定的函数可以是有着至多6-8个参数的任何非阻塞C函数。为使应用开发者们易使用,ffwd提供了小的API:

FFWD_Server_Init()开启一个服务器线程,分配并初始化请求和响应行。

FFWD_Delegate(s,f,retvar,argc,args…)这个宏(指令)将函数f和一些指定的参数委托给服务方s。存储器在retvar处返回值。

使用ffwd API,客户方可能会用单行代码将函数的执行委托给服务方,指定函数,需要的参数和返回值变量。

4.实验评估(很遗憾的是,该实验的配置要求是XEON或者OPTERON处理器,我手头没有这方面资源,所以只配置了环境,未完成实验:


4.1实验设计

我们在表1中报告了在四台机器上运行得到的评估结果。然而,由于空间的限制,绝大多数图表来自于64核Broadwell系统(Intel发布的微处理器架构),除非特别提出,这四个系统上运行结果所得的趋势是一致的。(详见本论文的技术报告)

为了精确地特征化ffwd的性能,我们在应用层级和微基准测试程序层级进行试验,比较ffwd与大量的其他的有竞争性的方法的差别:传统的锁定包括测试-设置自旋锁(TAS,test-and-set),测试-测试-设置自旋锁(TTAS),MUTEX,MCS,CLH,TICKET,HTICKET,FC,RCL。对于一些特定的基准测试程序,我们包含了几个特别针对它的实现,包括无锁的,软件事务内存的和结合的方式。

除非特别提到的,我们在讨论的机器上面使用数目与支持的硬件线程一致的线程。一个重要的特例是FFWDx2的方式,它用两个用户/绿色线程超额了每个硬件线程。这些用户线程在发送请求后立刻放弃了CPU。FFWDx2只在我们的微处理程序中进行评估。在ffwd的案例中,我们在我们的实验中对每个套接口专用一个内核,即使套接口并未在使用中,让每个套接口至多有30个线程在Broadwell机器上运行客户/应用程序。尽管让每个套接口专用一个服务方内核不是ffwd的一个设计要求,它保证了实验设计的简单与弹性。从使用这些剩下的几个内核来获取性能的提升对ffwd而言是微不足道的。

在所有的实验中,基准测试程序线程按照预先确定的顺序被固定在硬件线程中。在Xeon中,我们首先在每个可获得的内核中用一个线程填充一个套接口,然后用相同的方式填充连续的套接口,最后按照相同的顺序重新访问每个套接口来填入被每个内核支持的第二个线程。对于FFWDx2,每个硬件线程在两个用户线程中立即填入结果。我们在AMD系统中按照相似的顺序去做,因为Opteron内核共享一些资源。首个传送在每个对中填入一个内核:第二个传送填充第二个内核。最后,随后的图标中的数据点代表了至少10次独立运行的平均值,除了缓存的,它是一个长线的基准程序,以及链表/哈希表,它被配置成在每个数据点运行15秒。

所有的程序用gcc5.4.0编译,使用3级优化,在Ubuntu 16.04环境下运行。这些实验在glibc标准分配器下运行(glibc是GNU发布的libc库,即c运行库。glibc是linux系统中最底层的api,几乎其它任何运行库都会依赖于glibc。glibc除了封装linux操作系统所提供的系统服务外,它本身也提供了许多其它一些必要功能服务的实现。由于 glibc 囊括了几乎所有的 UNIX 通行的标准,可以想见其内容包罗万象。而就像其他的 UNIX 系统一样,其内含的档案群分散于系统的树状目录结构中,像一个支架一般撑起整个操作系统。在 GNU/Linux 系统中,其C函数库发展史点出了GNU/Linux 演进的几个重要里程碑,用 glibc 作为系统的C函数库,是GNU/Linux演进的一个重要里程碑。)我们也用Hoard,slab,和jemalloc(这些都是c内存管理程序)进行了实验,并未发现这些工作对于实验结果有显著改进(为简洁起见省略了这些数据)。在这些工作中并行的操作并非密集分配的,通常实现一些小的,固定大小的分配。同时,我们发现ptmalloc(glibc下辖的内存管理,支持并行处理时多个线程共享进程的内存空间,各线程可能并发请求内存,保证分配和回收内存的正确和高效)在每个线程中近似地创建竞技场,很大程度上避免了区域所的竞争。

 

4.2应用层级的基准测试程序

我们应用层级的基准测试程序取自SPLASH-2,PHOENIX v2.0.0的基准程序,以及和RCL论文中使用的基准程序的相同子集的Memcached v1.4.6/Memslap v1.0.2.反过来,RCL论文根据在临界区花费时间的比例来选择这些基准测试程序。关于Phoenix基准测试程序组的一些担忧也被提出,它们包括:(a)在线性回归程序中发现了一个所欲共享问题【52,65】,尽管【65】表明优化级别为-O3的gcc消除了该问题。(b)矩阵乘法程序可能与现在最先进水平的矩阵乘法相比没什么可比性。(c)字符串匹配程序可能不是一个典型的工作负荷。因此,基于Phoenix程序组的委托和上锁程序间的比较应该有保留地看待。不过,使用与同一个基准测试程序集就可以与RCL进行直接的(性能)比较。我们包含了三个基准测试程序中每个的具有两种输入大小的样例。我们由于时间限制并未包含BerkeleyCB基准测试程序。图四概括了我们在Broadwell机器上的应用层级的结果,反映出与标准Posix互斥锁相关的提速。我们既无法用MCS成功地运行热辐射基准程序,并且由于时间限制无法用扁平合并的情况下实现Memcached.总之,我们发现ffwd进一步提高了已经由RCL体现出来的委托系统的性能。

图5-6显示了性能随着线程数目的变化有着很大的变化,反映了一个很不同的实情。

4.3微基准测试程序

4.3.1获取与添加

4.3.2队列和栈

4.3.3链表

4.3.4二叉搜索树

4.3.5哈希表

 

5.委托的接口代码

5.1结合委托和锁定

在一些设定中,ffwd提供了比锁定与合并更大的性能改进,但是自旋锁通常是高度并行程序的最佳性能解决方案。这就预示着混合的解决方案。只要它们各自管理的数据结构是独立的,就没有什么会阻止程序中ffwd和锁定的共存。因此,为了获得最佳性能,可以使用ffwd作为中央共享工作队列,而用自旋锁来保护使用了细粒度锁定的百万个桶的哈希表。

 

6.相关工作(本文貌似与我们学的进程同步有关)

访问共享数据结构是并发程序中常见的性能瓶颈,这已经催生了一系列旨在提高性能和可扩缩性的方法。通过互斥(锁定)的同步仍然是访问共享变量的最流行的方案,有效和和可扩展的锁的设计已成为数十年来研究的一个热门话题。我们接下来回顾一下关于锁定的文献和其他的方法。

6.1自旋锁(spinlocks)

6.2基于队列的锁(Queue-basedLocks)比如CLH,MSC锁等。CLH锁是一个自旋锁,能确保无饥饿性,采用先来先服务原则;也是一种基于链表的可扩展的、高性能、公平的自旋锁,申请线程只在本地变量上自旋,不断轮询前驱的状态,如果发现前驱释放了锁就结束自旋。CLH是在前趋节点的locked域上自旋等待,而MCS是在自己节点的locked域上自旋等待。(https://www.cnblogs.com/yuyutianxia/p/4296220.html

6.3无锁数据结构 (Lock-FreeData Structures)

6.4读拷贝修改(Read-Copy-Update)

对于被RCU保护的共享数据结构,读操作不需要获得任何锁就可以访问,但写操作在访问它时首先拷贝一个副本,然后对副本进行修改,最后在适当的时机把指向原来数据的指针重新指向新的被修改的数据。这个时机就是所有引用该数据的CPU都退出对共享数据的操作。Linux内核中内存管理大量的运用到了RCU机制。为每个内存对象增加了一个原子计数器用来继续该对象当前访问数。当没有其他进程在访问该对象时(计数器为0),才允许回收该内存。

6.5软件事务内存(Software TransactionalMemory)

它是一种类似于数据库事务的并发控制机制,用于控制并发计算中对共享内存的访问本质是为提高并发,通过事务管理内存的读写访问来避免锁的使用。(STM是一种非常乐观的并发控制机制:一个线程独立完成对共享内存的修改完全忽略可能会有其它的线程存在,但是线程在日志中记录对共享内容的每一个读写动作。其他的并发控制一般是在进行写操作时来保证与其他事务的一致性(不能修改已经被别的事务修改过的共享数据),STM在完成一个事务之后,再验证其它线程有没有并发的对共享内存进行或修改,从而保证事务是完整的。因此,STM事务的最后一个操作是验证,如果验证通过,则提交,否则取消,导致所有以前进行的修改动作回滚。如果一个事务不能够提交,一般的,事务将回滚,并且从入口开始重新执行。

6.6委托与合并(Delegationand Combining)

用来改善基于锁的,而且是被高度竞争的锁的方法的性能。在委托中,一个服务方线程代表多个客户方线程操作一个数据结构。无论何时,对于单个数据结构,至多一个线程是服务方,因此需要降低同步操作的数目。在专门服务方的极端案例中(比如在ffwd中),根本不需要同步。相比基于锁的架构,委托方法的其他的优势在于共享的数据结构存放在服务方的缓存中。合并是一类很流行的委托手段:在flat combining中,线程将需要完成的任务加入一个列表中。一个线程通过得到一个全局的锁变为服务方/合并者,它在释放锁之前执行线程临界区中的任务。不同的线程在这一过程中都可能成为服务方/合并者。在高度竞争锁的情况下,合并的方式比上锁的方式性能要好,但是带来了额外的管理开销和负载。

6.7批数据结构(batcheddata structures)

在批处理数据结构中,多个操作在被并行应用于数据结构之前被汇集到批处理中。委托服务方可以服务于批数据结构,有结合两种方法的好处的潜在能力。

7.总结

总之,ffwd提供了一个有效的,高性能的多核计算机委托模式的设计与实现。给定一个在单线程上跑得比在多线程上更快的数据结构,ffwd在多线程程序中,使得该数据结构也可以运行,从而提供了无可比拟的性能。对于其他的应用,案例就没那么清晰了。给定足够的并行度,以及使得委托的服务方饱和的长临界区,细粒度的锁定和并行数据结构有时是更好的设计选择。

通过描绘ffwd,并为委托模式提供一个充分的理由使之成为一个有吸引力的,易使用的,高平均的访问共享数据结构的方式,我们希望能鼓励社区进一步挖掘它的潜力。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值