《深入浅出DPDK》读书笔记(八):网卡性能优化(异步中断模式、轮询模式、混和中断轮询模式)

本文内容为读书笔记,摘自《深入浅出DPDK》


82.DPDK的轮询模式

DPDK PMD( Poll Mode Driver)轮询模式驱动程序

DPDK采用了轮询或者轮询混杂中断的模式来进行收包和发包,此前主流运行在操作系统内核态的网卡驱动程序基本都是基于异步中断处理模式。

7.1.1 异步中断模式

当有包进入网卡收包队列后,网卡会产生硬件(MSIX/MSI/INTX)中断,进而触发CPU中断,进入中断服务程序,在中断服务程序(包含下半部)来完成收包的处理。当然为了改善包处理性能,也可以在中断处理过程中加入轮询,来避免过多的中断响应次数。总体而言,基于异步中断信号模式的收包,是不断地在做中断处理,上下文切换,每次处理这种开销是固定的,累加带来的负荷显而易见。在CPU比I/O速率高很多时,这个负荷可以被相对忽略,问题不大,但如果连接的是高速网卡且I/O频繁,大量数据进出系统,开销累加就被充分放大。中断是异步方式,因此CPU无需阻塞等待,有效利用率较高,特别是在收包吞吐率比较低或者没有包进入收包队列的时候,CPU可以用于其他任务处理。

当有包需要发送出去的时候,基于异步中断信号的驱动程序会准备好要发送的包,配置好发送队列的各个描述符。在包被真正发送完成时,网卡同样会产生硬件中断信号,进而触发CPU中断,进入中断服务程序,来完成发包后的处理,例如释放缓存等。与收包一样,发送过程也会包含不断地做中断处理,上下文切换,每次中断都带来CPU开销;同上,CPU有效利用率高,特别是在发包吞吐率比较低或者完全没有发包的情况。

7.1.2 轮询模式

DPDK起初的纯轮询模式是指收发包完全不使用任何中断,集中所有运算资源用于报文处理。但这不是意味着DPDK不可以支持任何中断。根据应用场景需要,中断可以被支持,最典型的就是链路层状态发生变化的中断触发与处理。

DPDK纯轮询模式是指收发包完全不使用中断处理的高吞吐率的方式。DPDK所有的收发包有关的中断在物理端口初始化的时候都会关闭,也就是说,CPU这边在任何时候都不会收到收包或者发包成功的中断信号,也不需要任何收发包有关的中断处理。DPDK到底是怎么知道有包进入到网卡,完成收包?到底怎么准备发包,知道哪些包已经成功经由网卡发送出去呢?

前面已经详细介绍了收发包的全部过程,任何包进入到网卡,网卡硬件会进行必要的检查、计算、解析和过滤等,最终包会进入物理端口的某一个队列。前面已经介绍了物理端口上的每一个收包队列,都会有一个对应的由收包描述符组成的软件队列来进行硬件和软件的交互,以达到收包的目的。前面第6章已经详细介绍了描述符。DPDK的轮询驱动程序负责初始化好每一个收包描述符,其中就包含把包缓冲内存块的物理地址填充到收包描述符对应的位置,以及把对应的收包成功标志复位。然后驱动程序修改相应的队列管理寄存器来通知网卡硬件队列里面的哪些位置的描述符是可以有硬件把收到的包填充进来的。网卡硬件会把收到的包一一填充到对应的收包描述符表示的缓冲内存块里面,同时把必要的信息填充到收包描述符里面,其中最重要的就是标记好收包成功标志。当一个收包描述符所代表的缓冲内存块大小不够存放一个完整的包时,这时候就可能需要两个甚至多个收包描述符来处理一个包。

每一个收包队列,DPDK都会有一个对应的软件线程负责轮询里面的收包描述符的收包成功的标志。一旦发现某一个收包描述符的收包成功标志被硬件置位了,就意味着有一个包已经进入到网卡,并且网卡已经存储到描述符对应的缓冲内存块里面,这时候驱动程序会解析相应的收包描述符,提取各种有用的信息,然后填充对应的缓冲内存块头部。然后把收包缓冲内存块存放到收包函数提供的数组里面,同时分配好一个新的缓冲内存块给这个描述符,以便下一次收包。

