《深入浅出DPDK》—第2章2.5节Cache预取

本节书摘来自华章出版社《深入浅出DPDK》一书中的第2章,第2.5节Cache预取,作者朱河清,梁存铭,胡雪焜,曹水 等,更多章节内容可以访问云栖社区“华章计算机”公众号查看。

2.5 Cache预取
以上章节讲到了多种和Cache相关的技术,但是事实上,Cache对于绝大多数程序员来说都是透明不可见的。程序员在编写程序时不需要关心是否有Cache的存在,有几级Cache,每级Cache的大小是多少;不需要关心Cache采取何种策略将指令和数据从内存中加载到Cache中;也不需要关心Cache何时将处理完毕的数据写回到内存中。这一切,都是硬件自动完成的。但是,硬件也不是完全智能的,能够完美无缺地处理各种各样的情况,保证程序能够以最优的效率执行。因此,一些体系架构引入了能够对Cache进行预取的指令,从而使一些对程序执行效率有很高要求的程序员能够一定程度上控制Cache,加快程序的执行。
接下来,将简单介绍一下硬件预取的原理,通过英特尔NetBurst架构具体介绍其预取的原则,最后介绍软件可以使用的Cache预取指令。

2.5.1 Cache的预取原理
Cache之所以能够提高系统性能,主要是程序执行存在局部性现象,即时间局部性和空间局部性。
1)时间局部性:是指程序即将用到的指令/数据可能就是目前正在使用的指令/数据。因此,当前用到的指令/数据在使用完毕之后可以暂时存放在Cache中,可以在将来的时候再被处理器用到。一个简单的例子就是一个循环语句的指令,当循环终止的条件满足之前,处理器需要反复执行循环语句中的指令。
2)空间局部性:是指程序即将用到的指令/数据可能与目前正在使用的指令/数据在空间上相邻或者相近。因此,在处理器处理当前指令/数据时,可以从内存中把相邻区域的指令/数据读取到Cache中,这样,当处理器需要处理相邻内存区域的指令/数据时,可以直接从Cache中读取,节省访问内存的时间。一个简单的例子就是一个需要顺序处理的数组。
所谓的Cache预取,也就是预测数据并取入到Cache中,是根据空间局部性和时间局部性,以及当前执行状态、历史执行过程、软件提示等信息,然后以一定的合理方法,在数据/指令被使用前取入Cache。这样,当数据/指令需要被使用时,就能快速从Cache中加载到处理器内部进行运算和执行。
以上介绍的只是基本的预取原理,在不同体系架构,甚至不同处理器上,具体采取的预取方法都可能是不同的。以下以英特尔NetBurst架构的处理器为例介绍其预取的原则。详细内容请参见[Ref2-1]。

