网络协议栈性能影响因素及优化技术

影响网络协议栈的性能开销及优化方法如下。

mark

1 网卡中断开销

轮询中断是操作系统与外设进行 I/O 通信的两种主要方式。一般情况下,网络中数据分组的到来具有不可预测性。若采用轮询模式对数据分组进行持续监听,会造成很高的CPU占用率,进而影响系统其他进程的运行效率,故主流操作系统都采用中断来处理网络的请求。中断处理方式在低速网络I/O场景下非常有效高速网络中,随着网络 I/O速率的不断上升,网卡面对大量高速数据分组引发频繁的中断,中断引起的上下文切换开销将变得不可忽视,造成较高的时延,并引起吞吐量下降。

1.1 轮询代替中断

轮询不存在中断所固有的开销。以网卡接收分组为例,在轮询模式下,系统会在初始化时屏蔽收发分组中断, 并使用一个线程或进程不断检测收取分组描述符里的收取分组成功标志是否被网卡置位,以此来判断是否有数据分组,整个收取过程没有发生上下文切换,因此也就避免了相应的开销。在高速网络I/O下,轮询带来的性能提升是非常显著的。

轮询不一定总是优于中断。只有当I/O速率接近CPU速率时,中断的开销变得不可忽略,轮询模式的优势才能体现;相反,如果数据吞吐率很低,中断能有更好的CPU利用率,此时不宜采用中断模式。 基于以上分析,针对网络流量抖动较大的场景, 可以选用中断与轮询的混合模式,即:在流量小时使用中断模式,当遇到大流量时切换为轮询模式。

目前Linux内核(NAPI)与DPDK都支持这种混合中断轮询模式。NAPI(Linux New API):可以动态地决定中断还是轮询。中断只有在一个batch的第一个数据包时是enable,然后polling定期enabled,用来检查收到数据包的设备。数据包参数存到DMA-able memory,等待CPU available。当DMA-able memory里面没有数据包时中断重新使能。这样使得,IO低时候用中断;IO高用轮询。

mark

2 内存复制开销

提供服务的应用进程运行在用户空间,而通常情况下,操作系统首先将到来的数据分组读取到内核空间,为了使上层应用能够对接收到的数据进行相应处理,需要将内核将数据从内核空间复制到用户空间;而对于应用进程产生的数据, 也需要由用户空间复制到内核空间,最后由网卡发出。操作系统在处理数据过程中往往需要 进行多次复制操作,严重影响了高速网络服务的性能。

2.1 零拷贝技术(Zero-copy)

零拷贝指的是除网卡将数据DMA复制进内存外(非CPU参与), 从数据分组接收到应用程序处理数据分组,整个过程中不存在数据复制。高速网络下,网络IO性能甚至超过CPU处理能力,频繁数据拷贝会耗尽CPU计算资源,使CPU成为性能瓶颈。同时,数据从内核空间进入应用程序所处的用户空间,该过程中引发的上下文切换开销同样不可忽略。

DPDK、Netmap、PF-ring等高性能数据分组处理框架都用了零拷贝技术。

PF-ring:将内核中的数据包缓存区暴露给用户态程序,数据包缓存区预分配。PF_Ring DNA (Direct NIC Access):利用特殊的driver,提供NIC内存到用户空间内存的映射。缺点:通过DMA可能错误使用内存地址,导致系统崩溃。

3 锁开销

当多个线程或进程需要对某一共享资源进行操作时,往往需要通过锁机制来保证数据的一致性和同步性,而加锁带来的开销会显著降低数据处理的性能。具体来说,锁机制造成的开销主要有两方面:一方面,线程在为共享资源上锁或者去锁的过程中通常需要耗时几十纳秒;另一方面,在竞争锁的过程中,等待线程在阻塞过程中无法进行有效的数据处理和计算,从而降低了整个系统的并发性能。无论是宿主机还是客户机的内核协议栈中,都存在大量的共享资源,这也制约着整个系统的可扩展性。

3.1 连接本地化

​ 将共享的数据结构分区 & 连接本地化以减少锁开销/跨核开销。即设法将同一个流的所有数据分组交由同一个CPU核处理,从而避免不同核对于同一资源的竞争, 进而也就避免了加锁的开销。同时, 在同一个CPU核处理同一条流也可以提升缓存的命中率,避免跨核的开销。

4 上下文切换开销

数据平面进行资源的分配调度过程中涉及多种类型的上下文切换:在网卡中断系统调用进程调度跨核资源访问 等上下文切换过程中,操作系统均需要保存当前状态,上下文切换开销大,严重影响系统性能。

4.1 用户态协议栈

直接将协议栈移动到应用层实现,避免昂贵的上下文切换开销, 如mTCP、 lwIP等方案

4.2 批处理系统调用

批处理系统调用平摊开销,MegaPipe、FlexSC、 VOS均使用批量系统调用来减小开销。

5 文件系统管理开销

socket是一种文件抽象,Linux 为了实现统一文件管理,通过虚拟文件系统(virtual file system,VFS)为套接字绑定了一系列对应的数据结构如inode、dentry等。通常情况下, 这些重量级的数据结构对于套接字本身的功能来说是不必要的[Fastsocket]。而在NFV的应用场景下(或者高并发的短连接),往往需要对 socket进行频繁的分配和释放,因而操作系统在管理这些数据结构的过程中会引起较大的性能开销。

5.1 自定义轻量级socket

自定义轻量级的 socket,一些优化方案如mTCP、lwIP等,选择直接绕过VFS,在用户态重新定义实现socket结构体;另外一些优化 方案如MegaPipe等,虽然在内核实现,但也通过自定义API避免了原有VFS的文件操作

5.2 继承VFS的socket实现但简化

继承VFS的socket实现,但是简化掉inode与 dentry 的初始化与销毁过程,抛弃其中的锁。这是因为对于socket而言,inode与dentry是完全无用的。代表性工作是Fastsocket。 相比前一种方案,该方案的优点在于能够完全兼容传统 socket,便于应用抑制 继承VFS的socket实现,但是简化掉inode与 dentry 的初始化与销毁过程,抛弃其中的锁。这是因为对于socket而言,inode与dentry是完全无用的。代表性工作是Fastsocket。 相比前一种方案,该方案的优点在于能够完全兼容传统 socket,便于应用抑制

6 缓存未命中开销

如果在存在频繁的跨核调用, 由此带来的缓存未命中会造成严重的数据读写时延,从而降低系统整体如果在存在频繁的跨核调用, 由此带来的缓存未命中会造成严重的数据读写时延,从而降低系统整体性如果在存在频繁的跨核调用, 由此带来的缓存未命中会造成严重的数据读写时延,从而降低系统整体如果在存在频繁的跨核调用, 由此带来的缓存未命中会造成严重的数据读写时延,从而降低系统整体性如果在存在频繁的跨核调用, 由此带来的缓存未命中会造成严重的数据读写时延,从而降低系统整体如果在存在频繁的跨核调用, 由此带来的缓存未命中会造成严重的数据读写时延,从而降低系统整体性如果在存在频繁的跨核调用, 由此带来的缓存未命中会造成严重的数据读写时延,从而降低系统整体如果在存在频繁的跨核调用, 由此带来的缓存未命中会造成严重的数据读写时延,从而降低系统整体性