LEC22

大纲

  1. OS网络栈性能
  2. IX系统作为学习案例

Linux网络软件架构

图示,略

注意事项

  1. 只有kernel可以访问NIC网卡硬件
  2. 只有kernel可以解包,比如ARP和TCP
  3. kernel负责阻止不同连接互相影响
  4. kernel网络栈中存在大量锁和核间共享,比如队列,TCP连接状态等.

网络软件架构是如何影响性能的?

聚焦于网络高性能服务器

  1. 比如,带缓存的键值对存储服务器
  2. 高请求频率
  3. 短的请求报文和回复报文
  4. 同时存在大量的请求客户端和大量的并发请求.
  5. 希望在高负载下,获取大吞吐量(每秒处理的请求数)
  6. 希望在低负载下,获取低时延(每个请求的处理时间)

吞吐量硬件限制

  1. 10G以太网卡: 每秒处理1500w个小包
  2. 40G以太网卡: 每秒处理6000w个小包
  3. 内存: 每秒处理数G数据
  4. 中断产生: 每秒产生100w中断
  5. 系统调用: 每秒数百万
  6. 竞争锁: 每秒100w
  7. 核间数移动: 每秒数百万
  8. 综上: 8.1 如果受限于网卡和内存,每秒约处理1000w短请求 8.2 如果受限于中断和锁,每核每秒大约可以处理100w请求

延迟要求

  1. 延迟很重要,尤其对于较大的网页.
  2. 低负载: 一系列处理步骤的时间总和. 2.1 网络传输速度,及交换机处理速度. 2.2 中断 2.3 队列处理 2.4 睡眠和唤醒 2.5 系统调用 2.6 核间数据移动 2.7 内存读取
  3. 高负载 3.1 延迟主要由队列等待时间决定 3.2 高吞吐量下的效率提升可以减少队列等待时间. 3.3 突发的到达会增加队列时间 3.4 突发的服务时间会增加队列时间. 3.5 结构问题会导致队列时间增加,负载不平衡或者有队列没有处理等. 3.6 延迟问题定位很困难,也很难提升.

IX: 被设计为高性能网络栈

  1. OSDI 14
  2. 基于Linux
  3. 不同的系统调用API,不再支持Linux API
  4. 不同的网络协议栈架构,不再使用Linux的协议栈和设计.

IX: 图例Figure 1(a)

IX注意事项

  1. IX通过Dune运行在VMX non-root模式,即guest模式.
  2. IX内核运行在CPL=0
  3. IX应用运行在CPL=3
  4. Linux内核给予IX权限,访问NIC队列和调用CPU核,然后Linux基本不再被运行.
  5. 报文缓冲通过内存在IX内核和IX应用间共享,所以报文数据不会被拷贝.

主要优化思想: 系统调用接口

  1. 问题: 当报文比较小的时候,系统调用消耗比较大.我们希望可以发送比系统调用次数更多的数据包.
  2. 解决方案: run_io() 2.1 run_io参数描述了同时向多个TCP连接写入. 2.2 run_io返回值描述了多个TCP连接的传入数据. 2.3 更为通用的,返回新的连接事件. 2.4 所以,每次用户空间/内核转换都可以完成很多工作.尽量减少这种转换.
  3. 伪码:
    while True:
     run_io(out, in)
     for msg in in:
       process msg
       out.append(reply)
    

主要优化思想: 运行直至完成 Figure 1(b)

  1. 问题: Linux使用CPU时间,分阶段通过队列来移动数据包.
  2. 队列的优点在于应用可以做其他事情.缺点在于会降低性能,比如锁,缓冲,核间通信.
  3. 什么是运行至完成? 3.1 在完成一组输入请求前,不开始另一组请求处理. 3.2 完成处理包括: 驱动,TCP协议栈,应用处理,入队回复.
  4. 如何实现? 4.1 run_io,系统调用陷入内核,返回所有需要app处理的数据包. 4.2 app将回复报文通过run_io,传入内核.
  5. 这么做的原因? 5.1 单个线程处理一组报文的所有步骤. 5.2 避免了队列,睡眠/唤醒,上下文切换,核间数据传输. 5.3 相较于长队列,有助于活跃的数据包缓存于CPU数据缓冲区 5.4 避免了每个阶段的处理器平衡困扰.

主要优化思想: 轮询代替中断

  1. 问题: 中断消耗较大,而且中断时冗余的,如果通常都需要等待输入.
  2. 什么是轮询? 2.1 阶段性检查NIC的DMA队列,查看是否有新的输入.并且禁止NIC中断.
  3. 实现的困难 3.1 检查时机,在每个循环中设置检查点? 3.2 检查太频繁会浪费CPU 3.3 检查太少,会导致高延迟,队列溢出.
  4. IX的解决方案 4.1 每个CPU核专门运行一个app线程, while(1) { run_io(); ... } 4.2 run_io轮询NIC DMA队列 4.3 没有CPU时间浪费,如果没有输入,则CPU没有任务需要完成 4.4 如果有输入,则获取一组输入,并将之返回给App. 4.5 轮询在低负载时,频率高.高负载时,频率低.效果非常好,论文称之为自适应轮询