2.5.2 NetBurst架构处理器上的预取
在NetBurst架构上,每一级Cache都有相应的硬件预取单元,根据相应原则来预取数据/指令。由于篇幅原因,仅以一级数据Cache进行介绍。
1.一级数据Cache的预取单元
NetBurst架构的处理器上有两个硬件预取单元,用来加快程序,这样可以更快速地将所需要的数据送到一级数据Cache中。
1)数据Cache预取单元:也叫基于流的预取单元(Streaming prefetcher)。当程序以地址递增的方式访问数据时,该单元会被激活,自动预取下一个Cache行的数据。
2)基于指令寄存器(Instruction Pointer,IP)的预取单元:该单元会监测指令寄存器的读取(Load)指令,当该单元发现读取数据块的大小总是相对固定的情况下,会自动预取下一块数据。假设当前读取地址是0xA000,读取数据块大小为256个字节,那地址是0xA100-0xA200的数据就会自动被预取到一级数据Cache中。该预取单元能够追踪的最大数据块大小是2K字节。
不过需要指出的是,只有以下的条件全部满足的情况下,数据预取的机制才会被激活。
1)读取的数据是回写(Writeback)的内存类型。
2)预取的请求必须在一个4K物理页的内部。这是因为对于程序员来说,虽然指令和数据的虚拟地址都是连续的,但是分配的物理页很有可能是不连续的。而预取是根据物理地址进行判断的,因此跨界预取的指令和数据很有可能是属于其他进程的,或者没有被分配的物理页。
3)处理器的流水线作业中没有fence或者lock这样的指令。
4)当前读取(Load)指令没有出现很多Cache不命中。
5)前端总线不是很繁忙。
6)没有连续的存储(Store)指令。
在该硬件预取单元激活的情况下,也不一定能够提高程序的执行效率。这取决于程序是如何执行的。
当程序需要多次访问某种大的数据结构,并且访问的顺序是有规律的,硬件单元能够捕捉到这种规律,进而能够提前预取需要处理的数据,那么就能提高程序的执行效率;当访问的顺序没有规律,或者硬件不能捕捉这种规律,这种预取不但会降低程序的性能,而且会占用更多的带宽,浪费一级Cache有限的空间;甚至在某些极端情况下,程序本身就占用了很多一级数据Cache的空间,而预取单元为了预取它认为程序需要的数据,不适当地淘汰了程序本身存放在一级Cache的数据,从而导致程序的性能严重下降。
2.硬件预取所遵循的原则
在Netburst架构的处理器中,硬件遵循以下原则来决定是否开启自动预取。
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 两个执行效率迥异的程序
虽然绝大多数Cache预取对程序员来说都是透明的,但是了解预取的基本原理还是很有必要的,这样可以帮助我们编写高效的程序。以下就是两个相似的程序片段,但是执行效率却相差极大。这两个程序片段都定义了一个二维数组arr1024,对数组中每个元素都进行赋值操作。在内循环内,程序1是依次对ai, ai, ai… ai进行赋值;程序2是依次对a0, a1, a2 … a[1023] [i]进行赋值。

程序1:

for(int i = 0; i < 1024; i++) {
    for(int j = 0; j < 1024; j++) {
        arr[i][j] = num++;
    }
}

程序2:
for(int i = 0; i < 1024; i++) {
    for(int j = 0; j < 1024; j++) {
        arr[j][i] = num++;
    }
}

通过图2-8可以清晰地看到程序1和程序2的执行顺序。程序1是按照数组在内存中的保存方式顺序访问,而程序2则是跳跃式访问。对于程序1,硬件预取单元能够自动预取接下来需要访问的数据到Cache,节省访问内存的时间,从而提高程序1的执行效率;对于程序2,硬件不能够识别数据访问的规律,因而不会预取,从而使程序2总是需要在内存中读取数据,降低了执行的效率。


55622def6bb139c44c11e7fea225439b0f3d86c4

2.5.4 软件预取
从上面的介绍可以看出,硬件预取单元并不一定能够提高程序执行的效率,有些时候可能会极大地降低执行的效率。因此,一些体系架构的处理器增加了一些指令,使得软件开发者和编译器能够部分控制Cache。能够影响Cache的指令很多,本书仅介绍预取相关的指令。
软件预取指令
预取指令使软件开发者在性能相关区域,把即将用到的数据从内存中加载到Cache,这样当前数据处理完毕后,即将用到的数据已经在Cache中,大大减小了从内存直接读取的开销,也减少了处理器等待的时间,从而提高了性能。增加预取指令并不是让软件开发者需要时时考虑到Cache的存在,让软件自己来管理Cache,而是在某些热点区域,或者性能相关区域能够通过显示地加载数据到Cache,提高程序执行的效率。不过,不正确地使用预取指令,造成Cache中负载过重或者无用数据的比例增加,反而还会造成程序性能下降,也有可能造成其他程序执行效率降低(比如某程序大量加载数据到三级Cache,影响到其他程序)。因此,软件开发者需要仔细衡量利弊,充分进行测试,才能够正确地优化程序。需要指出的是,预取指令只对数据有效,对指令预取是无效的。表2-1给出了预取的指令列表。

304c1df53cea160700f3817de257cc777a2f83b0

