今天分享最近阅读的一篇文章:“Breaking Band,A Breakdown of High-Performance Communication”,这篇文章发表在ICPP 2019会议。由加州大学欧文分校和ARM公司合作完成。从题目中可以看到,这篇文章是一篇评测型的文章,它主要对高性能网络点对点通信中的各部分开销进行了比较详细的分析。本文将首先介绍论文的研究背景与论文最核心的贡献,以及本文涉及到的一些相关知识。然后介绍论文的评测方法,评测结果,以及作者提出的一些优化方案。
1.研究背景及论文的主要贡献
我们知道,高性能网络通信的两个永恒的目标是高带宽和低延迟。其中,通信带宽主要受限于底层网络器件的物理带宽,随着底层芯片制造技术的不断突破,网络带宽也在稳步增长,当然,前提是有足够的CPU核进行通信;而与之相反,通信延迟主要由主机软件栈和I/O子系统共同决定。换言之,通信延迟与软件执行开销密切相关。因此,作者认为,为了进一步优化网络通信延迟,需要对软件通信开销和I/O子系统有进一步的了解。作者在论文中着重分析了点对点通信路径上各个组件的延迟开销,并提出了一个问题,即通信路径上的某个组件得到了一定的性能提升,整体通信性能能获得多少收益?
2.相关知识
首先是UCX通信框架介绍,作者在论文中使用的通信框架就是这个UCX。UCX是一个用于数据处理和高性能计算的通信框架,其目标是为应用提供统一的通信接口。UCX处于应用层和驱动层之间,它屏蔽了底层不同通信网络的差异,为应用提供统一的通信接口。UCX本身分为两层,其中UCP层实现高层接口,ULP层实现与底层通信网络的对接。UCX提供的通信接口主要可以划分为三类,即图中高性能计算使用的API,I/O访问的API以及通信连接的管理。
其中HPC API包括Tag Matching和Active Message接口。
I/O API包括流式接口,远程过程调用等。
连接管理包括地址解析,连接建立与断开等。
目前已经有很多项目迁移到UCX框架上,例如Nvidia的NCCL,主要实现GPU之间的通信;以及OpenMPI,MPICH等MPI通信库。论文的所有分析都是在UCX通信框架下完成的。
同时,为了追踪网络通信过程中在I/O子系统中的开销,作者使用了PCIe协议分析仪来抓取PCIe数据包。这也是作者自述与其它评测不同的地方。PCIe设备接入PCIe流量探测卡上,探测卡位于PCIe设备和主板PCIe插槽之间,捕获设备发送和接收的PCIe数据包,然后经过PCIe协议分析仪解析后在终端显示。(这里论文作者还提到了自己使用的最先进的风扇制冷系统 :-))。
下图给出了一个PCIe的流量示例。这里抓取了4个PCIe数据包,并按照PCIe的包格式进行显示。同时,每个包还会附带一个时间戳,该时间戳可以用于计算两次PCIe事务之间的时间间隔。除了PCIe流量,现在的PCIe协议分析仪还可以解析更高层次的协议,比如这里的四个PCIe包实际上是一个NVMe读请求的响应。
另外需要介绍的就是软件编程时使用的内存屏障。所谓内存屏障,按照维基百科给出的释义,指的是编程人员告诉CPU或编译器在进行操作的时候,严格按照一定的顺序来执行。屏障前后的指令不会由于系统优化等原因而乱序执行。那么,为什么需要内存屏障呢?下面给出了一个例子,两个线程在不同的核上运行,线程A通过修改flag来通知线程B获取data的数据。对于线程A,两条指令之间没有依赖关系,因此,出于优化考虑,第二条指令可能被调度到第一条指令之前。所以可能会产生以下不同的执行顺序。在第二种情况下,线程B首先捕获到了flag的更新,但此时data可能还没有全部写入,因此,读到的是还未更新的数据。注意这里是非预期结果而不是错误结果,这是因为站在编译器的角度来看,两个线程的执行结果都是正确的,但实际上线程A的指令有着隐含的顺序依赖关系。
Kernel提供了内存屏障,或者叫内存栅栏。方便编程人员告知底层软硬件保证内存屏障前后的指令不得乱序。在添加了内存屏障之后,线程A的两条指令保序执行,确保两个线程正确地进行数据同步。
在网络通信中,写门铃之前要设置内存屏障,以确保网卡在收到门铃后能够获取正确的描述符信息。预期的顺序是CPU准备描述符,写门铃,网卡读取描述符。但实际上写门铃的指令可能被调度到准备描述符之前,所以需要加入内存屏障来确保执行顺序。需要补充的一点是X86 CPU对于in/out指令不会重排序,因此如果访问I/O用的是PMIO而不是MMIO,可以省掉内存屏障的操作。毕竟内存屏障有一定的开销。
3.评测方法
系统由两个节点通过交换机连接。在网卡和主板之间接入PCIe逻辑分析仪。通信软件使用的是MPICH以及UCX框架。其中软件部分的开销通过读取CPU计时器获取,I/O子系统,网卡和交换机的延迟通过逻辑分析仪的时间戳获取。
4.注入开销与端到端延迟分析
作者评测的内容主要包括注入开销和端到端延迟。
首先是注入开销的评测。注入开销指的是软件连续发送请求时,网卡收到的不同消息之间的时间间隔。在同步发送模式下,软件每次Post之后一定要Poll一次完成事件才能继续发送。这其中包括使用PIO发送门铃和描述符,发送网络数据到对端,接收响应ACK,写完成事件,写回内存。实际上在第一次Post后,如果发送队列还有空闲,可以继续Post,来掩盖I/O子系统和底层网络的延迟。
也就是图中所示的异步处理。在p次Post之后,对应地去轮询p次完成事件。平均而言,网卡观察到的注入开销就是Post和Poll的开销之和,同时加上通信框架更新统计数据的开销。
接下来作者对实际的注入开销进行了评测。实验中软件连续发起Post请求,使用逻辑分析仪记录两次MemWr之间的时间差,这个时间差就是实际的注入开销。测试值与估算值的误差小于5%。
然后是端到端延迟的分析。其中Post和Poll的开销通过CPU定时器获取。
PCIe写延迟的时间通过计算MemWr和其对应的ACK的时间差获取。
网络延迟通过计算NIC接收到MemWr和NIC写完成事件的时间差获取。这里NIC接收到MemWr是指消息到达网卡,写完成事件则代表对端将ACK返回。因此,其时间差就是网络延迟。
最后一项RC-To-Mem,指的是RC收到PCIe包后写入内存的延迟。该项通过重复的ping-pong测试获取。即网卡收到一个pong消息时,写完成事件,同时,经过I/O子系统和CPU的一系列处理,再发送一个ping消息到网卡。因此,RC写内存的时间可以通过上述时间差减去其中PCIe和软件的开销获取。
通过前面公式估算出的延迟与真实测试的延迟误差小于5%。
接下来我们把前面测试的各项开销按照百分比的方式展示出来,观察它们在开销中所占的比例。从端到端延迟划分来看,主要延迟耗费在了软件处理和I/O子系统上。对于软件产生的开销,HLP代表高层通信框架开销,LLP代表底层通信接口及驱动引起的开销。高层抽象将软件开销翻了一倍多。
5.优化方案
通过对上述通信过程的分析,作者提出了可能的通信方案。
其一就是观察到在整体通信延迟中,I/O子系统占据了很大一部分开销,因此,将CPU与网卡进行集成能够有效地消除这部分延迟。图中横轴代表的是降低某部分开销的比例,纵轴代表的是性能提升。最上面的数据线是下面两条线之和。可以看到,在极端情况下,当I/O子系统的开销降低90%,系统延迟能够降低35%。另一方面,在底层网络延迟方面,虽然从计算结果来看,将网络延迟降低90%能够带来约25%的延迟下降,但是,目前SerDes的带宽越来越高,这意味着PAM也越来越高,从而造成FEC更加复杂,反而会增加一定的延迟。作者对网络器件本身延迟的下降持悲观态度。
接下来我们看一个实际的CPU集成网卡的示例。这个是日本的K Computer使用的互联方案。CPU是在ARM架构的基础上做了进一步定制,每个CPU与一块本地的HBM高带宽内存直连,不同的CPU通过片上互连构成NUMA架构。同时,芯片上集成了6个网络接口,CPU通过片上互连访问这些网络接口,避免了PCIe及I/O子系统带来的开销。同时,每个网络接口和一个路由器连接,外部接入一个6D Torus网络。
这张图给出了从Tofu1到TofuD的端到端延迟分析。可以看到,在三代互连结构中,延迟下降了50%左右。接下来我们一项项看这些优化。首先是Rx CPU,下降的延迟来自于Cache Injection技术,该技术从Tofu 2代开始使用,网卡将数据直接写入CPU Cache,减少CPU访存开销,类似于Intel DDIO技术。从Tofu1到Tofu2,Tx和Rx的Host Bus被优化掉了,因为从Tofu2开始,网卡被直接集成在芯片内部。Rx TNI的和Tx TNI的延迟下降,一方面是网卡本身架构上的优化,另一方面,对于Tx TNI,从Tofu2开始,网卡能够直接从CPU寄存器中获取请求描述符,进一步降低了延迟。而Tx CPU的下降并不明显,这部分主要是发送方准备描述符等软件操作,依赖于CPU主频的提升和Memory延迟的降低。比较有意思的是Packet Transfer这部分,以下只是个人猜测。从Tofu1到Tofu2,Packet Transfer增加了很大一部分,我们刚才提到过,SerDes带宽的提升可能会造成延迟的增加,这部分增加的延迟可能来自于此。到了TofuD,这部分延迟又降下来了,那么我们猜测是不是TofuD的带宽并没有Tofu2高呢?后者的Link Bandwidth确实比前者要低,所以延迟的下降可能是这个原因。