Design Guidelines for High Performance RDMA Systems

作者/机构: Anuj Kalia, Carnegie Mellon University; Michael Kaminsky, Intel Labs; David G. Andersen, Carnegie Mellon University

A1 主要贡献

本文旨在为系统设计者提供一套设计指南,以帮助他们在RDMA(远程直接内存访问)的设计空间中做出最佳选择。尽管现代RDMA硬件性能强大,但如何选择和使用RDMA操作会显著影响最终性能。

核心问题与研究目标
1. 设计选择的复杂性:程序员在使用RDMA网卡时面临众多选项(如操作类型、传输方式、优化标志等),而这些选项的相对性能取决于复杂的底层因素,如PCIe总线事务和专有的网卡架构。
2. 性能差异巨大:不同的RDMA设计选择可能导致吞吐量相差70倍,主机CPU消耗相差3.2倍。不存在一种“万能”的最佳方案,应用需求的微小变化都会影响设计的相对性能。
3. 底层细节的重要性:作者发现,底层细节(如PCIe事务和网卡架构)对RDMA系统设计至关重要,但现有研究缺乏对这些细节的系统性指导。

本文的主要贡献
1. 提供设计指南和工具:本文提供了一套设计指南,辅以开源的测量工具集,用于评估和优化影响端到端吞吐量的关键系统因素。每个指南都解释了如何判断其相关性(例如,使用PCIe计数器检测网卡和CPU之间的过度流量)以及如何缓解相关问题。
2. 通过实际系统验证指南的有效性
* 网络序列器(Sequencer):设计了一种新颖的网络序列器,其性能比现有设计高出50倍,单网卡可达每秒1.22亿次操作,并具有良好的可扩展性。
* 键值存储(Key-Value Store):应用这些指南将HERD键值缓存的CPU效率提高了83%,吞吐量提高了35%。
3. 揭示新的优化和陷阱
* 本文提出并评估了几种新的RDMA优化方法和潜在陷阱。
* 研究发现,当前的RDMA网卡处理原子操作的争用时效率极低,这使得依赖原子操作的系统设计(如【27, Designing a low-latency cuckoo hash table for write-intensive workloads, 2014, WSRC】,【30, Fast in-memory transaction processing using RDMA and HTM, 2015, 25th SOSP】,【11, The end of slow networks: It’s time for a redesign, 2015, CoRR】)性能低下。

本文的目标是为研究人员和开发者提供一份路线图,帮助他们在不成为RDMA专家的情况下,理解并利用这些底层细节,从而设计出高性能的RDMA系统。

A3 背景知识

硬件组件

图1:RDMA集群中节点的硬件组件
图1:RDMA集群中节点的硬件组件

图1展示了RDMA集群中一台机器的相关硬件组件。一个或多个端口的网卡连接到多核CPU的PCIe控制器。该PCIe控制器通过读写L3缓存来服务网卡的PCIe请求;在现代Intel服务器上【4, Intel Xeon Processor E5-1600/2400/2600/4600 (E5-Product Family) Product Families】,L3缓存提供了用于PCIe事件的计数器。

2.1 PCI Express

PCIe概述。目前最快的PCIe链接是PCIe "3.0 x16",即使用16个通道的第三代PCIe协议。PCIe链接的带宽是每通道带宽乘以通道数。PCIe是一个分层协议,其层头部会增加开销,理解这一点对提高效率很重要。RDMA操作会产生3种PCIe事务层数据包(TLP):读请求、写请求和读完成(写操作没有事务层响应)。图2a列出了我们集群中各代PCIe的带宽和头部开销。值得注意的是,20-26字节的头部开销与memcached【25, Scaling Memcache at Facebook, 2013, 10th USENIX NSDI】和RPC【15, Network interface design for low latency request-response protocols, 2013, USENIX ATC】等服务中常用的数据项大小相当。

(a) 各代PCIe的速度和头部大小。通道带宽不包括物理层编码开销。

图2(a) PCIe背景
图2(a) PCIe背景

MMIO写与DMA读的比较。从CPU向PCIe设备传输数据有两种方法,它们之间存在重要差异。CPU通过写入映射的设备内存(MMIO)来发起PCIe写操作。为避免为每条存储指令都生成一个PCIe写操作,CPU使用一种称为“写合并”的优化,将多个存储操作合并成缓存行大小的PCIe事务。PCIe设备拥有DMA引擎,可以使用DMA从DRAM中读取数据。DMA读不受限于缓存行大小,但如果一个读响应大于CPU的读完成合并大小(Crc),它将被分割成多个完成包。在我们测量中使用的Intel CPU(表2)的Crc为128字节;我们假设AMD CPU的Crc也为128字节【4, Intel Xeon Processor E5-1600/2400/2600/4600 (E5-Product Family) Product Families】【3, Intel Xeon Processor E5-1600/2400/2600/4600 v3 Product Families】。一个DMA读操作总是比一个同样大小的MMIO写操作使用更少的主机到设备PCIe带宽;图2b展示了分析性比较。这是一个重要因素,我们将在后续章节展示它如何影响上层协议的性能。

图2(b) PCIe背景
图2(b) PCIe背景

PCIe计数器。我们的贡献依赖于对网卡和CPU之间PCIe交互的理解。虽然精确的PCIe分析需要昂贵的PCIe分析仪或专有/机密的网卡手册,但现代CPU上可用的PCIe计数器可以提供一些有用的见解。对于每个计数器,每秒捕获的事件数即为其计数器速率。我们的分析主要使用DMA读(PCIeRdCur)和DMA写(PCIeItoM)的计数器。

2.2 RDMA

RDMA概述。RDMA是一种网络特性,允许直接访问远程计算机的内存。提供RDMA功能的网络包括InfiniBand、RoCE(RDMA over Converged Ethernet)和iWARP(Internet Wide Area RDMA Protocol)。RDMA网络通常提供高带宽和低延迟:每端口带宽100Gbps、往返延迟约2µs的网卡已商业可用。一个基于RDMA的通信协议的性能和可扩展性取决于多个因素,包括操作(verb)类型、传输方式、优化标志和操作发起方法。

