TCP发展受阻的原因是什么呢?RDMA和Linux TCP技术解析

描述

在考虑今天如何开始时,我回顾了一下这两天关于硬件和软件之间分歧的主题演讲。主要探讨了拥塞控制如何在这个不断发展的领域中增长和演变。由于技术不断变革,问题也不断涌现,因此解决方案需要随时间调整。此外,ChatGPT的普及和加速器的进步正在从根本上改变通信模式,以及通信的负载和要求。因此,我想确保大家了解这是我们真正要讨论的重点。接下来的讲话将专注于该领域的一个具体方面。

TCP

有一些问题,我知道你们中的一些人之前已经遇到过。为什么你会再次提出这个问题?为什么你要对比RDMA和TCP?实际上,RoCE和IB已经很好地满足了大多数高性能通信需求。然而,还有很多工作要做,特别是关于启用乱序传输(Out-of-order delivery)和选择性确认(Selective Acknowledgements, SACK),以提高自由传输的效率。我们期望实现比现有方案更好的性能,其中拥塞控制机制是一个关键。今天早些时候,我们也讨论了一些类似的增强功能。这个领域非常值得关注,因为不同的工作负载和流程对拥塞控制方案有不同的要求。因此,这也是RoCE持续发展和改进的方向。

另外,当我们从围绕RDMA构建的特殊定制网络转向使应用程序更具通用性时,流量将将成为更加关键的因素。对于TCP的熟悉程度,以及在规模上调试问题的经验,理解如何处理问题、如何分配带宽、如何确保公平性等,这些都是TCP带来的挑战,并迫使我们重新思考和审视这些问题。

最后一个观点是,虽然RoCE通常运行在定制的后端网络或特定目的构建网络上,但构建一个完整的生态系统还需要考虑通用存储的需求。之前的讨论提到了云服务,这些服务几乎总是在某种TCP变体上运行。随着机器学习的不断增长,特别是推理服务在云路径上的应用,这些服务并不总是依赖于定制网络。尽管如此,它们仍然面临巨大的数据和带宽需求。

因此,问题在于TCP是否存在根本性问题,是否需要彻底改进,或者我们能否重新利用它。在这个领域,观点各异,有些人认为TCP对于数据中心来说并不适用,我们需要从头开始设计。Homa就是这样一个例子。但让我们深入探讨一下,看看我们能为TCP做些什么。

TCP

那么,为什么要关注TCP呢?在数据中心网络的领域中,拥塞控制算法的丰富性无疑是最为关键的一环。这是一个历经数十年深入研究的领域,提供了众多行为模型供我们选择,使得我们可以根据具体用例挑选最合适的模型。无论你是在寻找适用于短距离数据中心链路的方案,还是适用于远距离广域网链路的方案,甚至是混合链路的解决方案,都有相应的选项可供选择。这个领域内的拥塞控制、速率控制、公平性等概念易于理解,同时选择性确认和高效重传等机制也得到了广泛的研究。此外,预测性支持乱序包交付和有序完成的能力也是系统设计中不可或缺的一部分。

一个值得探讨的问题是:这不是之前已经尝试过了吗?显然,iWARP就是其中一次较大的尝试。在这次尝试中,人们试图将RDMA框架概念置于TCP基本传输方案之上。但这一尝试至少需要依赖硬件TCP引擎来实现,这实际上排除了很多可能性。你必须选择你能实现的东西,无论是固件还是硬件,最终基本上限制了TCP可用的速率、速度和灵活性。此外,在网络上执行TCP以及在RDMA与主机之间进行框架转换的尝试,使得该协议变得异常复杂且难以实现。