每一个发包队列,DPDK都会有一个对应的软件线程负责设置需要发送出去的包,DPDK的驱动程序负责提取发包缓冲内存块的有效信息,例如包长、地址、校验和信息、VLAN配置信息等。DPDK的轮询驱动程序根据内存缓存块中的包的内容来负责初始化好每一个发包描述符,驱动程序会把每个包翻译成为一个或者多个发包描述符里能够理解的内容,然后写入发包描述符。其中最关键的有两个,一个就是标识完整的包结束的标志EOP (End Of Packet),另外一个就是请求报告发送状态RS (Report Status)。由于一个包可能存放在一个或者多个内存缓冲块里面,需要一个或者多个发包描述符来表示一个等待发送的包,EOP就是驱动程序用来通知网卡硬件一个完整的包结束的标志。每当驱动程序设置好相应的发包描述符,硬件就可以开始根据发包描述符的内容来发包,那么驱动程序可能会需要知道什么时候发包完成,然后回收占用的发包描述符和内存缓冲块。基于效率和性能上的考虑,驱动程序可能不需要每一个发包描述符都报告发送结果,RS就是用来由驱动程序来告诉网卡硬件什么时候需要报告发送结果的一个标志。不同的硬件会有不同的机制,有的网卡硬件要求每一个包都要报告发送结果,有的网卡硬件要求相隔几个包或者发包描述符再报告发送结果,而且可以由驱动程序来设置具体的位置。

发包的轮询就是轮询发包结束的硬件标志位。DPDK驱动程序根据需要发送的包的信息和内容,设置好相应的发包描述符,包含设置对应的RS标志,然后会在发包线程里不断查询发包是否结束。只有设置了RS标志的发包描述符,网卡硬件才会在发包完成时以写回的形式告诉发包结束。不同的网卡可能会有不同的写回方式,比如基于描述符的写回,比如基于头部的写回,等等。当驱动程序发现写回标志,意味着包已经发送完成,就释放对应的发包描述符和对应的内存缓冲块,这时候就全部完成了包的发送过程。

7.1.3 混和中断轮询模式

由于实际网络应用中可能存在的潮汐效应,在某些时间段网络数据流量可能很低,甚至完全没有需要处理的包,这样就会出现在高速端口下低负荷运行的场景,而完全轮询的方式会让处理器一直全速运行,明显浪费处理能力和不节能。因此在DPDK R2.1和R2.2陆续添加了收包中断与轮询的混合模式的支持,类似NAPI的思路,用户可以根据实际应用场景来选择完全轮询模式,或者混合中断轮询模式。而且,完全由用户来制定中断和轮询的切换策略,比如什么时候开始进入中断休眠等待收包,中断唤醒后轮询多长时间,等等。

图7-1所示为DPDK的例子程序l3fwd-power,使用了DPDK支持的中断加轮询的混合模式。应用程序开始就是轮询收包,这时候收包中断是关闭的。但是当连续多次收到的包的个数为零的时候,应用程序定义了一个简单的策略来决定是否以及什么时候让对应的收包线程进入休眠模式,并且在休眠之前使能收包中断。休眠之后对应的核的运算能力就被释放出来,完全可以用于其他任何运算,或者干脆进入省电模式,取决于内核怎么调度。当后续有任何包收到的时候,会产生一个收包中断,并且最终唤醒对应的应用程序收包线程。线程被唤醒后,就会关闭收包中断,再次轮询收包。当然,应用程序完全可以根据不同的需要来定义不同的策略来让收包线程休眠或者唤醒收包线程。

DPDK的混合中断轮询机制是基于UIO或VFIO来实现其收包中断通知与处理流程的。如果是基于VFIO的实现,该中断机制是可以支持队列级别的,即一个接收队列对应一个中断号,这是因为VFIO支持多MSI-X中断号。但如果是基于UIO的实现,该中断机制就只支持一个中断号,所有的队列共享一个中断号。

图7-1 DPDK中轮询驱动的机制

当然混合中断轮询模式相比完全轮询模式,会在包处理性能和时延方面有一定的牺牲,比如由于需要把DPDK工作线程从睡眠状态唤醒并运行,这样会引起中断触发后的第一个接收报文的时延增加。由于时延的增加,需要适当调整Mbuf队列的大小,以避免当大量报文同时到达时可能发生的丢包现象。在应用场景下如何更高效地利用处理器的计算能力,用户需要根据实际应用场景来做出最合适的选择。


83.网卡I/O性能优化

 在Linux系统上,可以通过“dmidecode memory”查看到处理器内缓存的信息。