2.2.1 RDMA verbs 和传输方式

队列对(QPs)与Verbs。RDMA主机使用队列对(QPs)进行通信;主机创建由发送队列和接收队列组成的QP,并使用verbs API向这些队列提交操作。我们称发起verb的主机为请求者(requester),目标主机为响应者(responder)。对于某些verbs,响应者实际上不发送响应。完成一个verb后,请求者的网卡可以选择性地通过DMA一个完成条目(CQE)到与QP关联的完成队列(CQ)来发出完成信号。通过在请求中设置一个标志,可以使verbs变为非信令(unsignaled);这些verbs不生成CQE,应用程序通过应用特定的方法来检测完成。

Verbs类型:内存型与消息型。Verbs分为两种类型:内存型verbs和消息型verbs。内存型verbs包括RDMA读、写和原子操作。这些verbs指定要操作的远程地址,并绕过响应者的CPU。消息型verbs包括发送和接收verbs。这些verbs涉及到响应者的CPU:发送的载荷被写入到由响应者CPU先前提交的接收操作所指定的地址。在本文中,我们将RDMA读、写、发送和接收verbs分别称为READ、WRITE、SEND和RECV。

传输方式:可靠与不可靠,连接与数据报。RDMA传输方式可以是可靠的或不可靠的,也可以是连接的或非连接的(也称为数据报)。对于可靠传输,网卡使用确认机制来保证消息的按序交付。不可靠传输不提供此保证。然而,现代RDMA实现(如InfiniBand)使用无损链路层,通过链路层流控制【1, Infiniband architecture specification volume 1】来防止基于拥塞的丢包,并通过链路层重传来防止基于比特错误的丢包【8, Mellanox OFED for linux user manual】。因此,不可靠传输丢包的情况非常罕见。连接传输要求QP之间建立一对一的连接,而数据报QP可以与多个QP通信。本文我们考虑两种连接传输类型:可靠连接(RC)和不可靠连接(UC)。目前的RDMA硬件只提供一种数据报传输:不可靠数据报(UD)。不同的传输方式支持不同的verbs子集:UC不支持RDMA读,UD不支持内存型verbs。表1对此进行了总结。

表1:每种传输类型支持的操作,以及它们在Mellanox上的SEND、WRITE和READ的WQE头大小。所有传输的RECV WQE头大小均为16 B。
表1:每种传输类型支持的操作,以及它们在Mellanox上的SEND、WRITE和READ的WQE头大小。所有传输的RECV WQE头大小均为16 B。

2.2.2 RDMA WQEs

工作队列元素(WQEs)的创建与传输。为了发起RDMA操作,请求者上的用户模式网卡驱动程序在主机内存中创建工作队列元素(WQEs);通常,WQEs是在一个预分配的连续内存区域中创建的,并且每个WQE都是单独缓存行对齐的。(我们将在3.1节讨论将WQE传输到设备的方法。)WQE的格式是厂商特定的,由网卡硬件决定。

WQE大小的影响因素。WQE的大小取决于几个因素:RDMA操作的类型、传输方式,以及载荷是由指针字段引用还是内联在WQE中(即WQE缓冲区包含载荷)。表1显示了Mellanox网卡在三种传输方式下的WQE头部大小。例如,对于一个36字节的WQE头部,一个内联了x字节载荷的WRITE WQE的大小是36 + x字节。UD WQE有更大的68字节头部,用于存储额外的路由信息。

2.2.3 术语和默认假设

入站与出站Verbs的区分。我们区分入站(inbound)和出站(outbound)verbs,因为它们的性能差异显著(第5节):内存型verbs和SEND在请求者端是出站的,在响应者端是入站的;RECV总是入站的。图3总结了这一点。由于我们的研究专注于小消息,默认情况下所有的WRITE和SEND都是内联的。我们将填充到缓存行对齐的函数定义为x := ⌊x/64⌋ * 64。我们用PbwPrPc分别表示PCIe 3.0的每通道带宽、请求头部大小和完成头部大小。

图3:服务器端的入站和出站verbs。
图3:服务器端的入站和出站verbs。

A2 方法细节 (RDMA设计指南)

我们现在提出我们的设计指南。随同每个指南,我们介绍新的优化措施,并简要描述先前文献中提出的方法。我们对网卡硬件做两个假设,这两个假设对所有当前可用的网卡都成立。首先,我们假设网卡是基于PCIe的设备。当前的网络接口(NI)主要是独立的PCIe卡;供应商开始将NI集成到芯片上【2, Intel Atom Processor C2000 Product Family for Microserver】【5, Intel Xeon Processor D-1500 Product Family】或封装上【6, Intel Xeon Phi Processor Knights Landing Architectural Overview】,但这些NI仍然使用PCIe协议与PCIe控制器通信,并且功能不如独立的NI强大。其次,我们假设网卡内部使用多个处理单元(PU)实现并行处理——这对于高速NI来说通常是成立的【19, Packet processing at 100 Gbps and beyond - challenges and perspectives, 2009, ITG Symposium on Photonic Networks】。如同传统的并行编程一样,这种并行性既提供了优化机会(第3.3节),也带来了陷阱(第3.4节)。

为了讨论以下优化对CPU和PCIe使用的影响,我们考虑将N个大小为D字节的WQE从CPU传输到网卡。

图4:用于传输跨越2个缓存行的两个WQE(阴影部分)的WQE-by-MMIO和Doorbell方法。箭头表示PCIe事务。红色(细)箭头是MMIO写;蓝色(粗)箭头是DMA读。箭头上标有WQE编号;箭头宽度表示事务大小。
图4:用于传输跨越2个缓存行的两个WQE(阴影部分)的WQE-by-MMIO和Doorbell方法。箭头表示PCIe事务。红色(细)箭头是MMIO写;蓝色(粗)箭头是DMA读。箭头上标有WQE编号;箭头宽度表示事务大小。