另一个尝试是名为OpenOnload或Onload的堆栈。该堆栈实际上将TCP库置于用户空间,并引导路径,以避免TCP执行受到应用程序线程的锁定争用,而是在应用程序的上下文中执行库。Solarflare的解决方案在高频交易(HFT)和高性能计算(HPC)领域取得了巨大成功,我相信这里的人们都有所了解。然而,它也面临着同样的问题。多年来,它已经偏离了可以称为标准TCP的东西,即Linux实现。这导致Linux堆栈经历的一些增强和改进在该模型中无法使用。随着时间的推移,它成为了一个分歧的解决方案,引发了一些额外的摩擦。

接下来是DPDK标准的经典用户空间TCP实现,这种实现经常在中间件中使用。它在电信解决方案中非常受欢迎,因为它试图以最小的CPU开销和最少的符合性或兼容性要求来执行TCP。然而,我认为它有些过于专业化,并不常见作为一种通用的广泛部署的解决方案。

TCP

因此,我们仍然面临一个问题:是否能让TCP在简单硬件上易于实现,并达到所需的性能水平

那么,多年来TCP发展受阻的原因是什么呢?套接字,对吧。我想在这方面不会有太多问题。同步的、基于系统调用的套接字接口存在问题,它与现代异步、低开销的标准接收器模型不符。然而,由于安装基数如此之大,实际上在短期内废除套接字是不值得实现的方案。

然而,在消息传递方面,我们确实面临着一系列挑战。一种常见的访问模式,在TCP上实现它却是相当困难的。特别是在字节流序列上构建消息框架,这本身就是一个棘手的问题。如果我们要支持无序的消息传递,那么难度将会进一步增加。虽然在高层次上有成功的例子,如NVMe-TCP,但这并不意味着这是一个简单的任务。你需要投入大量的工作来从字节流中提取PDU流,并确保每个层次都能独立且正确地工作。这意味着在效率和性能方面,我们需要付出相当大的努力。实际上,我们之前的讨论也提到了,你需要在某个暂存缓冲区中进行PDU分割,然后再进行DM处理。

传统上,TCP并不支持零拷贝技术,但稍后我们会看到关于零拷贝影响的相关数据。最新的Linux实现在发送(Tx)方面已经具备了相当不错的零拷贝功能,但在接收(Rx)方面仍存在一些问题。尽管Tx的实现很棒,且真正实现了零拷贝,但其效率仍无法与RDMA和写操作相比。关于内核页面翻转机制,一个关键问题是,除非CPU完全控制与实际页表相关的异构内存,否则它可能无法正常工作。这些页表可能位于PCI交换机中,并不受主机内存管理单元(MMU)的控制。

最后,TCP面临的一个挑战是精确放置和管理缓冲区。显然,你可以解锁一个缓冲区,并通过TCP套接字接口进行读写操作。但如果你希望进行更具体的操作,那么你不希望受到套接字缓冲区(SKB)的开销影响,也不希望从无法控制的内存空间分配任何额外数据。实现对应用程序的透明处理是相当困难的。因此,这些是我认为TCP存在的不足之处。

在接下来的演讲中,我们将探讨是否能解决这些问题,并讨论一个具体的实现方案。我们一直在努力尝试解决这些问题,并希望最终能够取得成功的结果。

TCP

我们目前正在参与的测试或生态系统,展示在左边的这张图片中。这是一块标准的Ryzen主板,上面插入了一张FPGA卡。这张FPGA卡上又连接了两个GPU。这张FPGA卡的功能类似于一个网络路由器和一个PCIe交换机,其PCI通道均为Gen 3 x16,以太网端口为100G。尽管速度是一个重要的考虑因素,但在这个设计中,从GPU出来的路径实际上并未达到100G的能力,而只能运行在大约40G。因此,我们不会过多地讨论这个路径的细节。

这个特定的FPGA设置,我们可以将其视为在Enfabrica中实际构建的设计的硬件近似。它支持一些令人印象深刻的功能。其中之一是它能够进行完整的硬件分类和转向。这意味着它可以一直查看到L5头。一旦它在L4级别识别出特定的流,特别是如果它知道了一个流是什么,它就可以跟踪在该流内部看到的字节偏移量。此外,它还实现了一个相当高效的TSO/GRO方案(幻灯片中的信息有误)。