如何能有效编写软件来高效利用cache呢?

Burst收发包就是DPDK的优化模式,它把收发包复杂的处理过程进行分解,打散成不同的相对较小的处理阶段,把相邻的数据访问、相似的数据运算集中处理。这样就能尽可能减少对内存或者低一级的处理器缓存的访问次数,用更少的访问次数来完成更多次收发包运算所需要数据的读或者写。(为啥我感觉有点像vpp呢?)

Burst从字面理解,可译为突发模式,是一次完成多个数据包的收发,比如8、16甚至32个数据包一次接收或者发送,由DPDK函数调用者来决定。

Burst收发包是DPDK普遍使用的软件接口,用户可以设定每次收发包函数调用所处理的包的个数。具体函数接口如下。

❑static inline uint16_t rte_eth_rx_burst (uint8_t port_id, uint16_t queue_id, struct rte_mbuf**rx_pkts, const uint16_t nb_pkts);

❑static inline uint16_t rte_eth_tx_burst (uint8_t port_id, uint16_t queue_id, struct rte_mbuf**tx_pkts, uint16_t nb_pkts);

可以看到,收包函数和发包函数的最后一个参数就是指定一次函数调用来处理的包的个数,当设置为1时,其实就是每次收一个包或者发一个包。可以看到DPDK大部分示例程序里面默认的Burst收发包是32个。为什么Burst模式收发包能减少内存访问次数,提高性能呢?图7-3是最简单的收发包的过程,在收到包以后,应用程序还可能会有很多运算,然后才是发包处理。

图7-3 数据收发处理的简化流程

如果每次只收一个包,然后处理,最后再发送出去。那么在收一个包的时候发生内存访问或者低一级的处理器缓存访问的时候,往往会把临近的数据一并同步到处理器缓存中,因为处理器缓存更新都是按固定cache line(例如64字节)加载的。到中间计算的时候为了空出处理器缓存,把前面读取的数据又废弃了,下一次需要用到临近数据的时候又需要重新访问低一级处理器缓存,甚至是直接内存访问。而Burst模式收包则一次处理多个包,在第一次加载数据的时候,临近的数据可能恰好是下一个包需要用到的数据,这时候就可能会发生收两个包或者更多需要加载的数据只需要一次内存访问或者低级别的处理器缓存访问。这样就用更少的内存访问或者低级别的处理器缓存访问完成了更多个包的收包处理,总体上就节省了平均单个包收包所需的时间,提高了性能。

如果每次只发送一个包,在内存访问或者处理器缓存同步的时候,同样是按照cache line大小加载或同步数据,一样会有一些数据同步了却不需要使用,这就造成了内存访问和处理器缓存同步能力的浪费。而Burst模式发包则是一次处理多个包,可以用更少的内存访问或者低级处理器缓存同步为更多的发包处理服务,总体上节约了平均每个包发送所需的时间,提高了性能。


84.为了达到批量处理下乱序时延隐藏的效果,常用的做法是在一个序列中铺开执行多个事务,以一个合理的步进迭代。

图7-5 逐包接收流程图

仔细观察这些主干操作,除了更新尾指针的操作,大都可以进行批量操作。尾指针的更新并非每包进行,一个原因是减少MMIO的访问开销,另一个原因是减少cache line的竞争。

图7-6 批量收包流程图

图7-6进行批处理的改造,考虑到Intel®82599网卡的描述符大小为16 B,而Intel®处理器一个cache line的大小为64B,网卡也倾向于整cache line的写回。另外以4为粒度不至于浪费太多的指令在无效的包操作上。在5个主干操作中,分配新缓存,重填描述符,更新描述符环尾指针是个很大的比重。如果把其从检查DD标志和包描述符处理操作路径中分离,可以缩短处理周期,也有助于做固定粒度的缓存分配和重填操作。这样延迟重填的方式,也能保证CPU写操作迟于DMA写操作整数倍个cache line的大小,从而避免cache line的写写竞争。


85.利用Intel SIMD指令进一步并行化包收发

进行批量处理改造之后,性能确实得到了很大提升,那是否还有改进空间呢。

(这块涉及到指令集,我先不做考量)


86.平台优化及其配置调优

DPDK本身对性能做了很多优化,但实际上要在某个平台上跑出最优的性能也不是一件容易的事情,这其中就涉及硬件软件各个方面的配置和注意事项,这里会系统地介绍各个相关的环节和值得注意的配置。

