使NVMe/TCP加速
保持对NVMe/TCP性能的最新改进。
下载演示:使NVMe/TCP加速
我Sagi Grimberg。我是Lightbits Labs的首席技术官和联合创始人,也是NVMe over TCP标准规范的合著者。
我们从一个简短的介绍开始。什么是通过TCP的NVMe ?NVMe通过TCP是在标准TCP/IP网络上运行NVMe的标准传输绑定。它遵循标准NVMe规范,该规范定义了队列接口和仅运行在TCP/IP套接字之上的多队列接口。它有标准的NVMe命令集,它被封装在我们称为NVMe/TCP pdu的东西里,用来映射TCP的流。在这个图中,我们基本上有NVMe架构,核心架构定义了管理,I/O和其他命令集。
01:14 SG:下面是定义胶囊,属性,发现的NVMe over Fabrics。而TCP上的NVMe基本上定义了额外的特性和消息传递,以及到底层结构本身的传输映射,在我们的例子中就是TCP/IP。
NVMe是如何通过TCP处理队列的或者说,NVMe是如何通过TCP定义队列的?基本上,每个队列映射到一个双向TCP连接,命令数据传输通常由一个专用上下文处理,它应该是在软件或硬件中。在这个图中,在左边,我们有一个主机它有一个到NVMe传输本身的队列接口,有一个提交队列和一个完成队列。所有的提交和完成都是在我们所说的NVMe-TCP I/O线程中处理的,或者是一些I/O上下文,由发出I/O的主机或网络触发,通常是完成I/O或接收数据。
02:24 SG:右边的控制器也是一样,基本上,这是上下文负责在主机和控制器之间传输数据。每个队列实际上通常映射到专用的cpu,但不一定,可能是更多的,其实可以少,但关键是没有controller-wide序列化,因此每个队列不依赖与其他队列共享磁带,这使得它非常平行。这里的图表是之前展示过的关于队列集合的标准图表,你也有管理队列,在主机和控制器之间,然后是一组I/O队列,队列对是提交和完成队列。在NVMe/TCP中,基本上每个队列都映射到一个双向TCP连接。所以,如果我们看一下延迟贡献,我们有一些可能会悄悄进来。首先,在串行化中,但在NVMe/TCP中,它是非常轻量级的,它是基于每个队列的,所以它可以很好地扩展。
03:43 SG:上下文切换。我们至少有两个由驱动本身贡献的,内存拷贝,通常是古董,我们可以做零拷贝作为内核级驱动。然而,在RX上,我们做内存复制,这不是一个很大的因素,但在非常,非常高的负载,它会导致额外的延迟。中断(NIC中断)无疑是有影响的,它们会消耗CPU并影响单个队列可以实现的可伸缩性。我们有LRO和GRO或自适应中断调节可以缓解一点,但延迟可能不那么一致。然后我们有套接字开销,它存在,但它真的不是很大,它非常快,因为套接字在多队列接口中是非竞争的,但在小的I/O中,它可能会产生影响。如果没有正确配置,中断、应用程序和I/O线程之间的亲和肯定会产生影响,我们将进一步讨论这方面的内容。很明显,缓存污染是由内存复制造成的,我们有一些,但在拥有足够大缓存的现代CPU内核中,这种污染并没有那么严重。
05:15 SG:然后我们会遇到线首阻塞,这在混合工作负载中很明显,我们会讲到如何解决这个问题。让我们以主机直接数据流为例。它开始当用户基本上问题文件或块I / O设备或一个文件描述符,然后我们忽略很多层堆栈,但最终由于排队回调,坐在司机本身,当然,我们谈论的是Linux。回调是NVMe / TCP请求队列和准备一个NVMe / TCP PDU的地方,一个内部队列进行进一步的处理,然后是I / O工作,这是I / O上下文或I / O的线程,它拿起这个I / O和开始处理它,发送给控制器。然后控制器对其进行处理,完成I/O,如果是读操作,则将数据发送回主机或结束完成。
06:21 SG:一旦主机接收它,第一个网卡,它产生一个中断说它有更多额外的数据报,应该处理的主机,然后NAPI被触发,基本上得到所有这些数据报,并将其插入到TCP / IP栈和过程。然后我们有驱动程序,当TCP消费者得到一个数据准备回调,在那一点它触发上下文基本上处理数据和完成,并完成I/O。下一个基本上是用户上下文完成I/O。如果它是一个异步接口,它可能会通过门防御或信号。现在,这里的东西要注意的是,首先,我们有一个上下文切换之间的部分,我们准备预定的I / O和部分I / O工作去接I / O和处理它,然后我们有一个软件中断之间的I / O队列,当NAPI触发,然后我们有一个额外的上下文,一旦数据就绪被调用,并且I/O工作上下文实际开始并处理I/O。
07:36 SG:这是我们可以消去的三个面积点。现在,第一次做了优化是解决混合工作负载,当我们说,队列的时钟可以明显的情况下我们有很多写的来自主机需要发送队列,然后主人发现自己因为NVMe / TCP发送大型消息定义了消息传递的TCP流,然后在它后面,是一个小的读操作,在这个大的写操作完成之前,它不能前进,如果它碰巧在一个队列上。
08:16 SG:因此,这个问题很明显,而缓解的方法是从分离Linux块层目前允许的不同队列映射开始的,这基本上可以定义不同的I/O类型,可以使用不同的队列。Linux上有三种不同的队列映射。其中之一是默认,它会托管默认队列集,通常只定义默认队列映射,所有I/ o都会在队列映射中使用它。
然后我们有一个专门的队列映射只是为了读I / O,这样读起来会有一组专用的队列相关联,剩下的将默认队列映射,然后第三个是队列映射,通常当应用程序信号通过一个国旗,一个高优先级标志I/O到内核,它对一个高优先级I/O命令感兴趣,它将被定向到极点队列映射,这个极点队列映射实际上可以设计主机延迟敏感的I/O。
09:38 SG:现在,我们所做的是一旦我们有混合工作负载,我们有不同的读者和作者都驻留在相同的应用程序,甚至不同的应用程序相同的主机,这些读写现在将通过不同的队列,所以他们永远不会看到head-of-line读写之间的阻碍。因此,我们所做的基本上是根据用户请求在NVMe/TCP中分配额外的队列,然后通过插入运行基准测试…我们将它插入区块带,运行基准测试,看看有什么改进。测试基本上是有16个读取器,每个读取器都在做同步读取,一个接一个,在后台我们有一个未绑定的写入器它发出一个大的突发,一个兆字节的写入。现在,这是一个未绑定的,这意味着这个线程,在一个高队列深度中发出一个兆字节的写,将在不同的CPU核之间旋转,并将最终与这16个读取器共享队列。
十一10 SG:基线,我们看到了QoS阅读,阅读IOPS大约80 k IOPS平均延迟396微秒和尾巴延迟可以14毫秒,这显然不是好,但现在我们分离的读取和写入不同的队列映射,我们现在得到的是IOPS增加了一倍多,达到171K IOPS,平均读延迟不到一半,为181微秒,4个9的读尾延迟提高了一个数量级。理解这一点很重要。
接下来,我们有亲和优化。现在我们有了不同的队列映射,接下来的问题是它们如何与I/O线程相关联以及它如何与应用程序相关联。基本上,Linux中的每个NVMe/TCP队列都有一个I/O CPU,它定义了I/O上下文在哪里运行。我们所做的是,我们对不同的队列映射使用了单独的对齐,每个队列映射都以0开始,所以它们是单独的,而不是合并在一起,这实现了非常好的对齐…这是您在应用程序和I/O线程之间所希望的,而不管I/O最终到达的队列映射是什么。
SG中午12:所以我们实际上做了一个微基准测试来测试这个改进,在这个基准测试中,我们测试了队列深度一个4K标准读延迟,然后我们再次使用了一个单线程应用程序,单线程工作负载队列深度是32,同样是4K读。所以,在队列深度1中,我们得到了10%的改进,这很好,然后队列深度32,我们实际上获得了更多的IOPS,从179K到231。这是开箱即用的,这绝对是一个很好的改进。
然后,我们将集中讨论触点切换以及我们已经做了什么来减轻它。所以需要的目标路径,记得有一次在两个幻灯片之前,我们表明,一旦排队回调,司机排队回调,NVMe / TCP队列请求得到一个I / O,实际上在其内部线索然后触发我们的上下文切换的I / O环境拥有TCP应变。所以我们要消去它。
14:10 SG:基本上我们在这里做的是在回调本身中,我们实际上从那个上下文直接发送I/O消除那个上下文开关上的I/O线程上下文。显然,现在我们有两个上下文可以同时访问TCP连接,它们需要序列化。所以我们需要增加它们之间的串行化上下文,这是一种沉默的测试,以及网络发送不保证原子,因此我们需要更改计划锁在心脏和阻止层改变它的队列接口RC SRC。
但考虑到我们现在有两个上下文可以同时访问流,我们可能不想争它。我们只做这个优化如果队列是空的,这意味着这是不必要的,即使队列是空的,这是一个顾问表示,只有在地图CPU与CPU运行,这意味着如果我们最终。如果I/O上下文和提交的上下文在同一个CPU端口上运行,这意味着如果我们与某人竞争,那就是与我们自己竞争,所以我们很可能能够在不被调度的情况下获取锁。
15:40 SG:所以,我们做到了,第二个优化是添加软件优先级,这基本上定义了指标排队与ADQ技术,出口流量基本上被导向专门的ADQ和NCQ集。
现在,移动到RX平面,正如我们提到的,一旦我们得到一个中断,我们不直接从中断本身处理I/O,我们实际上触发工作线程的I/O上下文,来实际处理传入的数据并完成。如果应用程序是轮询,我们提到过轮询队列设置我们提到过Linux中支持的超优先级标志。
现在我们有了能够通过直接I/O或I/O环轮询的应用程序。因此,我们基本上添加了NVMe/TCP轮询回调,并将其插入多队列块轮询接口。现在轮询接口调用,只是调用,网络堆栈中的BC功能,然后如果我们轮询应用程序轮询,我们就能在数据中识别它当我们得到回调时,我们不安排额外的上下文,我们不调度I/O工作线程,因为我们知道我们将直接处理来自轮询的传入数据。
17:27 SG:所以基本上,在现代的NIC cpu中断调节中,它可以很好地减少一些中断,但在ADQ中,它看起来中断更积极地减少,它工作得很好。于是我们就这么做了……
一旦我们添加了这两个消除上下文切换,我们再次运行相同的基准测试。现在基线是应用了I/O优化,我们实现了额外的10%队列深度1标准延迟4K读取,我们还实现了,我认为,在队列深度32,从一个线程的230K IOPS和247K IOPS之间的5到10%的改进。好,很好。接下来,当然,最新的E800系列,英特尔的最新网卡引入了一些ADQ改进。
18:37 SG:那么,ADQ有哪些改进呢?一般来说,首先是流量隔离,您可以关联一个特定的队列集并配置NIC,这样它就可以将特定的应用程序工作流引导到一个专用的队列集,该队列集不与主机的其他应用程序共享。所以入站配置基本上就是定义队列集配置,应用TC花过滤,然后通过RSS或流控制器添加队列选择。出站设置软件优先级,我们提到过,我们通过模式参数在NVMe/TCP控制器中打开了软件优先级,然后还配置了传输包的扩展,引导XPS匹配对称队列。
19:36 SG:值是我们在邻居工作负载和机会之间没有嘈杂的流量,以为特定工作流程自定义网络参数,这是我们在NVME / TCP中杠杆的东西。显然,其他改进,我们通过应用轮询来最小化接触切换并中断开销。因此,一旦申请投票友好,我们基本上就是使用ADQ配置的专用队列,它们充当NVME / TCP的轮询队列,即队列集。我们正在做的是,我们正在将应用程序上下文中的网络完成排放为轮询,我们将直接在应用程序上下文中处理完成。我们在响应时发送请求,正如我所说的那样,直接通过消除I / O上下文和应用程序之间的上下文切换。
我们还能够将多个队列分组到单个网卡硬件队列中,从而基本地简化了网卡硬件队列的共享,而不会引起不必要的上下文切换。因此,如果我们提醒自己,在驱动程序中我们有两个上下文开关,一个是DTX, PAX,一个是RxPax,我们有一个软中断驱动的网卡中断,现在我们消除了所有这三个与增强和ADQ一起。因此,这个值降低了CPU利用率、延迟和延迟抖动,总的来说都很低。
SG:日月星辰所以这里有一些测量,NVB TCP与ADQ启用或禁用,和延迟在QDAP1实现…当然,我忘了提到,这是在媒体本身之上添加的延迟,现在的传输开销是17.7,它可能更少,取决于CPU类。在QDAP32上,我们实际上在单个线程中从245K IOPS到341K IOPS之间获得了近30%的巨大改进,甚至更多。现在,如果你将这个线程相乘,并将它们应用到你从主机获得的总体吞吐量上,你基本上可以非常线性地扩展到一个点,你可以用几个核或10个核达到主机饱和。
22:37 SG:好吧。现在我们将得到最终的优化,这几乎与轮询工作负载中的所有延迟优化是分离的,我们将转移到与批处理相关的延迟优化,这更多地针对不太关心规范延迟和高优先级I/O的应用程序,但更多的是关于拥有面向带宽的工作负载,但在高带宽存在的情况下仍然实现良好的延迟。所以,优化是面向批处理的。我们想要的是利用块层在驱动程序中提供的关于队列建立的信息。因此,从驱动程序的角度来看,你并没有block层中建立的所有队列的真实视图。
二三35 SG:我们要做的第一件事是,第一件可用的事,block层基本上可以指示驱动程序正在排队的请求是否是最后一个。然后我们要做的是修改队列以及它如何处理内部队列。我们基本上让它更轻量级,将它从一个受锁保护的列表移动到一个无锁列表。然后,通过这个,我们基本上…在队列接口和从该队列提取请求的I/O上下文之间,我们将其设置为面向批处理的推和拉操作,这样我们可以获得更好的利用率或更少的原子操作和更多的批处理。
4:43 SG:基本上,所有这些信息,现在我们有一个更好的查看队列的建立和我们更高效的处理,然后通过消息添加这些信息,点击网络堆栈旗帜上的我们的发送操作网络更有效的行动。例如,如果我们知道我们要向网络发送一段数据并且我们知道我们身后有一个队列,我们会用更多的消息来表示它。我们会提示堆栈,它需要等待更多数据,而不是一起发送,它有机会批处理。但如果这是最后一块数据队列中,我们将发送和我们不知道的东西建立背后,我们打开消息记录显示堆栈,不管它了,它应该继续,刚才发送它。
25:53 SG:最后一个,是由康奈尔大学的一个团队所做的,他们建立了一个I/O调度器,它针对TCP流批处理工作进行了优化并优化了带宽和延迟。你可以在i10的论文中找到通过一个链接,或者您只能搜索“i10康奈尔”。
此I / O调度程序正在上游,并将很快提交。康奈尔球队已经完成了几个基准结果,我们可以在左侧看到这里,这只是标准的标准,或没有I10 I / O调度程序的上游NVME / TCP和优化。我们看到它可以实现的4K读取IOPS,我们在这里看到延迟大约100微秒,一旦我们进入145或仅限150岁以下,延迟就开始飙升,因为我们没有任何更高的效率并且只是延迟开始积累,我们有点击中墙壁。随着I10,有一些牺牲,小QDAP的潜伏期小,但在较高的QDAP中,我们可以看到我们可以推动超过200k的IOP或近225k的IOP,直到我们击中这种延迟刚刚开始增加。所以,它向我们展示了什么,首先是吞吐量的更高,这是一个单线。吞吐量高,IOP越高,延迟也保持不变。
28:01 SG:在右手边,我们可以看到吞吐量,我们有16个核心对RAM设备。16芯,我们看到,总体而言,所有请求的吞吐量的大小,直到它几乎浸透网卡,与一块改进,基本上应该建议我们,批处理和批处理是特别有用的,当我们谈论一个基于工作流的应用程序,NVMe / TCP。好吧。我们现在正在录音,所以我们没有任何问题,谢谢你们的聆听。