TCP

让我们先来看一些数据,然后再深入探讨其实际含义。右边的图表展示了我们所利用硬件驱动所能实现的性能。很明显,我们无法达到800G的速率。因此,在这个实验中,我们基本上从数据包中移除了所有有效载荷字节,使其仅在头部模式下运行。我们尽可能地提高应用程序和堆栈的运行速度,使用的是未经修改的Linux内核,上面运行着我们的驱动程序。我们的目标是测量在各种MTU数据包下,我们可以实现多快的数据包速率和有效吞吐量。吞吐量是通过将分组数乘以空包大小来计算的。

从图表中可以明显看出,没有GRO的情况下性能相当差。使用软件GRO相比基准性能有了明显的提升,但仍然相对较低。然而,当我们使用我们的硬件GRO方案和修改后的驱动程序时,开销大大降低,我们实际上可以接近达到800G的数据包速率。为了实现这一目标,我们需要覆盖非常广泛的MTU范围,并且在较低的MTU大小下,我们的速率增长也相当可观。因此,实际上,我们可以只运行所需速度的堆栈。

这意味着,我们学到或之前推测的其中一点是,套接字API接口存在相当大的问题。它几乎增加了所有的开销,并影响了驱动程序实现和资源效率。具体来说,套接字API接口没有在关键路径中进行缓存分配,而是选择在非关键路径中进行。然而,值得注意的是,原生的SACK和ACK操作实际上运行得非常好。它们能够保持相当稳定的速率,并且确实从无序数据和有序完成模型中受益。因此,在出现轻微故障时,它们能够非常无缝地恢复,而不会引发真正的问题,前提是故障和重传发生在某个特定的窗口内。

另一件要考虑的事情是,如果我们仔细审视Linux堆栈本身,它已经成为最令人满意的堆栈之一。每个月都有大约五到十个提交,其中一到两个通常是具有显著性能优势的重要更新。此外,Linux堆栈拥有一个庞大的社区,这个社区非常活跃,持续关注着性能及其影响。

在拥塞控制算法方面,已经有了巨大的改进。像BBR这样的新算法在关键地方产生了重要影响。短队列的实现使得缓冲区膨胀不再成为问题,即使在使用TSO/GRO类型方案时也是如此。此外,无缝路由对于链路负载和链路(或分布数据)的问题至关重要。

特别是在机器学习和大数据类型应用的背景下,这一点变得更加重要。想象一下,如果你有200G的链路,但在短时间内所有流量都集中在其中一个链路上,那么你就会遇到问题,整体效率会急剧下降。

值得一提的是,使用eBPF作为可配置拦截器是一种非常有效和有趣的动态拥塞控制机制。此外,Big TCP也是一个解决方案,它实际上应该在之前提到过的某个地方被提及。TCP在有效负载大小方面并没有做得很好,而Big TCP通过超过16位有效负载大小的限制,允许主机表达比16位更大的缓冲区。

TCP

这个图表没有展示的是CPU利用率,这显然是一个关键问题,与其他堆栈一样重要。其中一个待解决的问题是,为了达到所需的性能水平,需要多少CPU资源。今天,我们将探讨其中的一些问题,但对于真正感兴趣的人,我建议关注这两个链接。在我们发布幻灯片时,这两个链接应该会出现在其中。David在这两次演讲中详细阐述了我们对某些研究的看法,以及这些小改变如何影响CPU利用率,并影响我们达到高数据包速率的能力。

我们确实研究过像io_uring和af_xdp这样的解决方案。io_uring显然具有很多潜力,它拥有许多吸引人的特性,为你提供了人们期望的异步直接访问接口。然而,这是一个新的API,将其融入现有的堆栈中,如机器学习,需要时间。也许将来会实现。零拷贝RX路径仍在开发中,需要付出大量努力才能有效地实现零拷贝RX并获得所需的结果。

