LEC22
大纲
- OS网络栈性能
- IX系统作为学习案例
Linux网络软件架构
图示,略
注意事项
- 只有kernel可以访问NIC网卡硬件
- 只有kernel可以解包,比如ARP和TCP
- kernel负责阻止不同连接互相影响
- kernel网络栈中存在大量锁和核间共享,比如队列,TCP连接状态等.
网络软件架构是如何影响性能的?
聚焦于网络高性能服务器
- 比如,带缓存的键值对存储服务器
- 高请求频率
- 短的请求报文和回复报文
- 同时存在大量的请求客户端和大量的并发请求.
- 希望在高负载下,获取大吞吐量(每秒处理的请求数)
- 希望在低负载下,获取低时延(每个请求的处理时间)
吞吐量硬件限制
- 10G以太网卡: 每秒处理1500w个小包
- 40G以太网卡: 每秒处理6000w个小包
- 内存: 每秒处理数G数据
- 中断产生: 每秒产生100w中断
- 系统调用: 每秒数百万
- 竞争锁: 每秒100w
- 核间数移动: 每秒数百万
- 综上: 8.1 如果受限于网卡和内存,每秒约处理1000w短请求 8.2 如果受限于中断和锁,每核每秒大约可以处理100w请求
延迟要求
- 延迟很重要,尤其对于较大的网页.
- 低负载: 一系列处理步骤的时间总和. 2.1 网络传输速度,及交换机处理速度. 2.2 中断 2.3 队列处理 2.4 睡眠和唤醒 2.5 系统调用 2.6 核间数据移动 2.7 内存读取
- 高负载 3.1 延迟主要由队列等待时间决定 3.2 高吞吐量下的效率提升可以减少队列等待时间. 3.3 突发的到达会增加队列时间 3.4 突发的服务时间会增加队列时间. 3.5 结构问题会导致队列时间增加,负载不平衡或者有队列没有处理等. 3.6 延迟问题定位很困难,也很难提升.
IX: 被设计为高性能网络栈
- OSDI 14
- 基于Linux
- 不同的系统调用API,不再支持Linux API
- 不同的网络协议栈架构,不再使用Linux的协议栈和设计.
IX: 图例Figure 1(a)
IX注意事项
- IX通过Dune运行在VMX non-root模式,即guest模式.
- IX内核运行在CPL=0
- IX应用运行在CPL=3
- Linux内核给予IX权限,访问NIC队列和调用CPU核,然后Linux基本不再被运行.
- 报文缓冲通过内存在IX内核和IX应用间共享,所以报文数据不会被拷贝.
主要优化思想: 系统调用接口
- 问题: 当报文比较小的时候,系统调用消耗比较大.我们希望可以发送比系统调用次数更多的数据包.
- 解决方案: run_io() 2.1 run_io参数描述了同时向多个TCP连接写入. 2.2 run_io返回值描述了多个TCP连接的传入数据. 2.3 更为通用的,返回新的连接事件. 2.4 所以,每次用户空间/内核转换都可以完成很多工作.尽量减少这种转换.
- 伪码:
while True: run_io(out, in) for msg in in: process msg out.append(reply)
主要优化思想: 运行直至完成 Figure 1(b)
- 问题: Linux使用CPU时间,分阶段通过队列来移动数据包.
- 队列的优点在于应用可以做其他事情.缺点在于会降低性能,比如锁,缓冲,核间通信.
- 什么是运行至完成? 3.1 在完成一组输入请求前,不开始另一组请求处理. 3.2 完成处理包括: 驱动,TCP协议栈,应用处理,入队回复.
- 如何实现? 4.1 run_io,系统调用陷入内核,返回所有需要app处理的数据包. 4.2 app将回复报文通过run_io,传入内核.
- 这么做的原因? 5.1 单个线程处理一组报文的所有步骤. 5.2 避免了队列,睡眠/唤醒,上下文切换,核间数据传输. 5.3 相较于长队列,有助于活跃的数据包缓存于CPU数据缓冲区 5.4 避免了每个阶段的处理器平衡困扰.
主要优化思想: 轮询代替中断
- 问题: 中断消耗较大,而且中断时冗余的,如果通常都需要等待输入.
- 什么是轮询? 2.1 阶段性检查NIC的DMA队列,查看是否有新的输入.并且禁止NIC中断.
- 实现的困难 3.1 检查时机,在每个循环中设置检查点? 3.2 检查太频繁会浪费CPU 3.3 检查太少,会导致高延迟,队列溢出.
- IX的解决方案
4.1 每个CPU核专门运行一个app线程,
while(1) { run_io(); ... }
4.2 run_io轮询NIC DMA队列 4.3 没有CPU时间浪费,如果没有输入,则CPU没有任务需要完成 4.4 如果有输入,则获取一组输入,并将之返回给App. 4.5 轮询在低负载时,频率高.高负载时,频率低.效果非常好,论文称之为自适应轮询
主要优化思想: 多核并发
- 问题: 单核通常吞吐量不佳,往往会浪费网卡的大部分能力.
- 解决机会: 2.1 现实中通常会同时存在大量客户端. 2.2 每个客户端连接基本是独立的. 2.3 所有现代计算机都有多个CPU核.
- 风险: 3.1 竞争锁代价巨大. 3.2 核间数据移动代价巨大.
- 为了避免数据移动和锁 4.1 每个客户端的所有操作均在一个核上完成,包括TCP协议栈和报文处理. 4.2 所有核间均不需要共享数据.
- 可能需要共享的数据示例 5.1 报文内容 5.2 NIC队列 5.3 包空闲列表 5.4 TCP数据结构 5.5 应用数据,尤其是内存DB.
主要优化思想: 网卡多队列
- 现代网卡通常支持多个独立的DMA队列,NIC使用过滤器和hash来选择某个队列.
- Linux为每个IX应用设置一个独立的NIC DMA队列. 2.1 每个IX应用都对应一个core,一个独立DMA队列. 2.2 Linux为每个IX应用在NIC中设置一个过滤器.
- NIC hash每个客户端的IP和port,来选择一个队列.
3.1
flow-consistent hashing
或者receive-side scaling
3.2 NIC将同一TCP连接的所有报文分配到同一核上. 3.3 因此,不需要在核间共享TCP连接状态. 3.4 不需要在核间移动数据. - run_io查找NIC DMA队列时,只处理分配给其核自身的报文.
- 一个新的连接通过NIC hash分配到某个核上,并希望籍此实现负载均衡.
主要优化思想: 零拷贝
- 如何避免IX和IX应用间拷贝TCP数据? 1.1 即不断切换CPL=0/CPL=3,类似kernel用户态和内核态的切换. 1.2 每秒40G的吞吐量,如果有数据需要拷贝,会显著增加内存的压力.
- IX使用页表映射报文缓冲,使得IX内核和应用可以同时访问. 2.1 NIC通过DMA访问这些内存 2.2 run_io通过指针传递这些内存地址
- App和IX协同工作以获悉缓冲区何时free,并通过run_io通知底层free的buffer.
IX的限制条件
- 假设同时存在大量并发的客户端,并向服务器发出大量小请求. 1.1 你或许需要一个单个传输速率达40G/s的连接
- 假设各核之间负载平衡良好 2.1 客户端请求被NIC分配到各队列上 2.2 所有请求的处理时间都大致相同. 2.3 是否可以重新分配队列? 2.4 是否可以处理其他队列的任务?
- 假设所有请求都是非阻塞的 3.1 服务端代码处理请求时,不需要读取磁盘,发送RPC,然后等待. 3.2 阻塞的请求会导致CPU闲置,并使得等待队列迅速增长. 3.3 是否可以将所有阻塞请求统一交由固定线程/CPU核处理?
评估
- 我们的目标是什么?
- 高负载条件下的高吞吐量,尤其是请求回复报文都较小的情况
- 低负载条件下的低延迟,尤其是吞吐量与CPU核数成正比.
Figure2
- 低负载下的延迟情况
- 测试场景为: 一个客户端,同一时间,发出一个请求.
- x轴为报文大小,y轴为吞吐量(G/s)
- 为什么随着报文大小的增长,曲线在上升? 4.1 随着报文大小增加,很多固定的开销被分摊. 4.2 因为大量报文存在,增加了报文成批处理的几率
- 什么限制了曲线的增长? 5.1 10G以太网报文的最小头部
- 为什么IX可以击败Linux? 6.1 对于小报文,比如64字节 6.1.1 延迟限制 6.1.2 IX轮询,会快速处理报文 6.1.3 IX没有中断,队列,睡眠和唤醒 6.1.4 更少的用户态/内核态切换 6.1.5 论文称: IX处理时间为5.7us,而Linux为24us.差距非常明显. 6.2 对于大报文 6.2.1 吞吐量限制 6.2.2 IX对于大报文没有特别的优势. 6.2.3 IX零拷贝可以对于性能有帮助.
Figure3(a)
- 衡量通过增加核数对于吞吐量的影响.
- 理想情况下,吞吐量随着CPU核的数量同步增加,直到达到硬件网卡的限制. 2.1 注意,这并不表示软件的有效性,仅仅是表明了有良好的并发性能.
- 测试场景为: 18个客户端,报文长度64字节,每个客户端一个连接.
- x轴为CPU核数,y轴每秒处理的报文数.
- 在开始阶段,为什么曲线上升? 5.1 工作被分配到更多的并发CPU核上. 5.2 18个客户端,所以最多会有18个核被并发使用
- 并发量和CPU数目成正比么? 6.1 在开始时,对于所有曲线而言,是这样的. 6.2 所以锁竞争并没有抵消并发产生的效果.
- 注意,IX每秒每核大约可以处理50w个请求. 7.1 每个请求处理时间为2ms或者4000个CPU周期.
- 为什么IX可以击败Linux? 8.1 轮询,批处理系统调用,运行直至完成.
- 令人印象深刻的是,IX在400w/s请求的情况下,依然性能线性增长. 9.1 这表明并发效果近乎完美. 9.2 RSS有所帮助 9.3 在软件实现上,必须没有锁,没有任何核间数据移动.
IX设计改动
- 每个App网络协议栈 1.1 而不是所有App共享一个协议栈 1.2 允许报文缓冲区共享,从而实现零拷贝.
- 将App和CPU核,以及线程绑定 2.1 而不是由kernel复用CPU核. 2.2 允许轮询和运行直至完成 2.3 使软件更有效率,更有效. 2.4 支持更多的CPU核并发.
- 将App和NIC队列绑定 3.1 而不是共享队列 3.2 更加直接的访问,效率更高 3.3 支持更多的NIC队列