DPDK性能优化技术汇总以及学习路线

Memory Access

Address Alignment

在内存中存取一个变量最高效的方式是将其放在一个可以被它的长度整除的地址上。

(void *)&variable % sizeof(variable) == 0

所谓的按某个长度对齐就是这个意思。GCC编译器会自动帮我们处理这些事情。比较特殊的方式是将一个大型的结构体,或者静态数组按64byte的方式对齐:

int BigArray[1024] attribute((aligned(64)));

这主要是考虑到CPU的Cache Line长度多为64byte,变量按64对齐可以使其正好开始于一个Cache Line,减少Cache Miss/False Sharing以及利用CPU的高级指令集并行计算。

Note: _ attribute _((aligned(x)))有时只对全局变量有效,而对局部变量无效。

Huge Page

大页技术是当前流行的一种性能优化技术。在Linux系统中有一套复杂的进程虚拟地址和内存物理地址的转换机制,复杂的细节我们不去关心,只需要知道Linux是通过页(Page)这一机制(Look-up table)来确立两者的对应关系的。简单的类比就是在一本2000页的书中找到某一个章节,远比在一本2页的书中复杂。考虑到传统页面4KB的大小和大页2GB的小大之差,这个类比还不是那么恰当。在CPU中,需要以缓存的形式存储一些转换关系,这种缓存成为TLB Cache。使用大页可以减少TLB Cache Miss。
在这里插入图片描述

Virtual addr maps to physical addr

Note: Huge Page可以在绝大部分情况之下提高性能,但并不是所有情况下都可以起到提升性能的效果。对于内存,需要综合考虑各种因素,提高性能的基本策略还是以空间换时间。详细的分析文章请Click

DPDK学习路线以及视频讲解+qun832218493获取

1.dpdk PCI原理与testpmd/l3fwd/skeletion

2.kni数据流程

3.dpdk实现dns

4.dpdk高性能网关实现

5.半虚拟化virtio/vhost的加速
在这里插入图片描述

NUMA

严格来说NUMA并不是一种性能优化技术,而是一种内存架构。
在这里插入图片描述

NUMA Architecture

每一个CPU Core都与它本地连接的内存直接相连,独享总线,具有最快的读写速度。如果去远程(remote)内存去读写的话,则需要跨CPU Core执行。在DPDK中,有一整套精巧且高效的内存分配和管理机制,结合大页和NUMA等机制,基本原则是,将一个CPU Core需要处理的数据都放在离它最近的内存上。相关的实现可以参考DPDK中memseg memzone等内存机制相关代码的实现,这里可以有专门文章介绍。

Polling Mode Drive(PMD)

是DPDK实现的优化Linux网络接收发送性能的模块,官方有详细的介绍资料。Click

Memory Pool

对于需要频繁填充释放的内存空间可以采用内存池的方式预先动态分配一整块内存区域,然后统一进行管理,从而省去频繁的动态分配和释放过程,既提高了性能,同时也减少了内存碎片的产生。内存池多以队列的形式组织空闲或占用内存。在DPDK中,还考虑了地址对齐,以及CPU core local cache等因素,以提升性能。

这里提到的内存对齐不同于前面仅仅将变量放在一个合适的地址“数目”上,而是综合考虑了内存通道(channel)和rank,将变量(比如一个三层网络的Pkt),平均分布于不同的channel之上(多依靠padding),可以减少channel拥塞,显著提升性能。如图:
在这里插入图片描述

Two Channels and Quad-ranked DIMM Example

对于供多个线程同时使用的内存池,为了减少对内存池的读写冲突,可以考虑Local Cache的机制。即内存池为每一个线程/CPU Core维护一个Local Cache,本地的CPU Core对其操作是没有竞争的。每个CPU Core都是以bulk的形式从内存池中请求数据写入Local Cache或者将Local Cache的数据写入内存池。这样便大幅减少了读写冲突。
在这里插入图片描述

Local cache per core

Linker Considerations

可以理解的一个简单事实是,如果经常使用的函数被储存在指令内存的同一区域,甚至存储顺序和调用顺序一致,那么程序整体执行的效率将会有所提升。一个简单的方法就是尽量使用static函数:

static void f(void)

将同一模块中的函数在链接阶段放在一起。但很多时候,处于程序模块化编程考虑,模块之间互相调用的函数和方法并没有被显式地置于同一处指令内存,此时可以在关键函数集中采用:

_attaribute_(section(X))

将函数显式地置于Read only(program memory)Section X,方便一起调用。同时也可以map文件的形式安排指定。

Note: 同样的原则也适用于变量。

CPU

Advanced Instruction Set

使用先进的CPU指令集,带来的主要好处是可以并行完成向量化的操作,也就是所谓的SIMD(Single-Instruction-Multiple-Data)操作。当需要对大型数据集执行相同的操作的时候,向量操作可以带来明显的性能提升。例如图像处理、大型矩阵计算、网络数据包还有内存复制操作等。

Note: 对于数据间有互相依赖和操作上有继承的运算,比如排序,并不适合向量操作。

先进的指令集一般包括SSE SSE2 AVX AVX512 YMM ZMM。这些指令集对数据储存的地址都有比较严格的要求,比如256bit-YMM要求数据按32对齐,512-bit ZMM要求数据按64对齐。对于向量操作,一般希望符合如下条件:

小型的数据类型:char short int float

对大型数据集执行类似的操作

数据对齐

数据集长度可以被向量长度整除

Note: 可以将关键函数写为针对不同数据集的不同版本,视运行环境编译运行。

Compiler

Branch Predication

在这里插入图片描述

CPU是以流水线的方式执行程序指令。所谓流水线,可以简单理解为在执行一个指令的同时,读取下一条指令。对于程序中大量出现的if else while for ? :等含有条件判断的情景,CPU需要能够正确提取下一条指令以便流水线可以流畅执行下去。一旦提取的是错误分支的指令,虽然不影响程序运行的结果,但整条流水线都会被清空,再重新读入正确分支的指令,对程序运行效率影响颇大。CPU一般都有硬件分支预测器,但我们也可以用likely()/unlikely()等方式显示指定,另外在设计程序的时候也以使分支判断具有一定的规律性为好,比如一组经过排序的输入数据。

Branchless Code

为了最大限度减小Branch mispredication对性能带来的影响,可以将一些常见的分支判断转换为Branchless的形式。比如返回两个数中较大的值,一般可以写做:

int max = (x > y) ? x : y;

这里其实隐含了一个条件判断。如果用branchless的形式,同样的功能可以写做为:

int max = x ^ ((x ^ y) & -(x < y));

当有大量调用,同时输入无甚规律性的时候可以考虑采用Branchless code。一个比较全面的技巧合计在:Click。

loop-unrolling

Loop-unrolling的一大好处就是可以减少循环分支预测的次数。对于简单的循环,CPU其实可以很好的完成分支预测的工作,但对于嵌套的循环,或者循环内部会改变循环次数的循环,分支预测就变得困难。loop-unrolling的特点可以用如下的例子说明:

int i;
for (i = 0; i < 20; i++) {
    if (i % 2 == 0) {
        FuncA(i);
    } else {
        FuncB(i);
    }
    FuncC(i);
}

上面这个执行了20次的循环可以用loop-unrolling展开:

int i;
for (i = 0; i < 20; i += 2) {
    FuncA(i);
    FuncC(i);
    FuncB(i + 1);
    FuncC(i + 1);
}

Cons:

循环只执行10次,减少了一半

可以更准确得被CPU的branch predictor预测

没有了循环体内的if分支

Pros

如果循环计数器是奇数,则需要特别的处理。

Note: Loop-unrolling也需要考虑适用场合。主要适用于循环体的分支是主要的性能热点的时候。

Anti-aliasing

当有多个指针指向同一处物理内存(变量)的时候,称为pointer aliasing。作为编译器,并不能确认两个相同类型的指针是否指向同一处地址,即对其他指针的操作,是否会影响另外的指针所指向的内存。这就要求每次碰到这两个指针其中的任何一个的时候,都需要重新从内存中读取当前值。示例如下:

void Func1 (int a[], int *p) {
    int i;
    for (i = 0; i < 100; i++) {
        a[i] = *p + 2;
    }
}

void Func2() {
    int list[100];
    Func1(list, &list[8]);
}

在Func1中,有必要每次都重新读入p,并且重新计算p + 2,因为在Func2的调用中,与list[8]发生aliasing。对编译器来讲,它需要考虑这种“理论上的可能”,从而付出大量的重复劳动。

当程序可以确认两个指针不会发生aliasing的时候,可以用关键字__restrict__给编译器以明确的指示。

Prefetch

使用prefetch指令可以帮助我们提前预存一个将要使用的变量至CPU缓存:

_mm_prefetch

但在实际使用过程中要特别小心,现代CPU都有自己的硬件prefetch机制,如果不是经过测试,确认性能有所提高,尽量不要轻易使用该指令。这里有一篇资料对此有详细解释:Click

Note:需要确认CPU支持SSE指令集

Multi-threads

Lock-less

一般将GCC提供的一些原子操作视为“Lock-less code”。这些操作包括一些原子自增,CAS等操作。

__sync_fetch_and_add(type* ptr, type value)
__sync_compare_and_swap(type *ptr, type oldval, type newval)
etc…

这些操作虽然表面上没有了锁的痕迹,但实际上其汇编指令还是存在一个#lock锁总线的操作。所以也不必对其性能抱太高期望。对于所有关于锁的操作,需要强调的是,锁本身并不影响性能,只有对锁的争抢才影响性能。

Local Cache

如同之前介绍的那样,还有一种策略是将任务尽量划分为不相互依赖的各部分,分别交给不同的CPU Core去处理,仅仅在结果汇总的时候有少量的锁操作。在DPDK中大量应用了这种思想。

Core Affinity

将一个任务指定交给某个CPU Core处理,可以减少上下文切换和context switch的次数,以及提高缓存命中率。在Linux程序中可以通过

int sched_setaffinity(pid_t pid, size_t cpusetsize, const cpu_set_t *mask);

来设定线程的CPU亲和性。

False Sharing

False Sharing也是在多线程操作中需要避免的缓存失效的问题。如果两个变量分别被两个线程操作,但它们出现在同一条Cache Line中,则两个线程之间还是会互相影响。任何一个线程对该Cache Line的写操作,都会失整条Cache Line在另外一个线程处失效。如下图:
在这里插入图片描述

对此最简单的办法,是可以添加Cache Padding将两个变量分隔在不同的Cache Line之中,或者以Cache Line Size对齐的方式分配内存。

  • 4
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
DPDK(Data Plane Development Kit)是一个用于优化网络性能的软件开发工具包。它提供了一组库函数和驱动程序,可以帮助开发人员实现高性能的网络包处理应用。 DPDK性能优化主要体现在以下几个方面: 1. 高性能数据路径:DPDK通过绕过操作系统内核来直接访问网络设备,减少了内核处理的开销,提升了数据包处理的性能。使用DPDK可以实现高达数百万数据包每秒的处理能力,大大降低了网络延迟。 2. 多核并发处理:DPDK支持多核并发处理,可以利用现代多核处理器的优势来实现更高的吞吐量。通过将不同的处理任务分配到不同的核心上,并采用无锁数据结构和锁粒度优化等技术DPDK可以高效地实现并行处理。 3. 零拷贝技术DPDK利用了物理内存和非统一内存访问(NUMA)架构的特性,实现了高效的零拷贝技术。通过将网络数据包直接从网络设备接收到应用程序的内存空间,避免了不必要的数据拷贝操作,减少了内存带宽的消耗,提升了数据包处理的效率。 4. 硬件加速技术DPDK支持一些硬件加速技术,如Intel® QuickAssist 技术,可以用于加速加密、压缩等计算密集型操作。通过利用硬件加速技术,可以进一步提升网络包处理的性能。 综上所述,DPDK是一个用于优化网络性能的工具包,通过提供高性能数据路径、多核并发处理、零拷贝技术和硬件加速技术等功能,可以帮助开发人员实现高性能的网络包处理应用。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值