(这块应该好好看看,不过好像是硬件部署优化的问题)

“Speed:1067 MHz”则显示了当前的内存运行频率。对于双路服务器,访问本地内存与远端内存会带来性能差异。因此,在内存的分配与管理上需要注意,在DPDK的接口函数参数接口中,会看到很多的Socket这样的参数,用0或者1表征Socket 0(Node 0)或者Socket1(Node 1)。在很多技术手册中,也有用socket1和socket2来表征双路系统,这只是起点数字标识位不同,差异可忽视。

PCIe接口是高速以太网卡进出系统的通道,所以PCIe的接口直接决定了数据进出通道的大小。当前主流的PCIe规范可能支持PCIe Gen2或者PCIe Gen3,性能差一倍。PCIe支持不同的数据宽度(例如x4, x8, x16),显而易见,所提供的最大速率是不同的。

在多处理器平台上,不同的PCIe插槽可能连接在不同的处理器上,跨处理器的PCIe设备访问会引入额外的CPU间通信,对性能的影响大。PCIe插槽与具体哪路CPU连接,在主板设计上基本确定,建议软件开发人员查找硬件手册来确定插槽属于哪个CPU节点。图7-10展示了一个典型的双路服务器上的PCIe插槽与处理器的连接关系,软件开发者可以咨询硬件工程师获得类似信息。如果看不到物理插卡的具体位置(较少场合),一个简单的软件方法是查看PCIe地址中总线编号,如果小于80(例如0000:03:00.0),是连接到CPU0上,大于等于80(比如0000:81:00.0),则是连接在CPU1上。软件识别方法并不可靠,且会随着操作系统与驱动程序变化。

图7-10 一个典型双路服务器上的PCIe插槽与连接

PCIe插槽的速率匹配。


87.软件平台对包处理性能的影响

DPDK广泛使用了大页(2M或者1G)机制,以Linux系统为例,1G的大页一般不能在系统加载后动态分配,所以一般会在内核加载的时候设置好需要用到的大页。例如,增加内核启动参数“default_hugepagesz=1G hugepagesz=1G hugepages=8”来配置好8个1G的大页。在Linux系统上,可以通过命令“cat /proc/meminfo”来查看系统加载后的内存状况和大页的分配状况。

DPDK的软件线程一般都需要独占一些处理器的物理核或者逻辑核来完成稳定和高性能的包处理,如果硬件平台的处理器有足够多的核,一般都会预留出一些核来给DPDK应用程序使用。例如,增加内核启动参数“isolcpus=2, 3, 4, 5, 6, 7, 8”,使处理器上ID为2,3,4,5, 6,7,8的逻辑核不被操作系统调度。


88.那么运行参数可以是如下。

./l3fwd  -c  0xff00000  -n  4  -w  82:00.0  -w  85:00.0  --  -p  0x3  -config' (0,0,20), (0,1,21), (0,2,22), (0,3,23), (1,0,24), (1,1,25), (1,2,26), (1,3,27)' 

89.DPDK的示例程序里面默认的发包队列长度使用的是512,这就表示为每一个发包队列都分配512个发包描述符,这是一个适用大部分场合的经验值。


90.收包队列可释放描述符数量阈值(rx_free_thresh)


91. 发包队列发送结果报告阈值(tx_rs_thresh)


92.发包描述符释放阈值(tx_free_thresh)


系列文章

《深入浅出DPDK》读书笔记(一):基础部分知识点

《深入浅出DPDK》读书笔记(二):网卡的读写数据操作

《深入浅出DPDK》读书笔记(三):NUMA - Non Uniform Memory Architecture 非统一内存架构

《深入浅出DPDK》读书笔记(四):并行计算-SIMD是Single-Instruction Multiple-Data(单指令多数据)

《深入浅出DPDK》读书笔记(五):同步互斥机制

《深入浅出DPDK》读书笔记(六):报文转发(run to completion、pipeline、精确匹配算法、最长前缀匹配LPM)

《深入浅出DPDK》读书笔记(七):PCIe与包处理I/O


相关阅读

DPDK PMD( Poll Mode Driver)轮询模式驱动程序

 

已标记关键词 清除标记
相关推荐
©️2020 CSDN 皮肤主题: 酷酷鲨 设计师:CSDN官方博客 返回首页