3.1 减少CPU发起的MMIO

通过减少或替换MMIO来提升CPU效率和RDMA吞吐量。如果减少MMIO或将其替换为CPU和带宽效率更高的DMA,CPU效率和RDMA吞吐量都可以得到改善。CPU通过MMIO向网卡发送消息来发起网络操作。该消息可以(1) 包含新的工作队列元素,或者(2) 通过诸如最后一个WQE地址之类的信息来引用新的WQE。在第一种情况下,WQE通过64字节的写合并MMIO进行传输。在第二种情况下,网卡使用一个或多个DMA读取WQE。我们分别称这些方法为WQE-by-MMIO和Doorbell。(不同的技术对这些方法有不同的术语。Mellanox使用“BlueFlame”和“Doorbell”,而Intel® Omni-Path架构分别使用“PIO send”和“SDMA”。)图4总结了这一点。WQE-by-MMIO方法为低延迟进行了优化,通常是默认设置。两种优化可以通过减少MMIO来提高性能:

Doorbell批处理。如果一个应用程序可以向一个QP发出多个WQE,它就可以为这一批次使用一个Doorbell MMIO。
* CPU影响:Doorbell将CPU生成的MMIO从WQE-by-MMIO的 N * ⌈D/64⌉ 个减少到1个。
* PCIe影响:对于N=10,D=65和PCIe 3.0,Doorbell传输1534字节,而WQE-by-MMIO传输1800字节(附录A)。
在本文中,我们将Doorbell批处理称为批处理(batching)——通过WQE-by-MMIO进行的批处理WQE传输与一系列单独的WQE-by-MMIO相同,因此批处理仅对Doorbell有用。

WQE收缩。减少WQE使用的缓存行数量可以极大地提高吞吐量。例如,考虑将WQE大小从129字节仅减少1字节到128字节,并假设使用WQE-by-MMIO。
* CPU影响:CPU生成的MMIO从3个减少到2个。
* PCIe影响:PCIe事务的数量从3个减少到2个。
收缩机制包括压缩应用程序载荷,或用应用程序数据重载未使用的WQE头字段。

图5:用于发出两个4字节UD SEND的优化。由于68字节的头部,UD SEND WQE在Mellanox网卡上跨越2个缓存行;我们通过使用一个4字节的头字段作为载荷,将其缩小到1个缓存行。箭头符号遵循图4。
图5:用于发出两个4字节UD SEND的优化。由于68字节的头部,UD SEND WQE在Mellanox网卡上跨越2个缓存行;我们通过使用一个4字节的头字段作为载荷,将其缩小到1个缓存行。箭头符号遵循图4。

3.2 减少网卡发起的DMA

减少DMA以节省网卡处理能力和PCIe带宽。减少DMA可以节省网卡的处​​理能力和PCIe带宽,从而提高RDMA吞吐量。请注意,上述的批处理优化增加了一个DMA读,但它避免了多个MMIO,这通常是一个很好的权衡。

图6:针对小型SEND的RECV优化。
图6:针对小型SEND的RECV优化。

已知和新的DMA减少优化。已知的减少网卡发起的DMA的优化包括:避免完成DMA写的非信令verbs(第2.2.1节),以及避免载荷DMA读的载荷内联(第2.2.2节)。第3.1节中的两种优化也影响DMA:大N的批处理比小N需要更少的DMA读;WQE收缩进一步使这些读更小。网卡必须为完成的RECV进行DMA一个完成队列条目【1, Infiniband architecture specification volume 1】;这提供了一个额外的优化机会,如下所述。与其他仅表示完成且可有可无的verbs的CQE不同,RECV CQE包含重要元数据,如接收数据的​​大小。网卡通常为载荷和完成生成两个独立的DMA,分别写入应用程序和驱动程序拥有的内存。我们稍后会展示,相应的性能损失解释了“消息型verbs比内存型verbs慢”的经验法则,而使用以下避免DMA的优化则挑战了这一法则(对某些载荷大小而言)。我们在此假设与RECV对应的SEND携带一个X字节的载荷。

内联RECV (Inline RECV)。如果X很小(对于Mellanox的网卡约为64字节),网卡会将载荷封装在CQE中,驱动程序稍后会将其复制到应用程序指定的地址。
* CPU影响:复制小载荷会产生少量开销。
* PCIe影响:使用1个DMA而不是2个。

仅头部RECV (Header-only RECV)。如果X=0(即RDMA SEND数据包只包含头部而没有载荷),则在接收端不会生成载荷DMA。数据包头部的一些信息会包含在DMA的CQE中,可用于实现应用程序协议。我们称X=0的SEND和RECV为仅头部的,否则为常规的。
* PCIe影响:使用1个DMA而不是2个。

总结。图5和图6分别总结了这两条针对UD SEND和RECV的指南。这两种verbs在我们的评估中被广泛使用。

3.3 启用多个NIC PU

利用多队列增加CPU效率。利用网卡并行性对于高性能是必要的,但这需要特别关注。一个常见的RDMA编程决策是使用尽可能少的队列对,但这样做会将网卡并行性限制在QP的数量上。这是因为同一QP上的操作有顺序依赖性,理想情况下由同一个网卡处理单元处理,以避免跨PU同步。例如,在基于数据报的通信中,每个CPU核心一个QP就足以与所有远程核心通信。使用一个QP消耗最少的NIC SRAM来保存QP状态,同时避免了CPU核心之间共享QP。然而,它将一个CPU核心“绑定”到一个PU,并可能将核心吞吐量限制为PU吞吐量。当每个消息的应用程序处理量很小(例如第4.2节中的序列器)并且高速CPU核心压倒了功能较弱的PU时,很可能发生这种情况。在这种情况下,每个核心使用多个QP可以提高CPU效率;我们称之为多队列(multi-queue)优化。