我们在一定程度上研究了af_xdp,但基本上跳过了它。因为XDP的主要目标是将数据包快速送入用户空间,而协议栈仍然是一个待解决的问题。因此,它更接近我之前提到的DPDK解决方案。但实际上,你并没有获得一个经过良好测试、可互操作且高效运行的堆栈。

TCP

那么,我们构建了什么呢?我们构建的方式看起来像右侧这张相当复杂的图片。从这张图表中可以看出,大部分元素都是Linux堆栈中已有的部分,我们基本上没有进行大的修改,只是进行了一些细微的调整。核心的部分是我们所称的核心设备驱动程序,它负责驱动队列、进行维护工作,以及执行所有必要的操作。我们在堆栈中添加了一个非常标准的InfiniBand提供者和驱动程序库或驱动程序层。

这张图表应该与你所见过的其他图表非常相似。你的工作区域在用户空间,有工作队列和完成队列用于发送和接收工作,你还有一个提交密钥,可以直接将缓冲区发布到硬件上。这里最独特的地方在于这两个箭头。实际上,我们在侧通路上收集散射列表,并将其提交到内核中的TCP堆栈,使其驱动网络驱动程序推送传输数据包或读取请求数据包。

在接收路径上,当数据包到达时,我们执行完全相同的操作。当一个数据包到达时,我们通过这个库或这个堆栈,然后将数据放入接收队列中。而且,数据实际上将要到达的缓冲区的内存区域已经为硬件所准备,因此数据直接落在缓冲区所在的内存区域。

因此,这实际上是一种混合体,与你在现有框架中看到的TCP实现有所不同。但有趣的是,这张图表中的每一个元素都是真实存在的,我们只是对Linux堆栈中现有的方案和组件进行了重新组合和重新连接。

TCP

那么在我们的系统上实际运行起来是怎样的效果呢?我们采用的模型与之前的描述是一致的。现在,你的机器学习应用程序通过NIC与一个IB Verb堆栈进行通信,工作队列、发送和接收队列都对这个应用程序开放。应用程序通过驱动程序将这些操作传输到在CPU上运行的内核实现。有效载荷就位于这里,稍后你会看到我们提供的数据,对比了它在GPU内存区域和CPU内存区域之间的表现。正如之前所提到的,提交队列直接接入硬件,发送和接收工作请求通过内核驱动程序进行传递,并转换为我们的硬件队列格式,然后进行相应的处理。

值得一提的是,数据路径,特别是这个调用进入TCP/IP堆栈的部分,避开了近年来内核堆栈中大量的功能性增加。我们可以称之为“功能性增加”,即人们一直在使用和添加的各种钩子,用于劫持或接管数据移动。我们绕过了所有这些,对调用的内核函数进行了非常精心的选择。事实上,Linux堆栈始终建立在回调式设计的基础上,因此许多事情可以被隔离并单独调用,而不是从套接字的顶部一直到底部,以及反之亦然地获取整个数据包。

虽然这个模块显示的是TCP,但另一个值得注意的地方是,这个模块可以是任何东西。我们今天不会深入讨论这一点,但我们所讨论的结构模型允许我们用RXE替换这个模块。RXE是Linux内核中的一个RoCE实现。在接下来的讨论中,大部分内容将保持不变。

TCP

我们之前讨论过的一个问题是,在TCP字节流上放置消息时的无能或低效性。为了解决这一问题,过去的主要方法是使用iWARP,它位于RDMA堆栈的顶部。通过RDMA over DDP over MPA方案,在TCP的顶部构建了RDMA堆栈,以便在正确位置获得标记,并实现零拷贝的直接数据放置。