主要优化思想: 多核并发

  1. 问题: 单核通常吞吐量不佳,往往会浪费网卡的大部分能力.
  2. 解决机会: 2.1 现实中通常会同时存在大量客户端. 2.2 每个客户端连接基本是独立的. 2.3 所有现代计算机都有多个CPU核.
  3. 风险: 3.1 竞争锁代价巨大. 3.2 核间数据移动代价巨大.
  4. 为了避免数据移动和锁 4.1 每个客户端的所有操作均在一个核上完成,包括TCP协议栈和报文处理. 4.2 所有核间均不需要共享数据.
  5. 可能需要共享的数据示例 5.1 报文内容 5.2 NIC队列 5.3 包空闲列表 5.4 TCP数据结构 5.5 应用数据,尤其是内存DB.

主要优化思想: 网卡多队列

  1. 现代网卡通常支持多个独立的DMA队列,NIC使用过滤器和hash来选择某个队列.
  2. Linux为每个IX应用设置一个独立的NIC DMA队列. 2.1 每个IX应用都对应一个core,一个独立DMA队列. 2.2 Linux为每个IX应用在NIC中设置一个过滤器.
  3. NIC hash每个客户端的IP和port,来选择一个队列. 3.1 flow-consistent hashing或者receive-side scaling 3.2 NIC将同一TCP连接的所有报文分配到同一核上. 3.3 因此,不需要在核间共享TCP连接状态. 3.4 不需要在核间移动数据.
  4. run_io查找NIC DMA队列时,只处理分配给其核自身的报文.
  5. 一个新的连接通过NIC hash分配到某个核上,并希望籍此实现负载均衡.

主要优化思想: 零拷贝

  1. 如何避免IX和IX应用间拷贝TCP数据? 1.1 即不断切换CPL=0/CPL=3,类似kernel用户态和内核态的切换. 1.2 每秒40G的吞吐量,如果有数据需要拷贝,会显著增加内存的压力.
  2. IX使用页表映射报文缓冲,使得IX内核和应用可以同时访问. 2.1 NIC通过DMA访问这些内存 2.2 run_io通过指针传递这些内存地址
  3. App和IX协同工作以获悉缓冲区何时free,并通过run_io通知底层free的buffer.

IX的限制条件

  1. 假设同时存在大量并发的客户端,并向服务器发出大量小请求. 1.1 你或许需要一个单个传输速率达40G/s的连接
  2. 假设各核之间负载平衡良好 2.1 客户端请求被NIC分配到各队列上 2.2 所有请求的处理时间都大致相同. 2.3 是否可以重新分配队列? 2.4 是否可以处理其他队列的任务?
  3. 假设所有请求都是非阻塞的 3.1 服务端代码处理请求时,不需要读取磁盘,发送RPC,然后等待. 3.2 阻塞的请求会导致CPU闲置,并使得等待队列迅速增长. 3.3 是否可以将所有阻塞请求统一交由固定线程/CPU核处理?

评估

  1. 我们的目标是什么?
  2. 高负载条件下的高吞吐量,尤其是请求回复报文都较小的情况
  3. 低负载条件下的低延迟,尤其是吞吐量与CPU核数成正比.

Figure2

  1. 低负载下的延迟情况
  2. 测试场景为: 一个客户端,同一时间,发出一个请求.
  3. x轴为报文大小,y轴为吞吐量(G/s)
  4. 为什么随着报文大小的增长,曲线在上升? 4.1 随着报文大小增加,很多固定的开销被分摊. 4.2 因为大量报文存在,增加了报文成批处理的几率
  5. 什么限制了曲线的增长? 5.1 10G以太网报文的最小头部
  6. 为什么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)

  1. 衡量通过增加核数对于吞吐量的影响.
  2. 理想情况下,吞吐量随着CPU核的数量同步增加,直到达到硬件网卡的限制. 2.1 注意,这并不表示软件的有效性,仅仅是表明了有良好的并发性能.
  3. 测试场景为: 18个客户端,报文长度64字节,每个客户端一个连接.
  4. x轴为CPU核数,y轴每秒处理的报文数.
  5. 在开始阶段,为什么曲线上升? 5.1 工作被分配到更多的并发CPU核上. 5.2 18个客户端,所以最多会有18个核被并发使用
  6. 并发量和CPU数目成正比么? 6.1 在开始时,对于所有曲线而言,是这样的. 6.2 所以锁竞争并没有抵消并发产生的效果.
  7. 注意,IX每秒每核大约可以处理50w个请求. 7.1 每个请求处理时间为2ms或者4000个CPU周期.
  8. 为什么IX可以击败Linux? 8.1 轮询,批处理系统调用,运行直至完成.
  9. 令人印象深刻的是,IX在400w/s请求的情况下,依然性能线性增长. 9.1 这表明并发效果近乎完美. 9.2 RSS有所帮助 9.3 在软件实现上,必须没有锁,没有任何核间数据移动.

IX设计改动

  1. 每个App网络协议栈 1.1 而不是所有App共享一个协议栈 1.2 允许报文缓冲区共享,从而实现零拷贝.
  2. 将App和CPU核,以及线程绑定 2.1 而不是由kernel复用CPU核. 2.2 允许轮询和运行直至完成 2.3 使软件更有效率,更有效. 2.4 支持更多的CPU核并发.
  3. 将App和NIC队列绑定 3.1 而不是共享队列 3.2 更加直接的访问,效率更高 3.3 支持更多的NIC队列

results matching ""

    No results matching ""