3.4 避免NIC PU之间的争用

跨QP同步操作的性能瓶颈。需要跨QP同步的RDMA操作会引入PU之间的争用,其性能可能比无争用操作差一个数量级以上。例如,RDMA提供了对远程内存的原子操作,如比较并交换(compare-and-swap)和取值并加一(fetch-and-add)。据我们所知,在撰写本文时所有可用的网卡(包括最近发布的ConnectX-4【7, Mellanox ConnectX-4 product brief】)都对原子操作使用内部并发控制:PU为目标地址获取一个内部锁,并通过PCIe发出读-修改-写操作。注意,原子操作也会与非原子verbs争用。未来的网卡可能会使用PCIe的原子事务来实现更高性能、基于缓存一致性的并发控制。

内部锁机制的重要性。因此,网卡的内部锁定机制,例如锁的数量以及原子地址到这些锁的映射,非常重要;我们在第5.4节中描述了推断这一点的实验。请注意,由于网卡中SRAM有限,可用锁的数量很少,这会加剧工作负载中的争用。

3.5 避免NIC缓存未命中

NIC缓存的重要性及检测。网卡会缓存多种类型的信息;保持高缓存命中率至关重要,因为一次未命中就意味着一次通过PCIe的读取。缓存的信息包括(1) RDMA注册内存的虚拟到物理地址转换,(2) QP状态,以及(3) 工作队列元素缓存。虽然前两者是已知的【13, FaRM: Fast remote memory, 2014, 11th USENIX NSDI】,但第三者是未记录的,并在我们的实验中被发现。地址转换缓存未命中可以通过使用大页(例如2MB)来减少,QP状态缓存未命中可以通过使用更少的QP来减少【13, FaRM: Fast remote memory, 2014, 11th USENIX NSDI】。我们在这方面做出了两项新的贡献:

  • 检测缓存未命中:所有类型的NIC缓存未命中对应用程序都是透明的,并且可能难以检测。我们展示了如何利用PCIe计数器来完成此任务,通过检测和测量WQE缓存未命中(第5.3.2节)。一般来说,从PCIe计数器报告的实际读取中减去应用程序预期的PCIe读取,可以得到缓存未命中的估计值。而估算预期的PCIe读取则需要RDMA操作的PCIe模型(第5.1节)。

  • WQE缓存未命中:从CPU到NIC的初始工作队列元素传输会触发将WQE插入到NIC的WQE缓存中。当NIC最终处理此WQE时,如果它已被新的WQE驱逐,则可能发生缓存未命中。在第5.3.2节中,我们展示了如何测量和减少这些未命中。

A4 实验环境