预取指令是汇编指令,对于很多软件开发者来说,直接插入汇编指令不是很方便,一些程序库也提供了相应的软件版本。比如“mmintrin.h”提供了如下的函数原型:
void _mm_prefetch(char *p, int i);
p是需要预取的内存地址,i对应相应的预取指令,如表2-2所示。

cd34050a281a152bddfe0a1c565428d81d683c0d

接下来,我们将以DPDK中PMD(Polling Mode Driver)驱动中的一个程序片段看看DPDK是如何利用预取指令的。
DPDK中的预取
在讨论之前,我们需要了解另外一个和性能相关的话题。DPDK一个处理器核每秒钟大概能够处理33M个报文,大概每30纳秒需要处理一个报文,假设处理器的主频是2.7GHz,那么大概每80个处理器时钟周期就需要处理一个报文。那么,处理报文需要做一些什么事情呢?以下是一个基本过程。
1)写接收描述符到内存,填充数据缓冲区指针,网卡收到报文后就会根据这个地址把报文内容填充进去。
2)从内存中读取接收描述符(当收到报文时,网卡会更新该结构)(内存读),从而确认是否收到报文。
3)从接收描述符确认收到报文时,从内存中读取控制结构体的指针(内存读),再从内存中读取控制结构体(内存读),把从接收描述符读取的信息填充到该控制结构体。
4)更新接收队列寄存器,表示软件接收到了新的报文。
5)内存中读取报文头部(内存读),决定转发端口。
6)从控制结构体把报文信息填入到发送队列发送描述符,更新发送队列寄存器。
7)从内存中读取发送描述符(内存读),检查是否有包被硬件传送出去。
8)如果有的话,从内存中读取相应控制结构体(内存读),释放数据缓冲区。
可以看出,处理一个报文的过程,需要6次读取内存(见上“内存读”)。而之前我们讨论过,处理器从一级Cache读取数据需要3~5个时钟周期,二级是十几个时钟周期,三级是几十个时钟周期,而内存则需要几百个时钟周期。从性能数据来说,每80个时钟周期就要处理一个报文。
因此,DPDK必须保证所有需要读取的数据都在Cache中,否则一旦出现Cache不命中,性能将会严重下降。为了保证这点,DPDK采用了多种技术来进行优化,预取只是其中的一种。
而从上面的介绍可以看出,控制结构体和数据缓冲区的读取都没有遵循硬件预取的原则,因此DPDK必须用一些预取指令来提前加载相应数据。以下就是部分接收报文的代码。
while (nb_rx < nb_pkts) {
    rxdp = &rx_ring[rx_id]; //读取接收描述符
    staterr = rxdp->wb.upper.status_error;
    //检查是否有报文收到
    if (!(staterr & rte_cpu_to_le_32(IXGBE_RXDADV_STAT_DD)))
        break;
    rxd = *rxdp;
    //分配数据缓冲区
    nmb = rte_rxmbuf_alloc(rxq->mb_pool);
    nb_hold++;
    //读取控制结构体
    rxe = &sw_ring[rx_id];
    ……
    rx_id++;
    if (rx_id == rxq->nb_rx_desc)
        rx_id = 0;
    //预取下一个控制结构体mbuf
    rte_ixgbe_prefetch(sw_ring[rx_id].mbuf);
    //预取接收描述符和控制结构体指针
    if ((rx_id & 0x3) == 0) {
        rte_ixgbe_prefetch(&rx_ring[rx_id]);
        rte_ixgbe_prefetch(&sw_ring[rx_id]);
    }
    ……
    //预取报文
    rte_packet_prefetch((char *)rxm->buf_addr + rxm->data_off);
    //把接收描述符读取的信息存储在控制结构体mbuf中
    rxm->nb_segs = 1;
    rxm->next = NULL;
    rxm->pkt_len = pkt_len;
    rxm->data_len = pkt_len;
    rxm->port = rxq->port_id;
    ……
    rx_pkts[nb_rx++] = rxm;
}
  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: DPDK(Data Plane Development Kit)是一种高性能数据面开发工具包。深入浅出DPDK是一份详细介绍DPDK的PDF文档,适合初学者了解DPDK的基本概念和使用方法。 该文档从DPDK的介绍开始,介绍了DPDK的发展历史、使用场景和目标。接着详细介绍了DPDK的架构,包括主要组件和数据流向。其中详细介绍了包管理的基本原理和流程、缓存池的管理方式、内存对齐和地址映射等内容。 文档还介绍了如何使用DPDK,包括DPDK的编译和安装、如何使用DPDK进行虚拟网络功能(VNF)处理,以及如何使用DPDK进行数据包嗅探和过滤等。 此外,该文档还介绍了DPDK的性能优化技巧,包括如何优化包接收和处理、如何使用多核心优化性能和如何配置DPDK与硬件交互。 总体而言,深入浅出DPDK是一份非常有价值的文档,它不仅为初学者提供了详细的DPDK介绍和使用指南,还涵盖了DPDK的高级主题和性能优化技巧,为使用DPDK进行高性能数据面开发的人员提供了重要的参考资料。 ### 回答2: DPDK(Data Plane Development Kit)是一个高性能数据平面开发工具包,它使用用户态技术实现了零拷贝的数据处理和高速的数据包转发。作为一款开源工具,DPDK已经被广泛应用于虚拟化、云计算及网络功能虚拟化等领域。 《深入浅出dpdk》是一份由DPDK社区编写的指南,它的目的是帮助开发人员更好地了解DPDK,并使用DPDK构建高性能网络应用程序。该指南提供了详细的DPDK架构、API接口、应用案例和性能调优等方面的介绍,同时也介绍了其他相关技术,例如硬件加速、 NUMA架构、 数据库加速、分布式系统等。 《深入浅出dpdk》PDF版本可以从DPDK社区网站上自由下载,它包含了大量易懂的图表和代码实例,让开发人员更容易理解DPDK的概念和原理。对于想要在高性能数据平面开发方面得突破的开发人员来说,这份指南是不可或缺的学习资料。 需要注意的是,《深入浅出dpdk》不仅是一份教材,更是一份指导开发人员如何使用DPDK进行数据平面开发的实际指南。开发人员在阅读本指南的同时,也需要具备一定的网络编程基础和C语言编程能力。同时,开发过程中还需要注意DPDK版本的兼容性和网络设备的支持情况。 ### 回答3: DPDK(Data Plane Development Kit)是一种高性能数据面开发工具包,它可以帮助开发人员快速地实现高性能网络应用程序。本篇文章解析的Deep Dive into DPDK的PDF文件是一份帮助初学者深入了解DPDK的指南,其中详细地介绍了DPDK的各种重要概念、特性和使用方法。 首先,这份PDF文件详细介绍了DPDK的架构和工作原理。DPDK采用轮询机制减少了对内核的依赖,从而显著提高了网络数据处理的性能。此外,本文还介绍了DPDK中各种组件的作用和功能,例如EAL(Environment Abstraction Layer)、Mempool、Ring等。这些组件都是DPDK中重要的工具,它们可以帮助开发人员更好地构建高性能应用程序。 其次,本文还介绍了如何使用DPDK来构建网络应用程序。首先,开发人员需要安装DPDK,并正确配置相应环境变量。其次,本文还介绍了如何使用DPDK的API来实现数据包的发送和接收。这些API提供了高效的数据传输方式和内存管理方式,开发人员可以根据自己的需求进行自定义。 此外,本文还介绍了DPDK的一些高级功能,例如NUMA支持、内存优化等。这些功能可以帮助开发人员更好地控制和管理系统资源,从而确保系统的高性能和稳定性。 总之,本文是一份非常实用的DPDK指南,其中介绍了DPDK的各种重要概念、特性和使用方法。初学者可以通过阅读这份文件,快速掌握DPDK的核心知识,为构建高性能应用程序打下牢固的基础。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值