另一个选择,我指的是,这是一个L5协议,需要构建相当复杂的硬件。而我们正在添加或已实现的是这个概念:通过TCP进行消息传递。我们的方法是在TCP头部添加一个小的选项头,我称之为“BTH-like”,它就像一个信息头,包含了BTH的所有内容。假设它位于标准RoCE数据包中BTH标头所在的位置,它允许我们建立消息与队列之间的关联,以及确定传递消息的位置。特别地,它携带了两个信息字段:一个告诉我们操作的阶段,我们采用RoCE操作码;另一个告诉我们这个特定字节集合属于哪个消息,以及哪个字节偏移对应于该消息的起始位置。

这个方案类似于MPA或MPF标头解决的问题,但它允许我们的硬件根据消息序列获取缓冲区,除了字节序列外,还能正确处理消息,避免在消息叠加在字节流上时的混淆。这使我们相信这是一种更好的方法来解决问题,而不是iWARP。因为它使硬件保持简单,允许基本的TCP正常工作。当这个标头出现时,我们可以增强其功能并做一些更复杂的事情,而由此产生的效率提升是相当明显的。

然而,这种方法的一个缺点是我们目前将其添加为TCP选项头,这意味着中间设备可能无法正确处理。但对于我们当前关注的数据中心用例,我们认为这不是问题。如果人们希望与我们合作,将其纳入类似规范和标准的东西,以便中间设备可以配合,我们将非常感兴趣。

TCP

如果没有数据支持,谈话就失去了意义。在这里,我为你提供一些相关数据。在查看图表之前,我想先说明一下,我们展示的基准系统正是我们正在介绍的FPGA系统。如果你仅在此系统上运行TCP和iPerf,我们得到的延迟是几百微秒。但令人惊讶的是,这个系统能够以4K MTU或4K消息大小的速度达到每秒三百万个数据包,足以填满100G的带宽。

基于这个背景,我们再来仔细看看这些图表。左边的图表对比了我们与标准的RoCE。我想那应该是Mellanox CX-5,展示的是CPU到CPU或CPU到GPU之间的传输情况。这是针对熟悉GPU直通技术的人,其目的缓冲区来自GPU内存。你可以看到,延迟显然没有达到生产中基于硬件的实现水平。这主要有两个原因:一方面,我们在软件方面还有很多工作要做以提高效率;但另一方面,硬件运行的时钟频率只有我们所需的四分之一,并且缺乏许多优化。因此,在达到目标之前,我们的很多事务实际上需要多个DMA周期。

然而,我认为仍然值得骄傲的是,当你开始查看4K和16K的数字时,你会发现我们的延迟在这些范围内仍然相当平稳和稳定,而这在运行标准TCP时是无法看到的。在吞吐量方面,也许更有趣。同样,如果你注意到,在4K的表现上,我们确实比RoCE NIC差得多。这同样是目前软件和调优的问题,因为我们知道在运行标准TCP时我们可以达到100G的数值,所以这实际上是我们IB堆栈上的一个问题。

但更令人振奋的是,我们成功地掌握了曲线,达到了吞吐量。如果你看看4、16、64K大小的数据包,这是大部分机器学习工作重点关注的地方,我们的堆栈表现非常出色。另外,我应该指出的是,我们传递给GPU的数据,由于其再次通过我们的解决方案,与其他解决方案相比,其延迟稍微低一些。

TCP

最后一个图表将展示我们在这里要强调的关键点。通过我们所做的工作,有效地将堆栈与应用程序的其余部分以及服务这些操作的内存(在我们的案例中为GPU)隔离开来。你会发现,这实际上会带来更好的TCP性能。让我为你解释一下。

红色和蓝色的图表再次展示了标准的CPU到CPU和CPU到GPU操作。而黄色和青色的图表则揭示了当存在内存访问竞争者时,这两条曲线会发生什么变化。为了模拟这种情况,我们在系统上运行了stress-ng,并在后台最大限度地增加了DRAM的访问量。你会看到,CPU到CPU路径开始出现问题,有时甚至会产生严重的性能下降。而CPU到GPU路径则能够保持其性能,因为GPU存储控制器并没有受到stress-ng的太大影响。尽管蓝色和黄色的曲线略有偏移,但它们的峰值仍然保持在预期的位置。