评估设置:我们在表2中描述的三个集群上进行评估。我们用其网卡的缩写来命名集群,因为网卡是决定性能的主要硬件组件。
- 硬件配置
- CIB集群 (NetApp)
- CPU: Intel Xeon E5-2667 v3 (3.2 GHz, 8核)
- 网卡: 2 x Mellanox Connect-IB (56 Gbps/port)
- PCIe: 3.0 x8 (8 GT/s)
- CX3集群 (Emulab Apt):
- CPU: Intel Xeon E5-2660 v2 (2.2 GHz, 10核)
- 网卡: 1 x Mellanox ConnectX-3 Pro (40 Gbps/port)
- PCIe: 3.0 x8 (8 GT/s)
- CX集群 (NSF PRObE Nome):
- CPU: AMD Opteron 6272 (2.1 GHz, 8核)
- 网卡: 1 x Mellanox ConnectX-3 (40 Gbps/port)
- PCIe: 2.0 x8 (2.5 GT/s)
- 软件配置
- CIB和CX3集群运行Ubuntu 14.04,使用Mellanox OFED 2.4。
- CX集群运行Ubuntu 12.04,使用Mellanox OFED 2.3。
- 实验默认设置
- 对于非批处理操作,使用WQE-by-MMIO。
- 对于批处理操作,使用Doorbell。
- 当启用批处理但可用批次大小为1时,仍使用WQE-by-MMIO。
- 开源工具:本文提供了一个开源的测量和分析工具集 rdma_bench (https://github.com/efficient/rdma_bench),用于复现和扩展实验。

表2:测量集群。CX是NSF PRObE的Nome集群【17】,CX3是Emulab的Apt集群【31】,CIB是NetApp的一个集群。CX使用PCIe 2.0,速率为2.5 GT/s。
表2:测量集群。CX是NSF PRObE的Nome集群【17】,CX3是Emulab的Apt集群【31】,CIB是NetApp的一个集群。CX使用PCIe 2.0,速率为2.5 GT/s。

A4 实验结果

4. 改进的系统设计

HERD RPC 协议优化

本文使用HERD的RPC协议进行通信,并对其应用了以下通用优化:
1. 批处理 (Batching):服务器工作线程不是在检测到单个请求后立即响应,而是检查并收集来自C个客户端的N (≤ C) 个请求,然后通过一次批处理的Doorbell操作发送N个响应。这种批处理是机会性的,不会为了累积请求而等待,因此不会显著增加延迟。
2. 多队列 (Multi-queue):每个工作线程在批处理发送时,轮流使用可调数量的UD队列对。

4.2 网络序列器(Networked Sequencers)

实验内容是构建一个高性能的集中式序列器,为客户端提供单调递增的8字节整数。

基线设计与优化
* 基线: 使用HERD RPC实现。
* 实验结果 (图7)
* 批处理: 单核吞吐量从7.0 Mrps(百万请求/秒)提升到16.6 Mrps。
* 多队列: 在批处理基础上,每个核心使用3个每端口QP,单核吞吐量提升至27.4 Mrps。
* 扩展性: 使用6个核心和两种优化后,总吞吐量达到97.2 Mrps,接近101.6 Mops的DMA带宽瓶颈。超过6核后,由于批次变小,吞吐量下降。

图7:优化对基于HERD RPC的序列器(蓝色带圆点的线)的影响,以及Spec-S0和基于原子操作的序列器的吞-吐量
图7:优化对基于HERD RPC的序列器(蓝色带圆点的线)的影响,以及Spec-S0和基于原子操作的序列器的吞-吐量

序列器特定优化 (Spec-S0)
* 设计: 为解决HERD RPC的可扩展性限制和PCIe效率问题,设计了Spec-S0。它使用header-only SEND进行请求和响应,将请求方式从WRITE改为SEND/RECV,并利用SEND包头中的4字节立即数字段传输数据。对于8字节的序列号,客户端推测高4位字节,服务器在推测正确时仅返回低4位字节,否则返回完整的8字节。
* 实验结果 (图7, 表3)
* Spec-S0吞吐量达到122 Mrps,瓶颈从PCIe带宽转移到网卡处理能力。
* 与基线HERD RPC设计(批处理+多队列)相比,单核吞-吐量略低,但峰值吞吐量更高。

表3:CIB上的序列器吞吐量(Mrps)和瓶颈
表3:CIB上的序列器吞吐量(Mrps)和瓶颈

延迟分析 (图8)
* 实验: 比较Spec-S0在批处理和非批处理响应模式下的端到端延迟。
* 结论: 批处理模式因需要额外的DMA读而增加了最多1µs的延迟,但考虑到其带来的巨大吞吐量和CPU效率增益,这是可接受的。

图8:响应批处理对Spec-S0延迟的影响
图8:响应批处理对Spec-S0延迟的影响

基于原子操作的序列器
* 设计: 使用RDMA fetch-and-add原子操作实现序列器。
* 实验结果 (图7):由于网卡内部PU对计数器的锁争用,性能极差,仅达到2.24 Mrps,比优化后的设计慢50倍。

4.3 键值存储 (Key-Value Stores)

实验内容是应用设计指南来优化现有的RDMA键值存储系统。

提升HERD性能
* 实验设置: 在HERD中应用批处理优化,工作负载为95% GET和5% PUT,键为16字节,值为32字节。
* 实验结果 (图9)
* CPU效率: 批处理使HERD的单核吞吐量从6.7 Mrps提升到12.3 Mrps,提高了83%。
* 峰值吞吐量: 峰值吞吐量从72.8 Mrps提升到98.3 Mrps,提高了35%,瓶颈为PCIe DMA带宽。
* 设计对比: 优化后的HERD吞吐量比基于READ的键值存储(如Pilaf, FaRM-KV)高出63%。

图9:在5% PUT操作下HERD吞吐量的提升
图9:在5% PUT操作下HERD吞吐量的提升

基于原子操作的键值存储
* 实验内容: 模拟一个简化的DrTM-KV,其中GET操作用1个READ模拟,PUT操作用2个原子操作模拟,以评估其在混合读写负载下的性能。
* 实验结果 (图10)
* 性能下降: 在CX3集群上,仅10%的PUT操作就导致吞吐量下降72%;在CIB上则下降31%。
* PUT瓶颈: 100% PUT的负载下,吞吐量仅为100% GET负载的4%(CX3)和12%(CIB)。
* 结论: 网卡内部的锁争用严重影响了使用原子操作的键值存储的写性能。

图10:模拟的DrTM-KV在更新操作增加时的吞吐量
图10:模拟的DrTM-KV在更新操作增加时的吞吐量

5. RDMA中的底层因素

本节通过微基准测试深入分析影响RDMA性能的底层因素。

批处理操作 (Batched Operations)
* UD SENDs (图11):
* 批处理将峰值SEND吞吐量从80 Mops提升至101.6 Mops(提升27%)。
* 批处理的瓶颈是DMA带宽,而非批处理的瓶颈是MMIO带宽。
* 对于单核性能,批处理必须结合多队列优化才能发挥效果,否则性能反而下降。

图11:(a) CIB上批处理和非批处理UD SEND的峰值吞吐量,批大小为B;虚线显示相应的PCIe限制。(b) 优化对单核UD SEND吞吐量的影响,载荷为60字节(128字节WQE)。
图11:(a) CIB上批处理和非批处理UD SEND的峰值吞吐量,批大小为B;虚线显示相应的PCIe限制。(b) 优化对单核UD SEND吞吐量的影响,载荷为60字节(128字节WQE)。

* UD RECVs (表5):
* 使用header-only RECV避免了载荷DMA,使吞吐量在CIB上从82 Mops提升到122 Mops(提升49%),性能与入站WRITE相当。
* header-only SEND由于WQE更小(1个缓存行),吞吐量提升了54%。
表5:header-only (0) 和常规 (≥ 1) SENDs及RECVs的每网卡速率(百万/秒)
表5:header-only (0) 和常规 (≥ 1) SENDs及RECVs的每网卡速率(百万/秒)

非批处理操作 (Non-batched Operations)
* 入站READs和WRITEs (图12):
* 瓶颈在小载荷时是网卡处理能力,大载荷时是InfiniBand网络带宽。
* 不同网卡从处理能力瓶颈转换到带宽瓶颈的载荷大小不同(CIB为64字节,CX3为256字节),这对系统设计(如选择单次大READ还是多次小READ)有重要影响。

图12:入站READ和UC WRITE吞吐量,以及READ的InfiniBand限制。注意Y轴刻度不同。
图12:入站READ和UC WRITE吞吐量,以及READ的InfiniBand限制。注意Y轴刻度不同。

* 出站READs和WRITEs (图13):
* 高吞吐量需要通过流水线维持多个在途请求。
* CPU注入WQE的速度超过网卡处理速度时,会导致WQE缓存未命中,增加PCIe读流量。
* 通过PCIe计数器可以量化WQE缓存未命中。实验显示,在CIB上,增加在途READ请求数量(窗口大小N)会增加吞吐量,但也会因WQE缓存未命中而增加PCIe读。最佳窗口大小并非越大越好,且依赖于具体网卡。
图13:显示可能WQE缓存未命中的PCIe模型,以及对READ和RC WRITE的WQE缓存未命中测量。
图13:显示可能WQE缓存未命中的PCIe模型,以及对READ和RC WRITE的WQE缓存未命中测量。

原子操作 (Atomic Operations)
* 实验 (图14): 通过在Z个不同的计数器上执行原子操作来改变并行度。
* 结果:
* CX3网卡将所有原子操作串行化,吞吐量不随Z增加而改变,始终为2.7 Mops。
* CIB网卡有更好的内部锁机制,吞吐量随Z增加而上升,最高可达52 Mops。
* 锁机制推断: CIB似乎使用基于地址低12位的4096个桶来实现锁,导致地址低12位相同的操作会被串行化。
* 结论: 即便在CIB上,也需要仔细进行锁地址的布局以避免冲突。

图14:原子操作在并发增加时的吞吐量
图14:原子操作在并发增加时的吞吐量

A7 补充细节

5.1 PCIe模型

理解PCIe行为的复杂性。我们已经证明,理解PCIe行为对于提高RDMA性能至关重要。然而,在没有专有/机密的网卡手册和有限资源(仅有每个缓存行的PCIe计数器和未文档化的驱动软件)的情况下,推导PCIe行为的分析模型需要大量的实验和分析。我们推导出的模型在本文的多个地方以稍微简化的形式呈现(图5、6、13)。精确的分析模型很复杂,并取决于几个因素,如网卡、其PCIe能力、verb和传输方式、Doorbell批处理的级别等。为了使我们的模型易于获取,我们对两个Mellanox驱动程序(ConnectX-3和Connect-IB)的数据路径进行了插桩,以提供有关PCIe带宽使用的统计信息(https://github.com/efficient/rdma_bench)。我们的模型和驱动程序仅限于请求者端的PCIe行为。我们省略了响应者端的PCIe行为,因为它与我们之前工作【20, Using RDMA efficiently for key-value services, 2014, ACM SIGCOMM】中描述的相同:入站READ和WRITE分别生成一个PCIe读和写;入站SEND触发RECV完成——我们讨论了RECV的PCIe事务。

出站verbs
出站verbs

表4:CIB上不同RDMA verb模式的吞吐量和瓶颈。HO表示header-only优化。

5.2 批处理操作

批处理操作的适用性。当前硬件上批处理的一个限制使其主要适用于数据报传输:一个批次中的所有操作必须使用相同的队列对,因为Doorbell是针对每个QP的。这个限制似乎是NIC并行架构的根本所在:在一个假设的NIC设计中,如果Doorbell包含与多个队列对相关的信息(例如,“QP1有2个新WQE,QP2有1个新WQE”的紧凑编码),将Doorbell发送到处理这些QP的NIC处理单元将需要NIC内部昂贵的选择性广播。这些PU随后会为WQE发出单独的DMA,从而失去了批处理的合并优势。这个限制使得批处理对于仅提供两台机器之间一对一通信的连接型QP来说用处不大:在大型部署中,一个进程有多个发往同一台远程机器的消息的机会很低。因此,我们仅讨论UD传输的批处理。

CIB上的UD SEND吞吐量。图11显示了CIB上批处理和非批处理UD SEND的吞吐量和PCIe带宽限制。我们使用一台服务器向多台客户端机器发出SEND。对于批处理,我们使用大小为16的批次(即NIC每个Doorbell DMA 16个工作队列元素)。否则,CPU通过WQE-by-MMIO方法写入WQE。我们使用尽可能多的核心来最大化吞吐量。批处理将峰值SEND吞吐量从8000万操作/秒(Mops)提高了27%,达到1.016亿Mops。

5.2.1 UD SENDs

瓶颈分析。批处理吞吐量受限于DMA带宽。对于每个大小为Crc字节的DMA完成,都有Pc字节的头部开销(第2.1节),这导致CIB上有效的DMA读取带宽为13443 MB/s。由于UD WQE至少跨越2个缓存行,最大WQE传输速率为13443/128 = 1.05亿/秒,这与我们实现的吞吐量相差在5%以内;我们将差异归因于链路层和物理层的PCIe开销。非批处理吞吐量受限于MMIO带宽。CIB上的写合并MMIO速率为(16 * Pbw)/(64 + Pr) = 1.75亿缓存行/秒。带有非零载荷的UD SEND WQE至少跨越2个缓存行(表1),最高可达80 Mops。这与87.5 Mops的带宽限制相差在10%以内。

多队列优化。图11b显示了批处理和非批处理的60字节UD SEND的单核吞吐量——这是WQE能装入2个缓存行的最大载荷大小。有趣的是,如果只使用一个QP,批处理会降低核心吞吐量:使用一个QP时,一个核心与一个NIC处理单元耦合(第3.3节),因此吞吐量取决于PU如何处理批处理和非批处理操作。当我们通过使用多个QP打破这种耦合时,批处理才显示出预期的效果。在所有集群上,使用2个QP时,批处理吞吐量增加了约2倍,使用4个QP时增加了2-3.2倍。非批处理(WQE-by-MMIO)的吞吐量不会随着多个QP而增加(图中未显示),表明它受限于CPU。

设计启示。基于RDMA的系统通常可以在绕过CPU和涉及CPU的设计之间做出选择。例如,客户端可以通过直接从服务器内存中READ来访问键值存储【24, Using one-sided RDMA reads to build a fast, CPU-efficient key-value store, 2013, USENIX ATC】【13, FaRM: Fast remote memory, 2014, 11th USENIX NSDI】【30, Fast in-memory transaction processing using RDMA and HTM, 2015, 25th SOSP】【28, Nessie: A decoupled, client-driven, key-value store using RDMA, 2015, University of Waterloo Technical Report】,或者像HERD【20, Using RDMA efficiently for key-value services, 2014, ACM SIGCOMM】那样通过RPC。我们的结果表明,即使在最强大的NIC上实现峰值性能也不需要过多的CPU算力:只需4个核心就能饱和最快的PCIe链接。因此,只要应用程序级别的处理允许,涉及CPU的设计就不会受限于CPU处理能力。

5.2.2 UD RECVs

Header-only与常规RECVs的性能对比。表5比较了仅头部(header-only)和携带载荷的常规RECV的吞吐量(图6)。在我们的实验中,多台客户端机器向一台发布RECV的服务器机器发出SEND。在CIB上,通过仅头部RECV避免载荷DMA,吞吐量从82 Mops增加到122 Mops,提高了49%,使其与入站WRITE一样快(图12a)。表5还比较了仅头部和常规SEND(图5)。仅头部SEND使用单缓存行WQE,实现了54%的更高吞吐量。

设计启示。根据经验,开发人员在性能关键的机器上会避免使用RECV,而倾向于使用更快的READ/WRITE verbs【24, Using one-sided RDMA reads to build a fast, CPU-efficient key-value store, 2013, USENIX ATC】【20, Using RDMA efficiently for key-value services, 2014, ACM SIGCOMM】。我们的工作提供了确切的原因:RECV因为CQE DMA而变慢;如果避免了它,RECV就和入站WRITE一样快。

推测(Speculation)技术。当前的RDMA实现允许在仅头部SEND的数据包头中携带4字节的应用程序数据。对于需要更大消息的应用程序,如果可以进行推测,就可以使用仅头部SEND/RECV;我们在第4.2节中为8字节序列器演示了这样一种设计。总的来说,推测的工作方式如下:客户端在请求中传输它们期望的响应,在常见情况下得到一个小的确认响应。例如,在带有客户端缓存的键值存储中,客户端可以使用WRITE或常规SEND发送带有键及其缓存版本号的GET请求。如果版本有效,服务器则回复一个仅头部的“OK” SEND。对于某些应用,每个消息4字节的数据就足够了。例如,TPC-C【29, TPC-C】基准测试中的一些数据库表的主键大小在2到3字节之间。表访问请求可以使用仅头部SEND发送(使用剩下的1字节指定表ID),而响应可能需要一个更大的SEND。

5.3 非批处理操作

5.3.1 入站READs和WRITEs

性能和瓶颈。图12显示了入站READ和UC WRITE的实测吞吐量,以及入站READ的InfiniBand带宽限制。我们没有显示WRITE的InfiniBand限制和PCIe限制,因为它们更高。在我们的集群上,入站READ和WRITE最初受限于NIC的处理能力,然后受限于InfiniBand带宽。带宽成为瓶颈的载荷大小取决于NIC的处理能力相对于带宽。对于READ,CX、CX3和CIB的转换点分别约为128字节、256字节和64字节。CIB NIC足够强大,可以用64字节的READ饱和112 Gbps,而CX3 NIC需要256字节的READ才能饱和56 Gbps。

设计启示。这个转换点对于那些需要在READ的大小和数量之间进行权衡的系统来说是一个重要因素:对于小项目(约32字节)的键值查找,FaRM的键值存储【13, FaRM: Fast remote memory, 2014, 11th USENIX NSDI】可以使用一个大的(约256字节)READ。在一个入站READ决定GET性能的客户端-服务器设计中,这种设计在CX3上表现良好,因为32字节和256字节的READ具有相似的吞吐量;而其他设计,如DrTM-KV【30, Fast in-memory transaction processing using RDMA and HTM, 2015, 25th SOSP】和Pilaf【24, Using one-sided RDMA reads to build a fast, CPU-efficient key-value store, 2013, USENIX ATC】,它们使用2-3个小的READ,可能在CIB上提供更高的吞吐量。

5.3.2 出站READs和WRITEs

性能总结与WQE缓存未命中。为简洁起见,我们仅呈现CIB上非批处理出站操作的性能摘要。大于28字节的出站UC WRITE,即WQE跨越一个以上缓存行的WRITE(表1),最高可达80 Mops,并受限于PCIe MMIO吞吐量,类似于非批处理出站SEND(图11a)。READ最高可达88 Mops,并受限于NIC处理能力。实现高出站吞吐量需要通过流水线维持多个未完成的请求。当CPU发起一个RDMA操作时,工作队列元素被插入到NIC的WQE缓存中。然而,如果CPU注入新WQE的速度快于NIC的处理速度,这个WQE可能会被新的WQE驱逐。这可能在NIC最终处理此WQE以生成其RDMA请求包、服务其RDMA响应或两者兼有时导致缓存未命中。图13a总结了此模型。

实验与观察。为了量化这种效应,我们在CIB上进行了以下实验:服务器上的14个请求者线程向14个远程进程发出N个8字节READ或WRITE的窗口。在图13b和13c中,我们显示了累积的RDMA请求率,以及使用PCIeRdCur计数器率表示的WQE缓存未命中程度。每个线程在发出下一个窗口之前等待N个请求完成。我们使用服务器上的所有14个核心来产生最大可能的请求率,并使用RC传输来包括处理WRITE的ACK时产生的缓存未命中。我们得出以下观察,显示了WQE缓存在提高和理解RDMA吞吐量方面的重要性:
* 最佳窗口大小不明显:最大吞吐量的最佳窗口大小并不明显:吞吐量并非总是随窗口大小增加而增加,并且取决于NIC。例如,N = 16和N = 512分别在CX3和CIB上最大化READ吞吐量。
* 吞吐量与PCIe读取的权衡:更高的RDMA吞吐量可能是以PCIe读取为代价获得的。例如,在CIB上,随着N的增加,READ吞吐量和PCIe读取率都增加。尽管最大的N对于只发出出站READ的机器是最优的,但如果它还服务于其他操作,则可能不是最优的。
* READ与WRITE的差异:CIB的NIC可以处理CPU的峰值WQE注入率以进行WRITE,并且从不遭受缓存未命中。但这对于READ不成立,表明它们比可靠WRITE需要更多的NIC处理。

5.4 原子操作

性能与锁争用。NIC处理单元在原子操作期间会争用锁(第3.4节)。原子操作的性能取决于工作负载中的并行量与NIC内部锁定方案的关系。为了改变并行量,我们在服务器内存中创建了一个包含Z个8字节计数器的数组,多个远程客户端进程在每次迭代中对随机选择的计数器发出原子操作。图14显示了此实验中的总客户端吞吐量。对于CX3,无论Z为何值,吞吐量都保持在2.7 Mops;对于CIB,吞吐量上升到52 Mops。

推断锁定机制。CX3吞吐量图的平坦性表明它将所有原子操作串行化。对于CIB,我们测量了随机选择的地址对的性能,并观察到当两个地址的低12位(LSB)相同时性能较低。这强烈表明CIB使用4096个桶根据地址来分派原子操作——一个新操作会等到其槽位为空。

瓶颈和启示。由于串行化,CX3上的吞吐量受限于PCIe延迟。对于CIB,PCIe读-修改-写所需的缓冲和计算使NIC处理能力成为瓶颈。两个NIC上Z=1时的糟糕吞吐量再次证实,原子操作对于序列器来说是一个糟糕的选择;我们在第4节中的优化序列器用单个服务器CPU核心提供了12.2倍的更高性能。然而,用于数据存储的锁服务可能会使用更大的Z。如果这样的应用使用CIB,原子操作可能表现良好,但对于CX3(这是先前工作【27, Designing a low-latency cuckoo hash table for write-intensive workloads, 2014, WSRC】【30, Fast in-memory transaction processing using RDMA and HTM, 2015, 25th SOSP】中使用的NIC)来说,它们非常慢。即使使用CIB,仍需要仔细进行锁的放置。例如,如果页对齐的数据记录的锁变量在记录中的偏移量相同,那么所有锁请求的低12位将相同并被串行化。一个将锁放置在不同记录中不同偏移量的确定性方案,或者一个将锁与数据分开保存的方案会表现更好。

6. 相关工作

高性能RDMA系统。设计高性能RDMA系统是一个活跃的研究领域。最近的进展包括多个键值存储系统【24, Using one-sided RDMA reads to build a fast, CPU-efficient key-value store, 2013, USENIX ATC】【13, FaRM: Fast remote memory, 2014, 11th USENIX NSDI】【20, Using RDMA efficiently for key-value services, 2014, ACM SIGCOMM】【30, Fast in-memory transaction processing using RDMA and HTM, 2015, 25th SOSP】【28, Nessie: A decoupled, client-driven, key-value store using RDMA, 2015, University of Waterloo Technical Report】和分布式事务处理系统【30, Fast in-memory transaction processing using RDMA and HTM, 2015, 25th SOSP】【12, Fast and general distributed transactions using RDMA and HTM, 2016, 11th ACM EuroSys】【14, No compromises: Distributed transactions with consistency, availability, and performance, 2015, 25th ACM SOSP】【11, The end of slow networks: It’s time for a redesign, 2015, CoRR】。这些系统中的一个关键设计决策是verbs的选择,这是通过基于微基准测试的性能比较做出的。我们的工作表明,这些比较有更多的维度是这些项目未探索的:如果不探索底层因素和优化的空间,就无法详尽地比较两个verbs,而这些因素和优化每个都可能使verb性能相差数倍。

网络I/O的底层因素。尽管有大量工作测量网络通信的吞吐量和CPU利用率【18, PacketShader: a GPU-accelerated software router, 2010, ACM SIGCOMM】【16, Comparison of frameworks for high-performance packet io, 2015, ANCS】【26, netmap: a novel framework for fast packet I/O, 2012, USENIX ATC】【13, FaRM: Fast remote memory, 2014, 11th USENIX NSDI】【20, Using RDMA efficiently for key-value services, 2014, ACM SIGCOMM】,但关于理解网卡底层行为的现有文献较少。NIQ【15, Network interface design for low latency request-response protocols, 2013, USENIX ATC】提出了以太网NIC和CPU之间PCIe交互的高层视图,但没有讨论批处理传输期间发生的更微妙的交互。Lee等人【22, Platform io dma transaction acceleration, 2011, CACHES】使用PCIe协议分析仪研究了以太网卡的PCIe行为,并将PCIe流量分为Doorbell流量、以太网描述符流量和实际数据流量。类似地,使用PCIe分析仪分析RDMA NIC可能会比使用PCIe计数器揭示更多关于其行为的见解。

A5 结论

设计高性能RDMA系统需要深入理解底层的RDMA细节,例如PCIe行为和NIC架构。本文的研究证明了这一点:我们最优的序列器比现有设计快约50倍且能完美扩展;我们优化的HERD键值存储比原始版本快83%;我们最快的传输方法比常用的基准快3.2倍。我们相信,通过提供清晰的指南、基于这些指南的显著优化,以及用于在用户硬件上进行底层测量的工具和实验,我们的工作将鼓励研究人员和开发人员在使用RDMA硬件构建高性能系统之前,更好地理解它。

A6 附录

附录 A. WQE-by-MMIO和Doorbell的PCIe使用情况

我们将doorbell的大小表示为d。使用WQE-by-MMIO方法从CPU传输到NIC的总数据量是 Tbf = 10 * (⌈65/64⌉ * (64 + Pr)) 字节。通过缓存行填充,65字节的WQE在主机内存中被布局在128字节的槽位中;假设 Crc = 128,则 Tdb = (d + Pr) + (10 * (128 + Pc)) 字节。我们忽略了PCIe链路层流量,因为它与事务层流量相比很小:通常假设每4-5个TLP有2个链路层数据包(1个流控制更新和1个确认,均为8字节)【9, Understanding Performance of PCI Express Systems】,使得链路层开销<5%。代入d=8,得到 Tbf = 1800Tdb = 1534