第2章 Cache和内存
2.1 存储系统简介
本章会讨论Cache和内存
2.21 系统架构的演进
北桥也称为主桥(Host Bridge),主要用来处理高速信号,通常负 责与处理器的联系。南桥也称为IO桥(IO bridge),负责I/O总线之间的通信,比如PCI总线、SATA、USB等,可 以连接光驱、硬盘、键盘灯设备交换数据。在这种系统中,所有的数据 交换都需要通过北桥:
1)处理器访问内存需要通过北桥。
2)处理器访问所有的外设都需要通过北桥。
3)处理器之间的数据交换也需要通过北桥。
4)挂在南桥的所有设备访问内存也需要通过北桥
为了改善对内存的访问瓶颈,出现了另外一种系统设计,内存控制 器并没有被集成在北桥中,而是被单独隔离出来以协调北桥与某个相应 的内存之间的交互,如图2-2所示。这样的话,北桥可以和多个内存相 连。这种架构增加了内存的访问带宽,缓解了不同设备对 同一内存访问的拥塞问题,但是却没有改进单一北桥芯片的瓶颈的问 题。
的NUMA(Non-Uniform Memory Architecture,非一致性内存架构)系统。
2.1.2 内存子系统
为了了解内存子系统,首先需要解释一下和内存相关的常用用语。
1)RAM(Random Access Memory):随机访问存储器
2)SRAM(Static RAM):静态随机访问存储器
3)DRAM(Dynamic RAM):动态随机访问存储器。
4)SDRAM(Synchronous DRAM):同步动态随机访问存储器。
5)DDR(Double Data Rate SDRAM):双数据速率SDRAM。
6)DDR2:第二代DDR。
7)DDR3:第三代DDR。
8)DDR4:第四代DDR。
2.2 Cache系统简介
Cache的概念,其目的就是为 了匹配处理器和内存之间存在的巨大的速度鸿沟。
2.2.1 Cache的种类
一级Cache,一般分为数据Cache和指令Cache,数据Cache用来存储 数据,而指令Cache用于存放指令。这种Cache速度最快,一般处理器只 需要3~5个指令周期就能访问到数据,因此成本高,容量小,一般都只 有几十KB。在多核处理器内部,每个处理器核心都拥有仅属于自己的 一级Cache。
二级Cache,和一级Cache分为数据Cache和指令Cache不同,数据和 指令都无差别地存放在一起。速度相比一级Cache慢一些,处理器大约 需要十几个处理器周期才能访问到数据,容量也相对来说大一些,一般 有几百KB到几MB不等。在多核处理器内部,每个处理器核心都拥有仅 属于自己的二级Cache
三级Cache,速度更慢,处理器需要几十个处理器周期才能访问到 数据,容量更大,一般都有几MB到几十个MB。在多核处理器内部,三 级Cache由所有的核心所共有。这样的共享方式,其实也带来一个问 题,有的处理器可能会极大地占用三级Cache,导致其他处理器只能占 用极小的容量,从而导致Cache不命中,性能下降。因此,英特尔公司 推出了Intel® CAT技术,确保有一个公平,或者说软件可配置的算法来 控制每个核心可以用到的Cache大小。
2.2.2 TLB Cache
直接访问物理地址不安全,虚拟地址和分 段分页技术被提出来用来保护脆弱的软件系统。。软件使用虚拟地址访问 内存,而处理器负责虚拟地址到物理地址的映射工作。为了完成映射工 作,处理器采用多级页表来进行多次查找最终找到真正的物理地址
TLB(Translation Look-aside Buffer)Cache应运而生,专门 用于缓存内存中的页表项。。TLB一般都采用相连存储器或者按内容访问 存储器(CAM,Content Addressable Memory)。
2.3 Cache地址映射和变换
如何把 内存中的内容存放到Cache中去呢?这就需要一个映射算法和一个分块 机制。
分块机制就是说,Cache和内存以块为单位进行数据交换,块的大 小通常以在内存的一个存储周期中能够访问到的数据长度为限。当今主 流块的大小都是64字节,因此一个Cache line就是指64个字节大小的数 据块。
假设要找0000001110这个地址,根据空间局部性,之后很有可能用到旁边得地址,所以把块号为00000011这个块存入cache中
跟据Cache和内存之间的映射关系的不同,Cache可以分为三类:第 一类是全关联型Cache(full associative cache),第二类是直接关联型 Cache(direct mapped cache),第三类是组关联型Cache(N-ways associative cache)。
2.3.1 全关联型Cache
https://www.bilibili.com/video/BV1h3411h7kV?spm_id_from=333.337.search-card.all.click
全关联型Cache,块的冲突最小(没有冲突), Cache的利用率也高,但是需要一个访问速度很快的相联存储器。随着 Cache容量的增加,其电路设计变得十分复杂,因此只有容量很小的 Cache才会设计成全关联型的(如一些英特尔处理器中的TLB Cache)。
2.3.2 直接关联型Cache
直接关联是一种很“死”的映射方法,当映射到同一个 Cache块的多个内存块同时需要缓存在Cache中时,只有一个内存块能够 缓存,其他块需要被“淘汰”掉。因此,直接关联型命中率是最低的,但 是其实现方式最为简单,匹配速度也最快。
2.3.3 组关联型Cache
直接关联型Cache和全关联型Cache只是组关联型Cache的 特殊情况,当组内Cache Line数目为1时,即为直接关联型Cache。而当 组内Cache Line数目和Cache大小相等时,即整个Cache只有一个组,这 成为全关联型Cache。或每一个块一个组,就变成直接关联型Cache
2.4 Cache的写策略
“写”操作必须在确认是命中后才可进行
Ø写直达法(也称为存直达法)
执行“写”操作时,不仅写入Cache,而且也写入下一级存储器。
Ø写回法(也称为拷回法)
执行“写”操作时,只写入Cache。仅当Cache中相应的块被替换时,才写回主存。(设置“修改位”)
Ø按写分配(写时取)
写不命中时,先把所写单元所在的块调入Cache
再行写入。
Ø不按写分配(绕写法)
写不命中时,直接写入下一级存储器而不调块。
7.写策略与调块
Ø写回法 ── 按写分配
Ø写直达法 ── 不按写分配
2.5 Cache预取
2.5.1 Cache的预取原理
Cache之所以能够提高系统性能,主要是程序执行存在局部性现 象,即时间局部性和空间局部性。
1)时间局部性:是指程序即将用到的指令/数据可能就是目前正在 使用的指令/数据。因此,当前用到的指令/数据在使用完毕之后可以暂 时存放在Cache中,可以在将来的时候再被处理器用到。一个简单的例 子就是一个循环语句的指令,当循环终止的条件满足之前,处理器需要 反复执行循环语句中的指令。
2)空间局部性:是指程序即将用到的指令/数据可能与目前正在使 用的指令/数据在空间上相邻或者相近。因此,在处理器处理当前指令/ 数据时,可以从内存中把相邻区域的指令/数据读取到Cache中,这样, 当处理器需要处理相邻内存区域的指令/数据时,可以直接从Cache中读 取,节省访问内存的时间。一个简单的例子就是一个需要顺序处理的数 组。
2.5.2 NetBurst架构处理器上的预取
1.一级数据Cache的预取单元
1)数据Cache预取单元:也叫基于流的预取单元(Streaming prefetcher)。当程序以地址递增的方式访问数据时,该单元会被激活, 自动预取下一个Cache行的数据。
2)基于指令寄存器(Instruction Pointer,IP)的预取单元:该单元 会监测指令寄存器的读取(Load)指令,当该单元发现读取数据块的大 小总是相对固定的情况下,会自动预取下一块数据。假设当前读取地址 是0xA000,读取数据块大小为256个字节,那地址是0xA100-0xA200的 数据就会自动被预取到一级数据Cache中。该预取单元能够追踪的最大 数据块大小是2K字节。
当程序需要多次访问某种大的数据结构,并且访问的顺序是有规律 的,硬件单元能够捕捉到这种规律,进而能够提前预取需要处理的数 据,那么就能提高程序的执行效率;当访问的顺序没有规律,或者硬件 不能捕捉这种规律,这种预取不但会降低程序的性能,而且会占用更多 的带宽,浪费一级Cache有限的空间;甚至在某些极端情况下,程序本 身就占用了很多一级数据Cache的空间,而预取单元为了预取它认为程 序需要的数据,不适当地淘汰了程序本身存放在一级Cache的数据,从 而导致程序的性能严重下降。
2.硬件预取所遵循的原则
1)只有连续两次Cache不命中才能激活预取机制。并且,这两次不 命中的内存地址的位置偏差不能超过256或者512字节(NetBurst架构的 不同处理器定义的阈值不一样),否则也不会激活预取。这样做的目的 是因为预取也会有开销,会占用内部总线的带宽,当程序执行没有规律 时,盲目预取只会带来更多的开销,并且并不一定能够提高程序执行的 效率。
2)一个4K字节的页(Page)内,只定义一条流(Stream,可以是 指令,也可以是数据)。因为处理器同时能够追踪的流是有限的。 3)能够同时、独立地追踪8条流。每条流必须在一个4K字节的页 内。
4)对4K字节的边界之外不进行预取。也就是说,预取只会在一个 物理页(4K字节)内发生。这和一级数据Cache预取遵循相同的原则。 5)预取的数据存放在二级或者三级Cache中。
6)对于UC(Strong Uncacheable)和WC(Write Combining)内存 类型不进行预取。
2.5.3 两个执行效率迥异的程序
|
|
程序1是按照 数组在内存中的保存方式顺序访问,而程序2则是跳跃式访问。对于程 序1,硬件预取单元能够自动预取接下来需要访问的数据到Cache,节省 访问内存的时间,从而提高程序1的执行效率;对于程序2,硬件不能够 识别数据访问的规律,因而不会预取,从而使程序2总是需要在内存中 读取数据,降低了执行的效率.
2.5.4 软件预取
硬件预取单元并不一定能够提高程序执行 的效率,有些时候可能会极大地降低执行的效率。因此,一些体系架构 的处理器增加了一些指令,使得软件开发者和编译器能够部分控制 Cache。能够影响Cache的指令很多,本书仅介绍预取相关的指令。
·DPDK中的预取
在讨论之前,我们需要了解另外一个和性能相关的话题。DPDK一 个处理器核每秒钟大概能够处理33M个报文,大概每30纳秒需要处理一 个报文,假设处理器的主频是2.7GHz,那么大概每80个处理器时钟周期 就需要处理一个报文。那么,处理报文需要做一些什么事情呢?以下是 一个基本过程。
可以看出,处理一个报文的过程,需要6次读取内存(见上“内存 读”)。而之前我们讨论过,处理器从一级Cache读取数据需要3~5个时 钟周期,二级是十几个时钟周期,三级是几十个时钟周期,而内存则需 要几百个时钟周期。从性能数据来说,每80个时钟周期就要处理一个报 文。 因此,DPDK必须保证所有需要读取的数据都在Cache中,否则一 旦出现Cache不命中,性能将会严重下降。为了保证这点,DPDK采用 了多种技术来进行优化,预取只是其中的一种。 而从上面的介绍可以看出,控制结构体和数据缓冲区的读取都没有 遵循硬件预取的原则,因此DPDK必须用一些预取指令来提前加载相应 数据。
2.6 Cache一致性
1)该数据结构或者数据缓冲区的起始地址是Cache Line对齐的吗? 如果不是,即使该数据区域的大小小于Cache Line,那么也需要占用两 个Cache entry;并且,假设第一个Cache Line前半部属于另外一个数据 结构并且另外一个处理器核正在处理它,那么当两个核都修改了该 Cache Line从而写回各自的一级Cache,准备送到内存时,如何同步数 据?毕竟每个核都只修改了该Cache Line的一部分。
2)假设该数据结构或者数据缓冲区的起始地址是Cache Line对齐 的,但是有多个核同时对该段内存进行读写,当同时对内存进行写回操 作时,如何解决冲突?
2.6.1 Cache Line对齐
定义该数据结构或者数据缓冲区时就申明对齐
2.6.2 Cache一致性问题的由来
对于读,首先是从 内存加载到Cache,最后送到处理器内部的寄存器;对于写,则是从寄 存器送到Cache,最后通过内部总线写回到内存。即多个处理器对某个内存块同时读写,会 引起冲突的问题,这也被称为Cache一致性问题。
假设只是单核处理器,那么只有一个处理器会对内存进行读 写,Cache也是只有一份,因而不会出现一致性的问题。 2)假设是多核处理器系统,但是Cache是所有处理器共享的,那么 当一个处理器对内存进行修改并且缓存在Cache中时,其他处理器都能 看到这个变化,因而也不会产生一致性的问题。 3)假设是多核处理器系统,每个核心也有独占的Cache,但是 Cache只会采用直写,那么当一个处理器对内存进行修改之后,Cache会 马上将数据写入到内存中,也不会有问题吗?考虑之前我们介绍的一个 例子,线程A把结果写回到内存中,但是线程B只会从独占的Cache中读 取这个变量(因为没人通知它内存的数据产生了变化),因此在这种条 件下还是会有Cache一致性的问题。
Cache一致性问题的根源是因为存在多个处理器独占的 Cache,而不是多个处理器。
处理器共享Cache,那么就不会有任何问题。但是,这 种解决办法的问题就是太慢了。首先,既然是共享的Cache,势必容量 不能小,那么就是说访问速度相比之前提到的一级、二级Cache,速度 肯定几倍或者十倍以上;其次,每个处理器每个时钟周期内只有一个处 理器才能访问Cache,那么处理器把时间都花在排队上了,这样效率太 低了。
2.6.3 一致性协议
解决Cache一致性问题的机制有两种:基于目录的协议(Directorybased protocol)和总线窥探协议(Bus snooping protocol)。
基于目录协议的系统中,需要缓存在Cache的内存块被统一存储在 一个目录表中,目录表统一管理所有的数据,协调一致性问题。该目录 表类似于一个仲裁者,当处理器需要把一个数据从内存中加载到自己独 占的Cache中时,需要向目录表提出申请;当一个内存块被某个处理器 改变之后,目录表负责改变其状态,更新其他处理器的Cache中的备 份,或者使其他处理器的Cache的备份无效。
总线窥探协议是在1983年被首先提出来,这个协议提出了一个窥探 (snooping)的动作,即对于被处理器独占的Cache中的缓存的内容,该 处理器负责监听总线,如果该内容被本处理器改变,则需要通过总线广 播;反之,如果该内容状态被其他处理器改变,本处理器的Cache从总 线收到了通知,则需要相应改变本地备份的状态。
基于目录的协议的延迟性较大,但是在拥有很多个处理器的系统 中,它有更好的可扩展性。而总线窥探协议适用于具有广播能力的总线 结构,允许每个处理器能够监听其他处理器对内存的访问,适合小规模 的多核系统
2.6.4 MESI协议
https://www.bilibili.com/video/BV1fK4y1E7NC?spm_id_from=333.337
总结:
当CPU写数据时(M),如果发现操作的变量是共享变量(S),会发出信号通知其他CPU将该变量的缓存行置为无效状态(I),因此其他CPU需要获取这个变量时,发现自己缓存中缓存改变量的缓存行时无效的那么他会从内存中重新获取,确保一致性
读取数据时,其他CPU缓存行如果发生修改,要先把修改的数据写入内存
当修改数据时,其他CPU的该缓存行状态必须全部失效
2.6.5 DPDK如何保证Cache一致性
数据结构定义。DPDK的应用程序很多情况下都需要多个 核同时来处理事务,因而,对于某些数据结构,我们给每个核都单独定 义一份,这样每个核都只访问属于自己核的备份。以上的数据结构“struct lcore_conf”总是以Cache行对齐,这样就不会 出现该数据结构横跨两个Cache行的问题。而定义的数 组“lcore[RTE_MAX_LCORE]”中RTE_MAX_LCORE指一个系统中最大 核的数量。DPDK中对每个核都进行编号,这样核n就只需要访问 lcore[n],核m只需要访问lcore[m],这样就避免了多个核访问同一个结 构体。
对网络端口的访问。在网络平台中,少不了访问网络设 备,比如网卡。多核情况下,有可能多个核访问同一个网卡的接收队 列/发送队列,也就是在内存中的一段内存结构。这样,也会引起Cache 一致性的问题。那么DPDK是如何解决这个问题的呢?网卡设备一般都具有多队列的能力,也就是说,一 个网卡有多个接收队列和多个访问队列,其他章节会很详细讲到,本节 不再赘述。 DPDK中,如果有多个核可能需要同时访问同一个网卡,那么 DPDK就会为每个核都准备一个单独的接收队列/发送队列。这样,就避 免了竞争,也避免了Cache一致性问题。
2.7 TLB和大页
2.7.1 逻辑地址到物理地址的转换
2.7.2 TLB
2.7.3 使用大页
从上面的逻辑地址到物理地址的转换我们知道,如果采用常规页 (4KB)并且使TLB总能命中,那么至少需要在TLB表中存放两个表 项,在这种情况下,只要寻址的内容都在该内容页内,那么两个表项就 足够了。如果一个程序使用了512个内容页也就是2MB大小,那么需要 512个页表表项才能保证不会出现TLB不命中的情况。通过上面的介 绍,我们知道TLB大小是很有限的,随着程序的变大或者程序使用内存 的增加,那么势必会增加TLB的使用项,最后导致TLB出现不命中的情 况。那么,在这种情况下,大页的优势就显现出来了。如果采用2MB作 为分页的基本单位,那么只需要一个表项就可以保证不出现TLB不命中 的情况;对于消耗内存以GB(2 30)为单位的大型程序,可以采用1GB 为单位作为分页的基本单位,减少TLB不命中的情况。
2.7.4 如何激活大页
首先,Linux操作系统采用了基于hugetlbfs的特殊文件系统来加入对 2MB或者1GB的大页面支持。这种采用特殊文件系统形式支持大页面的 方式,使得应用程序可以根据需要灵活地选择虚存页面大小,而不会被 强制使用2MB大页面。 为了使用大页,必须在编译内核的时候激活hugetlbfs。 在激活hugetlbfs之后,还必须在Linux启动之后保留一定数量的内存 作为大页来使用。现在有两种方式来预留内存。 第一种是在Linux命令行指定,这样Linux启动之后内存就已经预 留;第二种方式是在Linux启动之后,可以动态地预留内存作为大页使 用。以下是2MB大页命令行的参数。
2.8 DDIO
2.8.1 时代背景
Intel® DDIO(Data Direct I/O)的技术。该技术的主要 目的就是让服务器能更快处理网络接口的数据,提高系统整体的吞吐 率,降低延迟,同时减少能源的消耗。
当一个网络报文送到服务器的网卡时,网卡通过外部总线(比如 PCI总线)把数据和报文描述符送到内存。接着,CPU从内存读取数据 到Cache进而到寄存器。进行处理之后,再写回到Cache,并最终送到内 存中。最后,网卡读取内存数据,经过外部总线送到网卡内部,最终通 过网络接口发送出去。
可以看出,对于一个数据报文,CPU和网卡需要多次访问内存。而 内存相对CPU来讲是一个非常慢速的部件。CPU需要等待数百个周期才 能拿到数据,在这过程中,CPU什么也做不了。
DDIO技术是如何改进的呢?这种技术使外部网卡和CPU通过LLC Cache直接交换数据,绕过了内存这个相对慢速的部件。这样,就增加 了CPU处理网络报文的速度(减少了CPU和网卡等待内存的时间),减 小了网络报文在服务器端的处理延迟。这样做也带来了一个问题,因为 网络报文直接存储在LLC Cache中,这大大增加了对其容量的需求,因 而在英特尔的E5处理器系列产品中,把LLC Cache的容量提高到了 20MB。
2.8.2 网卡的读数据操作
通常来说,为了发送一个数据报文到网络上去,首先是运行在CPU 上的软件分配了一段内存,然后把这段内存读取到CPU内部,更新数 据,并且填充相应的报文描述符(网卡会通过读取描述符了解报文的相 应信息),然后写回到内存中,通知网卡,最终网卡把数据读回到内 部,并且发送到网络上去。
由于DDIO技术的引入,网卡的读操作减少了访问 内存的次数,因而提高了访问效率,减少了报文转发的延迟。在理想状 况下,NIC和处理器无需访问内存,直接通过访问Cache就可以完成更 新数据,把数据送到NIC内部,进而送到网络上的所有操作。
2.8.3 网卡的写数据操作
NIC从网络上收到报文后,通过PCI总线把报 文和相应的控制结构体送到预先分配的内存,然后通知相应的驱动程序 或者软件来处理。
DDIO技术在处理器和外设之间交换数据时,减少 了处理器和外设访问内存的次数,也减少了Cache写回的等待,提高了 系统的吞吐率和数据的交换延迟。
2.9 NUMA系统
https://zhuanlan.zhihu.com/p/272201846
NUMA(Non Uniform Memory Access),非一致性内存访问。
NUMA系统是从SMP(Symmetric Multiple Processing,对称多处理器)系统演化而来。
1)所有的硬件资源都是共享的。即每个处理器都能访问到任何内 存、外设等。
2)所有的处理器都是平等的,没有主从关系。
3)内存是统一结构、统一寻址的(UMA,Uniform Memory Architecture)。
4)处理器和内存,处理器和处理器都通过一条总线连接起来。
其结构如图2-14所示: SMP的问题也很明显,因为所有的处理器都通过一条总线连接起 来,因此随着处理器的增加,系统总线成为了系统瓶颈,另外,处理器 和内存之间的通信延迟也较大。为了克服以上的缺点,才应运而生了 NUMA架构,
1)Per-core memory。一个处理器上有多个核(core),per-core memory是指每个核都有属于自己的内存,即对于经常访问的数据结 构,每个核都有自己的备份。这样做一方面是为了本地内存的需要,另 外一方面也是因为上文提到的Cache一致性的需要,避免多个核访问同 一个Cache行。 2)本地设备本地处理。即用本地的处理器、本地的内存来处理本 地的设备上产生的数据。如果有一个PCI设备在node0上,就用node0上 的核来处理该设备,处理该设备用到的数据结构和数据缓冲区都从 node0上分配。以下是一个分配本地内存的例子
第3章 并行计算
处理器性能提升主要有两个途径,一个是提高IPC(每个时钟周期 内可以执行的指令条数),另一个是提高处理器主频率。每一代微架构 的调整可以伴随着对IPC的提高,从而提高处理器性能,只是幅度有 限。而提高处理器主频率对于性能的提升作用是明显而直接的。但一味 地提高频率很快会触及频率墙,因为处理器的功耗正比于主频的三次 方。 所以,最终要取得性能提升的进一步突破,还是要回到提高IPC这 个因素。经过处理器厂商的不懈努力,我们发现可以通过提高指令执行 的并行度来提高IPC。而提高并行度主要有两种方法,一种是提高微架 构的指令并行度,另一种是采用多核并发。这一章主要就分享这两种方 法在DPDK中的实践,并在指令并行方法中上进一步引入数据并发的介 绍
3.1 多核性能和可扩展性
3.1.1 追求性能水平扩展
Amdahl 定律(也叫阿姆达尔定律)的主要思想是:当我们对系统的某个部分加速时,其对系统整体性能的影响取决于该部分的重要性和加速程度。
Amdahl定律告诉我 们,假设一个任务的工作量不变,多核并行计算理论时延加速上限取决 于那些不能并行处理部分的比例。换句话说,多核并行计算下时延不能 随着核数增加而趋于无限小。该定律明确告诉我们,利用多核处理器提 升固定工作量性能的关键在于降低那些不得不串行部分占整个任务执行 的比例。
对于DPDK的主要应用领域——数据包处理,多数场景并不是完成 一个固定工作量的任务,更主要关注单位时间内的吞吐量。Gustafson定 律对于在固定工作时间下的推导给予我们更多的指导意义。它指出,多 核并行计算的吞吐率随核数增加而线性扩展,可并行处理部分占整个任 务比重越高,则增长的斜率越大。
3.1.2 多核处理器
CPU寄存器集合、中断逻 辑(Local APIC)、执行单元和Cache。一个完整的物理核需要拥有这 样的整套资源,提供一个指令执行线程。
多处理器结构指的是多颗单独封装的CPU通过外部总线连接,构成 的统一计算平台
超线程(Hyper-Threading)在一个处理器中提供两 个逻辑执行线程,逻辑线程共享流水线、执行单元和缓存。该技术的本 质是复用单处理器中的超标量流水线的多路执行单元,降低多路执行单 元中因指令依赖造成的执行单元闲置。
3.1.3 亲和性
CPU亲和性(Core affinity)就是一个特定的任务要在某 个给定的CPU上尽量长时间地运行而不被迁移到其他处理器上的倾向 性。这意味着线程可以不在处理器之间频繁迁移。这种状态正是我们所 希望的,因为线程迁移的频率小就意味着产生的负载小。
1.Linux内核对亲和性的支持
2.为什么应该使用亲和性
将线程与CPU绑定,最直观的好处就是提高了CPU Cache的命中 率,从而减少内存访问损耗,提高程序的速度。
3.线程独占
DPDK通过把线程绑定到逻辑核的方法来避免跨核任务中的切换开 销,但对于绑定运行的当前逻辑核,仍然可能会有线程切换的发生,若 希望进一步减少其他任务对于某个特定任务的影响,在亲和的基础上更 进一步,可以采取把逻辑核从内核调度系统剥离的方法。 Linux内核提供了启动参数isolcpus。对于有4个CPU的服务器,在启 动的时候加入启动参数isolcpus=2,3。那么系统启动后将不使用CPU3 和CPU4。注意,这里说的不使用不是绝对地不使用,系统启动后仍然 可以通过taskset命令指定哪些程序在这些核心中运行。步骤如下所示。
3.1.4 DPDK的多线程
DPDK的线程基于pthread接口创建,属于抢占式线程模型,受内核 调度支配。DPDK通过在多核设备上创建多个线程,每个线程绑定到单 独的核上,减少线程调度的开销,以提高性能。 DPDK的线程可以作为控制线程,也可以作为数据线程。在DPDK 的一些示例中,控制线程一般绑定到MASTER核上,接受用户配置,并 传递配置参数给数据线程等;数据线程分布在不同核上处理数据包。
lcore可以亲和到一个CPU或者一个CPU集 合,使得在运行时调整具体某个CPU承载lcore成为可能。 而另一个方面,多个lcore也可能亲和到同一个核。这里要注意的 是,同一个核上多个可抢占式的任务调度涉及非抢占式的库时,会有一 定限制。这里以非抢占式无锁rte_ring为例: 1)单生产者/单消费者模式,不受影响,可正常使用。 2)多生产者/多消费者模式且pthread调度策略都是SCHED_OTHER 时,可以使用,性能会有所影响。 3)多生产者/多消费者模式且pthread调度策略有SCHED_FIFO或者 SCHED_RR时,建议不使用,会产生死锁。
3.2 指令并发与数据并行
3.2.1 指令并发
现代多核处理器几乎都采用了超标量的体系结构来提高指令的并发 度,并进一步地允许对无依赖关系的指令乱序执行。这种用空间换时间 的方法,极大提高了IPC,使得一个时钟周期完成多条指令成为可能。
Scheduler下挂了8个Port,这表示每个core每个时钟周期最多可 以派发8条微指令操作。具体到指令的类型,比如Fast LEA,它可以同 时在Port 1和Port 5上派发。换句话说,该指令具有被多发的能力。可以 简单地理解为,该指令先后操作两个没有依赖关系的数据时,两条指令 有可能被处理器同时派发到执行单元执行,由此该指令实际执行的吞吐 率就提升了一倍
3.2.2 单指令多数据
https://zhuanlan.zhihu.com/p/55327037
SIMD是Single-Instruction Multiple-Data(单指令多数据)的缩写, 从字面的意思就能理解大致的含义。多数据指以特定宽度为一个数据单 元,多单元数据独立操作。而单指令指对于这样的多单元数据集,一个 指令操作作用到每个数据单元。可以把SIMD理解为向量化的操作方 式。典型SIMD操作如图3-7所示,两组各4个数据单元(X1,X2,X3, X4和Y1,Y2,Y3,Y4)并行操作,相同操作作用在相应的数据单元对 上(X1和Y1,X2和Y2,X3和Y3,X4和Y4),4对计算结果组成最后 的4数据单元数
实战DPDK
DPDK中的memcpy就利用到了SSE/AVX的特点。比较典型的就是 rte_memcpy内存拷贝函数。内存拷贝是一个非常简单的操作,算法上并 无难度,关键在于很好地利用处理器的各种并行特性。当前Intel的处理 器(例如Haswell、Sandy Bridge等)一个指令周期内可以执行两条Load 指令和一条Store指令,并且支持SIMD指令(SSE/AVX)来在一条指令 中处理多个数据,其Cache的带宽也对SIMD指令进行了很好的支持。因 此,在rte_memcpy中,我们使用了平台所支持的最大宽度的Load和Store 指令(Sandy Bridge为128bit,Haswell为256bit)。此外,由于非对齐的 存取操作往往需要花费更多的时钟周期,rte_memcpy优先保证Store指令 存储的地址对齐,利用处理器每个时钟周期可以执行两条Load这个超标 量特性来弥补一部分非对齐Load所带来的性能损失
3.3 小结
多核采用这种“横向扩展”的方法来提高系统的性能,该架构实现 了“分治法”策略。通过划分任务,线程应用能够充分利用多个执行内 核,并且可以在特定时间内执行更多任务。它的优点是能够充分并且灵 活地分配CPU,使它们的利用率最大化。但是,增加了上下文切换以及 缓存命中率的开销。总之,由于多个核的存在,多核同步问题也是一个 重要部分,由于很难严格做到每个核都不相关,因此引入无锁结构,这 将在以后做更进一步介绍
第4章 同步互斥机制
DPDK根据多核处理器的特点,遵循资源局部化的原则,解耦数据 的跨核共享,使得性能可以有很好的水平扩展。但当面对实际应用场 景,CPU核间的数据通信、数据同步、临界区保护等都是不得不面对的 问题。如何减少由这些基础组件引入的多核依赖的副作用,也是DPDK 的一个重要的努力方向
4.1 原子操作
4.1.1 处理器上的原子操作
在单处理器系统(UniProcessor)中,能够在单条指令中完成的操 作都可以认为是“原子操作”,因为中断只能发生于指令之间。这也是某 些CPU指令系统中引入了test_and_set、test_and_clear等指令用于临界资 源互斥的原因。
在多核CPU的时代,体系中运行着多个独立的CPU,即使是可以在 单个指令中完成的操作也可能会被干扰。典型的例子就是decl指令(递 减指令),它细分为三个过程:“读->改->写”,涉及两次内存操作。如 果多个CPU运行的多个进程或线程在同时对同一块内存执行这个指令, 那情况是无法预测的。
在x86平台上,总的来说,CPU提供三种独立的原子锁机制:原子 保证操作、加LOCK指令前缀和缓存一致性协议。
4.1.2 Linux内核原子操作
软件级的原子操作实现依赖于硬件原子操作的支持。对于Linux而 言,内核提供了两组原子操作接口:一组是针对整数进行操作;另一组 是针对单独的位进行操作。
1.原子整数操作 针对整数的原子操作只能处理atomic_t类型的数据。这里没有使用C 语言的int类型,主要是因为:
1)让原子函数只接受atomic_t类型操作数,可以确保原子操作只与 这种特殊类型数据一起使用。
2)使用atomic_t类型确保编译器不对相应的值进行访问优化。
3)使用atomic_t类型可以屏蔽不同体系结构上的数据类型的差异。 尽管Linux支持的所有机器上的整型数据都是32位,但是使用atomic_t的 代码只能将该类型的数据当作24位来使用。这个限制完全是因为在 SPARC体系结构上,原子操作的实现不同于其他体系结构:32位int类 型的低8位嵌入了一个锁,因为SPARC体系结构对原子操作缺乏指令级 的支持,所以只能利用该锁来避免对原子类型数据的并发访问。 原子整数操作最常见的用途就是实现计数器。原子操作通常是内敛 函数,往往通过内嵌汇编指令来实现。如果某个函数本来就是原子的, 那么它往往会被定义成一个宏。
2.原子性与顺序性 原子性确保指令执行期间不被打断,要么全部执行,要么根本不执 行。而顺序性确保即使两条或多条指令出现在独立的执行线程中,甚至 独立的处理器上,它们本该执行的顺序依然要保持。
3.原子位操作 原子位操作定义在文件中。令人感到奇怪的是,位操作函数是对普 通的内存地址进行操作的。原子位操作在多数情况下是对一个字长的内 存访问,因而位编号在0~31之间(在64位机器上是0~63之间),但是对 位号的范围没有限制。
4.1.3 DPDK原子操作实现和应用
https://zhuanlan.zhihu.com/p/125737864
在理解原子操作在DPDK的实现之前,建议读者仔细阅读并且能够 理解第2章的内容,那部分是我们理解内存操作的基础,因为原子操作 的最终反映也是对内存资源的操作。 原子操作在DPDK代码中的定义都在rte_atomic.h文件中,主要包含 两部分:内存屏蔽和原16、32和64位的原子操作API。
4.2 读写锁
4.2.1 Linux读写锁主要API
4.2.2 DPDK读写锁实现和应用
读写锁在DPDK中主要应用在下面几个地方,对操作的对象进行保 护。
·在查找空闲的memory segment的时候,使用读写锁来保护memseg 结构。LPM表创建、查找和释放。
·Memory ring的创建、查找和释放。
·ACL表的创建、查找和释放。
·Memzone的创建、查找和释放等。
4.3 自旋锁
何谓自旋锁(spin lock)?它是为实现保护共享资源而提出一种锁 机制。其实,自旋锁与互斥锁比较类似,它们都是为了解决对某项资源 的互斥使用。无论是互斥锁,还是自旋锁,在任何时刻,最多只能有一 个保持者,也就说,在任何时刻最多只能有一个执行单元获得锁。但是 两者在调度机制上略有不同。对于互斥锁,如果资源已经被占用,资源 申请者只能进入睡眠状态。但是自旋锁不会引起调用者睡眠,如果自旋 锁已经被别的执行单元保持,调用者就一直循环在那里看是否该自旋锁 的保持者已经释放了锁,“自旋”一词就是因此而得名。
4.3.1 自旋锁的缺点
自旋锁必须基于CPU的数据总线锁定,它通过读取一个内存单元 (spinlock_t)来判断这个自旋锁是否已经被别的CPU锁住。如果否,它 写进一个特定值,表示锁定了总线,然后返回。如果是,它会重复以上 操作直到成功,或者spin次数超过一个设定值。记住上面提及到的:锁 定数据总线的指令只能保证一个指令操作期间CPU独占数据总线。(自 旋锁在锁定的时侯,不会睡眠而是会持续地尝试)。其作用是为了解决 某项资源的互斥使用。因为自旋锁不会引起调用者睡眠,所以自旋锁的 效率远高于互斥锁。虽然自旋锁的效率比互斥锁高,但是它也有些不足 之处:
1)自旋锁一直占用CPU,它在未获得锁的情况下,一直运行—— 自旋,所以占用着CPU,如果不能在很短的时间内获得锁,这无疑会使 CPU效率降低。
2)在用自旋锁时有可能造成死锁,当递归调用时有可能造成死 锁,调用有些其他函数(如copy_to_user()、copy_from_user()、 kmalloc()等)也可能造成死锁。
因此我们要慎重使用自旋锁,自旋锁只有在内核可抢占式或SMP的 情况下才真正需要,在单CPU且不可抢占式的内核下,自旋锁的操作为 空操作。自旋锁适用于锁使用者保持锁时间比较短的情况。
4.3.2 Linux自旋锁API
自旋锁使用时有两点需要注意:
1)自旋锁是不可递归的,递归地请求同一个自旋锁会造成死锁。
2)线程获取自旋锁之前,要禁止当前处理器上的中断。(防止获 取锁的线程和中断形成竞争条件)
比如:当前线程获取自旋锁后,在临界区中被中断处理程序打断, 中断处理程序正好也要获取这个锁,于是中断处理程序会等待当前线程 释放锁,而当前线程也在等待中断执行完后再执行临界区和释放锁的代 码。
4.3.3 DPDK自旋锁实现和应用
DPDK中自旋锁API的定义在rte_spinlock.h文件中,其中下面三个 API被广泛的应用在告警、日志、中断机制、内存共享和link bonding的 代码中,用于临界资源的保护。
4.4 无锁机制
当前,高性能的服务器软件(例如,HTTP加速器)在大部分情况 下是运行在多核服务器上的,当前的硬件可以提供32、64或者更多的 CPU,在这种高并发的环境下,锁竞争机制有时会比数据拷贝、上下文 切换等更伤害系统的性能。因此,在多核环境下,需要把重要的数据结 构从锁的保护下移到无锁环境,以提高软件性能。 所以,现在无锁机制变得越来越流行,在特定的场合使用不同的无 锁队列,可以节省锁开销,提高程序效率。Linux内核中有无锁队列的 实现,可谓简洁而不简单。
4.4.1 Linux内核无锁环形缓冲
环形缓冲区通常有一个读指针和一个写指针。读指针指向环形缓冲 区中可读的数据,写指针指向环形缓冲区中可写的数据。通过移动读指 针和写指针就可以实现缓冲区的数据读取和写入。在通常情况下,环形 缓冲区的读用户仅仅会影响读指针,而写用户仅仅会影响写指针。如果 仅仅有一个读用户和一个写用户,那么不需要添加互斥保护机制就可以 保证数据的正确性。但是,如果有多个读写用户访问环形缓冲区,那么 必须添加互斥保护机制来确保多个用户互斥访问环形缓冲区。具体来 讲,如果有多个写用户和一个读用户,那么只是需要给写用户加锁进行 保护;反之,如果有一个写用户和多个读用户,那么只是需要对读用户 进行加锁保护。
在Linux内核代码中,kfifo就是采用无锁环形缓冲的实现,kfifo是 一种“First In First Out”数据结构,它采用了前面提到的环形缓冲区来实 现,提供一个无边界的字节流服务。采用环形缓冲区的好处是,当一个 数据元素被用掉后,其余数据元素不需要移动其存储位置,从而减少拷 贝,提高效率。更重要的是,kfifo采用了并行无锁技术,kfifo实现的单 生产/单消费模式的共享队列是不需要加锁同步的。
4.4.2 DPDK无锁环形缓冲
基于无锁环形缓冲的的原理,Intel DPDK提供了一套无锁环形缓冲 区队列管理代码,支持单生产者产品入列,单消费者产品出列;多名生 产者产品入列,多名消费者出列操作
4.4.2.1 rte_ring的数据结构定义
下面是DPDK中的rte_ring的数据结构定义,可以清楚地理解rte_ring 的设计基础。
4.4.2.2 环形缓冲区的剖析
4.4.2.3 单生产者入队
4.4.2.4 单消费者出队
4.4.2.5 多生产者入队
4.5 小结
原子操作适用于对单个bit位或者单个整型数的操作,不适用于对临 界资源进行长时间的保护。
自旋锁主要用来防止多处理器中并发访问临界区,防止内核抢占造 成的竞争。另外,自旋锁不允许任务睡眠(持有自旋锁的任务睡眠会造 成自死锁——因为睡眠有可能造成持有锁的内核任务被重新调度,而再 次申请自己已持有的锁),它能够在中断上下文中使用。
读写锁实际是一种特殊的自旋锁,适用于对共享资源的访问者划分 成读者和写者,读者只对共享资源进行读访问,写者则需要对共享资源 进行写操作。写者是排他性的,一个读写锁同时只能有一个写者或多个 读者(与CPU数相关),但不能同时既有读者又有写者。
无锁队列中单生产者——单消费者模型中不需要加锁,定长的可以 通过读指针和写指针进行控制队列操作,变长的通过读指针、写指针、 结束指针控制操作。
(一)多对多(一)模型中正常逻辑操作是要对队列操作进行加锁 处理。加锁的性能开销较大,一般采用无锁实现,DPDK中就是采用的 无锁实现,加锁的性能开销较大,DPDK中采用的无锁数据结构实现, 非常高效。
每种同步互斥机制都有其适用场景,我们在使用的时候应该扬长避 短,最大限度地发挥它们的优势,这样才能编写高性能的代码。另外, 在DPDK代码中,这些机制都在用户空间中实现,便于移植,所以又可 以为编写其他用户空间的代码提供参考和便利
第5章 报文转发
对于一个报文的整个生命周期如何从一个对接运营商的外部接口进 入一个路由器,再通过一个连接计算机的内部接口发送出去的过程,大 家应该是充满好奇和疑问的,整个报文处理的流程就如同计算机的中央 处理器对于指令的处理具有重复性、多样性、复杂性和高效性。只有弄 清其中每个环节才能帮助我们更有效地提高网络报文的处理能力
5.1 网络处理模块划分
网络报文的处理和转发主要分为硬件处理部分与软件处理部分,由 以下模块构成:
·Packet input:报文输入。
·Pre-processing:对报文进行比较粗粒度的处理。
·Input classification:对报文进行较细粒度的分流。
·Ingress queuing:提供基于描述符的队列FIFO。
·Delivery/Scheduling:根据队列优先级和CPU状态进行调度。
·Accelerator:提供加解密和压缩/解压缩等硬件功能。
·Egress queueing:在出口上根据QOS等级进行调度。
·Post processing:后期报文处理释放缓存。
·Packet output:从硬件上发送出去。
如图5-1所示,我们可以看到在浅色和阴影对应的模块都是和硬件 相关的,因此要提升这部分性能的最佳选择就是尽量多地去选择网卡上 或网络设备芯片上所提供的一些和网络特定功能相关的卸载的特性,而 在深色软件部分可以通过提高算法的效率和结合CPU相关的并行指令来 提升网络性能。了解了网络处理模块的基本组成部分后,我们再来看不 同的转发框架下如何让这些模块协同工作完成网络包处理。
5.2 转发框架介绍
传统的Network Processor(专用网络处理器)转发的模型可以分为 run to completion(运行至终结,简称RTC)模型和pipeline(流水线) 模型。
1.pipeline模型 从名字上,就可以看出pipeline模型借鉴于工业上的流水线模型, 将一个功能(大于模块级的功能)分解成多个独立的阶段,不同阶段间 通过队列传递产品。这样,对于一些CPU密集和I/O密集的应用,通过 pipeline模型,我们可以把CPU密集的操作放在一个微处理引擎上执行, 将I/O密集的操作放在另外一个微处理引擎上执行。通过过滤器可以为 不同的操作分配不同的线程,通过连接两者的队列匹配两者的处理速 度,从而达到最好的并发效率。
2.run to completion模型 run to completion(运行至终结)模型是主要针对DPDK一般程序的 运行方法,一个程序中一般会分为几个不同的逻辑功能,但是这几个逻 辑功能会在一个CPU的核上运行,我们可以进行水平扩展使得在SMP的 系统中多个核上执行一样逻辑的程序,从而提高单位时间内事务处理的 量。但是由于每个核上的处理能力其实都是一样的,并没有针对某个逻 辑功能进行优化,因此在这个层面上与pipeline模型比较,run to completion模型是不高效的。
5.3 转发算法
除了良好的转发框架之外,转发中很重要的一部分内容就是对于报 文字段的匹配和识别,在DPDK中主要用到了精确匹配(Exact Match) 算法和最长前缀匹配(Longest Prefix Matching,LPM)算法来进行报文 的匹配从而获得相应的信息。
5.3.1 精确匹配算法
精确匹配算法的主要思想就是利用哈希算法对所要匹配的值进行哈 希,从而加快查找速度。决定哈希性能的主要参数是负载参数
介绍了哈希相关的一些基础后,我们来看下DPDK的具体实现。 DPDK中主要支持CRC32和J hash,这里主要介绍CRC相关的内容和优 化。 其实,精确匹配主要需要解决两个问题:进行数据的签名(哈 希),解决哈希的冲突问题。CRC32和J hash是两个数字签名的不同算 法。我们先来看下CRC。
5.3.2 最长前缀匹配算法
最长前缀匹配(Longest Prefix Matching,LPM)算法是指在IP协议 中被路由器用于在路由表中进行选择的一个算法。 因为路由表中的每个表项都指定了一个网络,所以一个目的地址可 能与多个表项匹配。最明确的一个表项——即子网掩码最长的一个—— 就叫做最长前缀匹配。之所以这样称呼它,是因为这个表项也是路由表 中与目的地址的高位匹配得最多的表项。
5.3.3 ACL算法
ACL主要思路就是创建了Tier相关的数据结构,匹配字段中每个字 段中的每个字节都会作为Tier中的一层进行匹配,每一层都作为到达最 终匹配结果的一个路径。
5.3.4 报文分发
Packet distributor(报文分发)是DPDK提供给用户的一个用于包分 发的API库,用于进行包分发。
一般是通过一个distributor分发到不同的 worker上进行报文处理,当报文处理完后再通过worker返回给 distributor
5.4 小结
本章着重讲述了DPDK的数据报文转发模型以及常用的基本转发算 法,包括两种主要使用的模式run to completion和pipeline,然后详细介 绍了三种转发算法和一个常用的DPDK报文分发库。通过本章的内容, 读者可以了解基本的网络包处理流程和DPDK的工作模式
第6章 PCIe与包处理I/O
- PCI总线取得了很大的成功,但随着CPU的主频不断提高,PCI总线的带宽也捉襟见肘。此外,它本身存在一些架构上的缺陷,面临一系列挑战,包括带宽、流量控制、数据传送质量等;
- PCIe应运而生,能有效解决这些问题,所以PCIe才是我们的主角;
前面各章主要讨论CPU上数据包处理的各种相关优化技术。从本章 开始,我们的视线逐步从CPU转移到网卡I/O。这一章将会从CPU与I/O 的总线PCIe开始,带领读者领略CPU与网卡DMA协同工作的整个交互 过程,量化分析PCIe数据包传输的理论带宽。以此为基础,进一步剖析 性能优化的思考过程,分享实践的心得体会
6.1 从PCIe事务的角度看包处理
6.1.1 PCIe概览
https://www.cnblogs.com/LoyenWang/p/14165852.html
PCIe(PCI Express)
是目前PC和嵌入式系统中最常用的高速总线,PCIe在PCI的基础上发展而来,在软件上PCIe与PCI是后向兼容的,PCI的系统软件可以用在PCIe系统中。
外设组件互联标准
PCI Express(Peripheral Component Interconnect Express)又称 PCIe,它是一种高速串行通信互联标准。格式说明由外设组件互联特别 兴趣小组PCI-SIG(PCI Special Interest Group)维护,以取代传统总线 通信架构,如PCI、PCI-X以及AGP。
理解包在PCIe上如何传输,首先需要了解PCIe是一种怎样的数据传 输协议规范。
PCIe规范遵循开放系统互联参考模型(OSI),自上而下分为事务 传输层、数据链路层、物理层,如图6-1a所示。对于特定的网卡(如图 6-1b所示),PCIe一般作为处理器外部接口,把物理层朝PCIe根组件 (Root Complex)方向的流量叫做上游流量(upstream或者inbound), 反之叫做下游流量(downstream或者outbound)。
6.1.2 PCIe事务传输
如果在PCIe的线路上抓取一个TLP(Transaction Layer Packet,事务 传输层数据包),其格式就如图6-2所示,它是一种分组形式,层层嵌 套,事务传输层也拥有头部、数据和校验部分。应用层的数据内容就承 载在数据部分,而头部定义了一组事务类型。表6-1列出了所有支持的 TLP包类型。对于CPU从网卡收发包来说,用到的PCIe的事务类型主要 以Memory Read/Write(MRd/MWr)和Completion with Data(CpID)为 主
6.1.3 PCIe带宽
6.2 PCIe上的数据传输能力
可是除了TLP的协议开销以外,有时还会有实现开销的存在。比如 有些网卡可能会要求每个TLP都要从Lane0开始,甚至要求从偶数的时 钟周期开始。由于存在这样的实现因素影响,有效带宽还会进一步降 低。
6.3 网卡DMA描述符环形队列
DMA(Direct Memory Access,直接存储器访问)是一种高速的数 据传输方式,允许在外部设备和存储器之间直接读写数据。数据既不通 过CPU,也不需要CPU干预。整个数据传输操作在DMA控制器的控制 下进行。除了在数据传输开始和结束时做一点处理外,在传输过程中 CPU可以进行其他的工作。
网卡DMA控制器通过环形队列与CPU交互。环形队列由一组控制 寄存器和一块物理上连续的缓存构成。主要的控制寄存器有Base、 Size、Head和Tail。通过设置Base寄存器,可以将分配的一段物理连续 的内存地址作为环形队列的起始地址,通告给DMA控制器。同样通过 Size寄存器,可以通告该内存块的大小。Head寄存器往往对软件只读, 它表示硬件当前访问的描述符单元。而Tail寄存器则由软件来填写更 新,通知DMA控制器当前已准备好被硬件访问的描述符单元。
6.4 数据包收发——CPU和I/O的协奏
DMA控制器通过一组描述符环行队列与CPU互操作完成包的收 发。环形队列的内容部分位于主存中,控制部分通过访问外设寄存器的 方式完成。
从CPU的角度来看,主要的操作分为系统内存(可能是处理器的缓 存)的直接访问和对外部寄存器MMIO的操作。对于MMIO的操作需经 过PCIe总线的传输。由于外部寄存器访问的数据宽度有限(例如,32bit 的Tail寄存器),其PCIe事务有效传输率很低。另外由于PCIe总线访问 的高时延特性,在数据包收发中应该尽量减少操作来提高效率。本节后 续部分会继续讨论MMIO操作的优化。对于前者CPU直接访存部分,这 会在7.2节更系统地介绍,从减少CPU开销的角度来讨论更有效访存的方 法。
从PCIe设备上DMA控制器的角度来看,其操作有访问系统内存和 PCIe设备上的片上内存(in-chip memory)。这里不讨论片上内存。所 以从DMA控制器来讲,我们主要关注其通过PCIe事务传输的访问系统 内存操作。绝大多数收发包的PCIe带宽都被这类操作消耗。所以很有必 要去了解一下都有哪些操作,我们也会在本节进行介绍,并分析如何优 化这类操作。
6.5 PCIe的净荷转发带宽
6.6 Mbuf与Mempool
6.7 小结
本章带领读者探访了I/O和CPU之间关于数据包处理的各项技术及 优化细节。下一章就将进入到网卡内部,去探究网卡性能调试的方法
第7章 网卡性能优化
前面介绍了PCIe这一层级的细节,接下来就从DPDK在软件设计、 硬件平台选择和配置以及软件平台的设置等方面深入分析和介绍怎样完 成网卡性能优化,并且跑出最优的性能
7.1 DPDK的轮询模式
DPDK采用了轮询或者轮询混杂中断的模式来进行收包和发包,此 前主流运行在操作系统内核态的网卡驱动程序基本都是基于异步中断处 理模式。
7.1.1 异步中断模式
当有包进入网卡收包队列后,网卡会产生硬件 (MSIX/MSI/INTX)中断,进而触发CPU中断,进入中断服务程序,在 中断服务程序(包含下半部)来完成收包的处理。当然为了改善包处理 性能,也可以在中断处理过程中加入轮询,来避免过多的中断响应次 数。总体而言,基于异步中断信号模式的收包,是不断地在做中断处 理,上下文切换,每次处理这种开销是固定的,累加带来的负荷显而易 见。在CPU比I/O速率高很多时,这个负荷可以被相对忽略,问题不 大,但如果连接的是高速网卡且I/O频繁,大量数据进出系统,开销累 加就被充分放大。中断是异步方式,因此CPU无需阻塞等待,有效利用 率较高,特别是在收包吞吐率比较低或者没有包进入收包队列的时候, CPU可以用于其他任务处理。
当有包需要发送出去的时候,基于异步中断信号的驱动程序会准备 好要发送的包,配置好发送队列的各个描述符。在包被真正发送完成 时,网卡同样会产生硬件中断信号,进而触发CPU中断,进入中断服务 程序,来完成发包后的处理,例如释放缓存等。与收包一样,发送过程 也会包含不断地做中断处理,上下文切换,每次中断都带来CPU开销; 同上,CPU有效利用率高,特别是在发包吞吐率比较低或者完全没有发 包的情况。
7.1.2 轮询模式
7.1.3 混和中断轮询模式
DPDK的混合中断轮询机制是基于UIO或VFIO来实现其收包中断通 知与处理流程的。如果是基于VFIO的实现,该中断机制是可以支持队 列级别的,即一个接收队列对应一个中断号,这是因为VFIO支持多 MSI-X中断号。但如果是基于UIO的实现,该中断机制就只支持一个中 断号,所有的队列共享一个中断号
在应用场景下如何更高效地利用处理器的计算 能力,用户需要根据实际应用场景来做出最合适的选择
7.2 网卡I/O性能优化
7.2.1 Burst收发包的优点
Burst收发包就是DPDK的优化模式,它把收发包复杂的处理过程进 行分解,打散成不同的相对较小的处理阶段,把相邻的数据访问、相似 的数据运算集中处理。这样就能尽可能减少对内存或者低一级的处理器 缓存的访问次数,用更少的访问次数来完成更多次收发包运算所需要数 据的读或者写。
7.2.2 批处理和时延隐藏
1)时延(Latency):处理器核心执行单元完成一条指令 (instruction)所需要的时钟周期数。
2)吞吐(Throughput):处理器指令发射端口再次允许接受相同 指令所需等待的时钟周期数。
时延描述了前后两个关联操作的等待时间,吞吐则描述了指令的并 发能力。在时延相对固定的情况下,要提升指令执行的整体性能,利用 有些指令的多发能力就显得很重要。
7.3 平台优化及其配置调优
7.3.1 硬件平台对包处理性能的影响
7.3.2 软件平台对包处理性能的影响
7.4 队列长度及各种阈值的设置
7.4.1 收包队列长度
收包队列的长度就是每个收包队列分配的收包描述符个数,每个收 包描述符都会分配有对应的Mbuf缓存块。收包队列的长度就表示了在 软件驱动程序读取所收到的包之前最大的缓存包的能力,长度越长,则 可以缓存更多的包,长度越短,则缓存更少的包。
7.4.2 发包队列长度
7.4.3 收包队列可释放描述符数量阈值(rx_free_thresh)
每一次收包函数的调用都可能成功 读取0、1或者多个包。每读出一个包,与之对应的收包描述符就是可以 释放的了,可以配置好用来后续收包过程。由收发包过程知道,需要更 新表示收包队列尾部索引的寄存器来通知硬件。实际上,DPDK驱动程 序并没有每次收包都更新收包队列尾部索引寄存器,而是在可释放的收 包描述符数量达到一个阈值(rx_free_thresh)的时候才真正更新收包队 列尾部索引寄存器。这个可释放收包描述符数量阈值在驱动程序里面的 默认值一般都是32,
7.4.4 发包队列发送结果报告阈值(tx_rs_thresh)
7.4.5 发包描述符释放阈值(tx_free_thresh)
当网卡硬件读取完发包描述符,并且DMA完成整个包的内容的传 送后,硬件就会根据发送结果回写标记来通知软件发包过程全部完成。 这时候,这些发包描述符就可以释放或者再次利用了,与之对应的 Mbuf也可以释放了。
7.5 小结
网卡性能的优化还涉及怎么更好地利用网卡硬件本身的功能特点。 系统优化需要很好地利用软件和硬件平台的各种可以优化细节共同地达 到优化的目的。本章从网卡、处理器、内存、PCIe接口等硬件系统,以 及BIOS、操作系统和DPDK软件编写角度总结了影响系统性能的元素, 讨论了怎样配置和使用来展示出网卡的最优性能。后续章节会继续介绍 高速网卡在并行化处理以及智能化、硬件卸载方面的一些新功能
第8章 流分类与多队列
多队列与流分类是当今网卡通用的技术。利用多队列及流分类技术 可以使得网卡更好地与多核处理器、多任务系统配合,从而达到更高效 IO处理的目的。 接下来的章节将以Intel的网卡为例,主要介绍其多队列和流分类是 如何工作的,各种分类方式适用于哪些场景,DPDK又是如何利用网卡 这些特性。
8.1 多队列
8.1.1 网卡多队列的由来
说起网卡多队列,顾名思义,也就是传统网卡的DMA队列有多 个,网卡有基于多个DMA队列的分配机制。
网卡多队列技术是一个硬件手段,需要结合软件将它很好地利用起 来从而达到设计的需求。利用该技术,可以做到分而治之,比如每个应 用一个队列,应用就可以根据自己的需求来对数据包进行控制。比如视 频数据强调实时性,而对数据的准确性要求不高,这样我们可以为其队 列设置更高的发送优先级,或者说使用更高优先级的队列,为了达到较 好的实时性,我们可以减小队列对应的带宽。而对那些要求准确性但是 不要求实时的数据(比如电子邮件的数据包队列),我们可以使用较低 的优先级和更大的带宽。
8.1.2 Linux内核对多队列的支持
8.1.3 DPDK与多队列
那么对于DPDK而言,其多队列是如何支持的呢。如果我们来观察 DPDK提供的一系列以太网设备的API,可以发现其Packet I/O机制具有 与生俱来的多队列支持功能,可以根据不同的平台或者需求,选择需要 使用的队列数目,并可以很方便地使用队列,指定队列发送或接收报 文。
·将网卡的某个接收队列分配给某个核,从该队列中收到的所有报 文都应当在该指定的核上处理结束。
·从核对应的本地存储中分配内存池,接收报文和对应的报文描述 符都位于该内存池。
·为每个核分配一个单独的发送队列,发送报文和对应的报文描述 符都位于该核和发送队列对应的本地内存池中。
8.1.4 队列分配
8.2 流分类
本章要讲述的流分类,指的是网卡依据数据包的特性将其分类的技 术。分类的信息可以以不同的方式呈现给数据包的处理者,比如将分类 信息记录于描述符中,将数据包丢弃或者将流导入某个或者某些队列 中。
8.2.1 包的类型
高级的网卡设备可以分析出包的类型,包的类型会携带在接收描述 符中,应用程序可以根据描述符快速地确定包是哪种类型的包,避免了 大量的解析包的软件开销。
网卡设备同时可以根据包的类型确定其关键字,从而根据关键字确 定其收包队列。上面章节提及的RSS及下面提到的Flow Director技术都 是依据包的类型匹配相应的关键字,从而决定其DMA的收包队列。
8.2.2 RSS
这里要介绍一种网卡上用 于将流量分散到不同的队列中的技术:RSS(Receive-Side Scaling,接 收方扩展),它是和硬件相关联的,
简单的说,RSS就是根据关键字通过哈希函数计算出哈希值,再由 哈希值确定队列。关键字是如何确定的呢?网卡会根据不同的数据包类 型选取出不同的关键字,见表8-1。比如IPV4UDP包的关键字就由四元 组组成(源IP地址、目的IP地址、源端口号、目的端口号),IPv4包的 关键字则是源IP地址和目的IP地址。更为灵活的是,使用者甚至可以修 改包类型对应的关键字以满足不同的需求。
RSS是否能将数据包均匀地散列在多个 队列中,取决于真实环境中的数据包构成和哈希函数的选取
8.2.3 Flow Director
Flow Director技术是Intel公司提出的根据包的字段精确匹配,将其 分配到某个特定队列的技术。
相比RSS的负载分担功能,它更加强调特定性。
比如,用户可以为某几个特定的TCP对话(S-IP+D-IP+S-Port+DPort)预留某个队列,那么处理这些TCP对话的应用就可以只关心这个 特定的队列,从而省去了CPU过滤数据包的开销,并且可以提高cache 的命中率。
8.2.4 服务质量
多队列应用于服务质量(QoS)流量类别:把发送队列分配给不同 的流量类别,可以让网卡在发送侧做调度;把收包队列分配给不同的流 量类别,可以做到基于流的限速。根据流中优先级或业务类型字段,可 以将流不同的业务类型有着不同的调度优先级及为其分配相应的带宽, 一般网卡依照VLAN标签的UP(User Priority,用户优先级)字段。网 卡依据UP字段,将流划分到某个业务类型(TC,Traffic Class),网卡 设备根据TC对业务做相应的处理,比如确定相对应的队列,根据优先 级调度等。
以Intel® 82599网卡为例,其使用DCB模型在网卡上实现QoS的功 能。DCB(Data Center Bridge)是包含了差分服务的一组功能,
1.发包方向
2.收包方向
8.2.5 虚拟化流分类方式
前面的章节介绍了RSS、Flow Director、QoS几种按照不同的规则分 配或指定队列的方式。另外,较常用的还有在虚拟化场景下多多队列方 式。
8.2.6 流过滤
流的合法性验证的主要任务是决定哪些数据包是合法的、可被接收 的。合法性检查主要包括对外部来的流和内部流的验证。
1)MAC地址的过滤(L2Filter)。 2)VLAN标签的过滤。 3)管理数据包的过滤
8.3 流分类技术的使用
当下流行的多队列网卡往往支持丰富的流分类技术,我们可以很好 地利用这些特定的分类机制,跟软件更好结合以满足多种多样的需求。
8.3.1 DPDK结合网卡Flow Director功能
一个设备需要一定的转发功能来处理数据平面的报文,同时需要处 理一定量的控制报文。对于转发功能而言,要求较高的吞吐量,需要多 个core来支持;对于控制报文的处理,其报文量并不大,但需要保证其 可靠性,并且其处理逻辑也不同于转发逻辑。那么,我们就可以使用 RSS来负载均衡其转发报文到多个核上,使用Flow Director将控制报文 分配到指定的队列上,使用单独的核来处理。
1)这样可以帮助用户在设计时做到分而治之。
2)节省了软件过滤数据报文的开销。
3)避免了应用在不同核处理之间的切换。
8.3.2 DPDK结合网卡虚拟化及Cloud Filter功能
8.4 可重构匹配表
可重构匹配表(Reconfigurable Match Table,RMT)是软件自定义 网络(Software Defined Networking,SDN)中提出的用于配置转发平面 的通用配置模型,
8.5 小结
当前行业高速网卡芯片提供商数量已经很少,网卡之间的功能差异 不大,但细节可能不同,使用具体功能之前,需要参见网卡手册。作为 相对成熟的技术,网卡功能大同小异,负载均衡与流分类是网络最基本 的功能,利用好网卡硬件特性,可以提高系统性能。充分理解网卡特 性,利用好网卡,需要一定的技术积累。
总体上,高速网卡智能化处理发展是个趋势。数据中心期望大量部 署基于网络虚拟化的应用,需要对进出服务器系统的数据报文实施安全 过滤、差异化服务的策略。在实现复杂网络功能又不占用过多运算资 源,智能化网卡被期待来实现这些功能服务
硬件加速和功能卸载
VLAN硬件卸载
IEEE1588硬件卸载功能
IP TCP/UDP/SCTP checksum硬件卸载功能
checksum计算是网络协议的容错性设计的一部分,基于网络传输不 可靠的假设,因此在Ethernet、IPv4、UDP、TCP、SCTP各个协议层设 计中都有checksum字段,用于校验包的正确性,checksum不涉及复杂的 逻辑。虽然各个协议定义主体不同,checksum算法参差不齐,但总体归 纳,checksum依然可以说是简单机械的计算,算法稳定,适合固化到硬 件中。需要注意的是,checksum可以硬件卸载,但依然需要软件的协同 配合实现。
分片功能卸载 TSO
TSO(TCP Segment Offload)是TCP分片功能的硬件卸载,显然这 是发送方向的功能。如我们所知,TCP会协商决定发送的TCP分片的大 小。对于从应用层获取的较大的数据,TCP需要根据下层网络的报文大 小限制,将其切分成较小的分片发送。
组包功能卸载 RSC
RSC(Receive Side Coalescing,接收方聚合)是TCP组包功能的硬 件卸载。硬件组包功能实际上是硬件拆包功能的逆向功能。 硬件组包功能针对TCP实现,是接收方向的功能,可以将拆分的 TCP分片聚合成一个大的分片,从而减轻软件的处理。 当硬件接收到TCP分片后,如图9-6和图9-7所示,硬件可以将多个 TCP分片缓存起来,并且将其排序,这样,硬件可以将一串TCP分片的 内容聚合起来。这样多个TCP分片最终传递给软件时将会呈现为一个分 片,这样带给软件的好处是明显的,软件将不再需要分析处理多个数据 包的头,同时对TCP包的排序的负担也有所减轻
9.8 小结
硬件卸载功能实际上是网卡功能的增强,通过由网卡硬件提供额外 的功能来分担CPU的处理负荷。可以认为是有好处而没有额外短处的功 能。但需要明确的是,由于硬件多种多样,硬件卸载功能的支持与否以 及支持的程度都可能不同,同时应用程序需要了解并使用硬件卸载功 能,否则是无法从硬件卸载功能中得到好处的。因此,这对开发者提出 了额外的要求