因此,这个隔离模型不仅有效,而且实际上解决了一些性能问题,特别是与TCP性能要求相关的问题。

TCP

总之,通过让操作系统让步,将RDMA应用作为Linux TCP堆栈之上的应用程序接口,你将能够看到性能上的显著提升。

-----

可以支持RDMA读写流,那么可以支持的最大消息大小是多少?

是的,我们确实支持RDMA读写流。关于您提到的最大消息大小,我们的BTH头部策略中包含一个OP code,这使得我们可以在硬件中灵活地处理不同的处理上下文或其他RDMA操作。目前,我们在该头部设定了一个24位消息大小,这一设计选择是基于实现上的多种因素考虑,旨在达到一个性能与复杂性的平衡。即使在处理高达800G和TB级的消息吞吐量时,这一设计也不会成为性能瓶颈。同时,在重试或类似操作中,它也不会增加太多复杂性。

还有一些限制,也就是我们硬件的这种FPGA近似,有些事情我们的设备做得更好。

来自Peter Gotzman的问题,关于Lipsy控制路径加IB verbs数据路径,应用程序是否需要采取特殊步骤来分配和固定缓冲区,我很好奇应用程序需要改变多少。

对于RDMA应用程序来说,它们可以正常工作,无需进行其他更改。但如果你打算尝试某种应用程序级的混合操作,我建议你使用RDMA机制进行注册,然后按你的需要进行。

Bernard的问题,你是否了解过软件iWARP?它很好地印证了你对于PDU传递复杂性的观点,然而,当采用分段卸载的方式时,它的性能表现得相当出色。

是的,这与我们的观察结果相吻合。以NVMe-TCP为例,我们投入了大量时间去研究其内在含义。在实际操作中,我们可以协助硬件进行分段和分段信号的处理。然而,这些上层类型方案的问题在于,最坏情况下的性能可能会非常糟糕。尤其是当在PDU边界上进行重传时,而这部分操作已经完成并跨越了多条消息。

实际上,有人提出这个问题,我感到很高兴。因为我也想在这方面发表一些看法。你的观点是正确的。确实存在处理基于PDU的协议的方法,但其中存在一个基本的区别。那就是,如果你需要查看TCP流以找到PDU边界,那么你需要全面查看TCP的完整负载。这将更加昂贵,并且不太适合无状态的卸载。相比之下,如果数据包边界与消息边界对齐,那么处理起来会更加高效。

这意味着,由于你能够获得数据包边界,因此你只需要在数据包传输过程中简单地处理其头部信息。而有效负载部分可以更接近零拷贝,正如Shrijeet所提到的,这些数据可以直接发送到GPU或类似设备进行处理。

还有一个问题来自Sean,关于RDMA的后续问题。在该头部中没有RKEY。

是的,我们在那里并没有放置完整的结构。在RoCE中,RKEY并不位于BTH中,而是位于BTH后的扩展头部中。所以,我们的处理方式其实是类似的。如果TCP BTH中的OP code表示后面有一个用于RDMA操作的扩展头部,那么你会在那里查找任何内存键或其他内容。我们目前还没有深入研究原子操作,但理论上来说,这也是可行的。当然,我并不想过于预测未来,但理论上,你可以在那里放置一个原子扩展头部,或者任何与RoCE和InfiniBand类似的内容。

至于我想说的,Sean,我们正在寻求关于这个头部需要什么的参与或合作。另外,你关于层次结构的具体问题,我认为,引发了我们应该放置什么以及是否放置过多的问题。这可能会导致TCPE选项头部存在大小限制和扩展的含义。




审核编辑:刘清

打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分