如何做魔道祖师网站,太原网站建设鸣蝉公司,空间设计软件,陕icp网站建设一、背景介绍
在前面介绍了DPDK中的上层对并行的支持#xff0c;特别是对多核的支持。但是#xff0c;大家都知道#xff0c;再怎么好的设计和架构#xff0c;再优秀的编码#xff0c;最终都要落到硬件和固件对整个上层应用的支持。单纯的硬件好处理#xff0c;一个核不…一、背景介绍
在前面介绍了DPDK中的上层对并行的支持特别是对多核的支持。但是大家都知道再怎么好的设计和架构再优秀的编码最终都要落到硬件和固件对整个上层应用的支持。单纯的硬件好处理一个核不够多个核在可能的情况下把CPU的频率增加加大缓存等等。在现有水平的范围内这些都是可以比较容易做到的。 但是另外一个就是在CPU上如何最终运行指令也可以叫做固件设计这个就需要设计人员动脑子了。一般来说IPCInstruction Per Clock一个时钟周期内执行的指令数量可不要看成进程间通信的数量越高CPU运行性能越高频率和核数相同。 现代CPU基本使用了越标量superscalar体系结构通过以空间换时间的方式实行了指令级并行运算。不同的架构的处理器可能在硬件设计本身有所不同但在追求并行度上原理基本相同。 在前面的多核编程中介绍过几种指令目前常用的基本以SIMD单指令流多数据流和MIMD多指令流多数据流为主。后者一般是多核和多CPU当然更高层次的多计算机也算但在分析本文中更倾向的是SIMD毕竟一个核心能处理多少更能体现性能和效率。 SIMD其实很容易理解可以认为是一种并行的批处理。原来只能一次取一条指令处理一条数据这次可以一条指令处理多条数据。举个最简单的例子加指令需要有两次读操作数而如果使用SIMD,则一次就可以都读进来。其后的处理周期也是如此那么效率至少增加了一倍。 而这些指令设计和处理会形成一个指令集它的发展也有一个过程intel的SIMD指令集主要有MMX, SSE, AVX, AVX-512主流就是SSE/AVX。AMD的比较复杂有兴趣可以查找看一下。
二、DPDK中的应用
在DPDK中对SIMD的应用体现在数据的处理上DPDK提供了一个化化的拷贝memcpy函数它充分利用了SIMD指令集
static __rte_always_inline void *
rte_memcpy(void *dst, const void *src, size_t n)
{if (!(((uintptr_t)dst | (uintptr_t)src) ALIGNMENT_MASK))return rte_memcpy_aligned(dst, src, n);elsereturn rte_memcpy_generic(dst, src, n);
}
static __rte_always_inline void *
rte_memcpy_aligned(void *dst, const void *src, size_t n)
{void *ret dst;/* Copy size 16 bytes */if (n 16) {return rte_mov15_or_less(dst, src, n);}/* Copy 16 size 32 bytes */if (n 32) {rte_mov16((uint8_t *)dst, (const uint8_t *)src);rte_mov16((uint8_t *)dst - 16 n,(const uint8_t *)src - 16 n);return ret;}/* Copy 32 size 64 bytes */if (n 64) {rte_mov32((uint8_t *)dst, (const uint8_t *)src);rte_mov32((uint8_t *)dst - 32 n,(const uint8_t *)src - 32 n);return ret;}/* Copy 64 bytes blocks */for (; n 64; n - 64) {rte_mov64((uint8_t *)dst, (const uint8_t *)src);dst (uint8_t *)dst 64;src (const uint8_t *)src 64;}/* Copy whatever left */rte_mov64((uint8_t *)dst - 64 n,(const uint8_t *)src - 64 n);return ret;
}
static __rte_always_inline void *
rte_memcpy_generic(void *dst, const void *src, size_t n)
{__m128i xmm0, xmm1, xmm2, xmm3, xmm4, xmm5, xmm6, xmm7, xmm8;void *ret dst;size_t dstofss;size_t srcofs;/*** Copy less than 16 bytes*/if (n 16) {return rte_mov15_or_less(dst, src, n);}/*** Fast way when copy size doesnt exceed 512 bytes*/if (n 32) {rte_mov16((uint8_t *)dst, (const uint8_t *)src);rte_mov16((uint8_t *)dst - 16 n, (const uint8_t *)src - 16 n);return ret;}if (n 48) {rte_mov32((uint8_t *)dst, (const uint8_t *)src);rte_mov16((uint8_t *)dst - 16 n, (const uint8_t *)src - 16 n);return ret;}if (n 64) {rte_mov32((uint8_t *)dst, (const uint8_t *)src);rte_mov16((uint8_t *)dst 32, (const uint8_t *)src 32);rte_mov16((uint8_t *)dst - 16 n, (const uint8_t *)src - 16 n);return ret;}if (n 128) {goto COPY_BLOCK_128_BACK15;}if (n 512) {if (n 256) {n - 256;rte_mov128((uint8_t *)dst, (const uint8_t *)src);rte_mov128((uint8_t *)dst 128, (const uint8_t *)src 128);src (const uint8_t *)src 256;dst (uint8_t *)dst 256;}
COPY_BLOCK_255_BACK15:if (n 128) {n - 128;rte_mov128((uint8_t *)dst, (const uint8_t *)src);src (const uint8_t *)src 128;dst (uint8_t *)dst 128;}
COPY_BLOCK_128_BACK15:if (n 64) {n - 64;rte_mov64((uint8_t *)dst, (const uint8_t *)src);src (const uint8_t *)src 64;dst (uint8_t *)dst 64;}
COPY_BLOCK_64_BACK15:if (n 32) {n - 32;rte_mov32((uint8_t *)dst, (const uint8_t *)src);src (const uint8_t *)src 32;dst (uint8_t *)dst 32;}if (n 16) {rte_mov16((uint8_t *)dst, (const uint8_t *)src);rte_mov16((uint8_t *)dst - 16 n, (const uint8_t *)src - 16 n);return ret;}if (n 0) {rte_mov16((uint8_t *)dst - 16 n, (const uint8_t *)src - 16 n);}return ret;}/*** Make store aligned when copy size exceeds 512 bytes,* and make sure the first 15 bytes are copied, because* unaligned copy functions require up to 15 bytes* backwards access.*/dstofss (uintptr_t)dst 0x0F;if (dstofss 0) {dstofss 16 - dstofss 16;n - dstofss;rte_mov32((uint8_t *)dst, (const uint8_t *)src);src (const uint8_t *)src dstofss;dst (uint8_t *)dst dstofss;}srcofs ((uintptr_t)src 0x0F);/*** For aligned copy*/if (srcofs 0) {/*** Copy 256-byte blocks*/for (; n 256; n - 256) {rte_mov256((uint8_t *)dst, (const uint8_t *)src);dst (uint8_t *)dst 256;src (const uint8_t *)src 256;}/*** Copy whatever left*/goto COPY_BLOCK_255_BACK15;}/*** For copy with unaligned load*/MOVEUNALIGNED_LEFT47(dst, src, n, srcofs);/*** Copy whatever left*/goto COPY_BLOCK_64_BACK15;
}更多相关的代码在rte_memcpy.h和rte_memcpy.c中注意它包含不同CPU架构平台的多个版本不要搞混。 从上面的代码可以看到影响拷贝速度的有以下几点 1、字节对齐和数据的加载存储。 这个大家都明白除了字节对齐速度加快外而且DPDK中还对不同的字节对齐以及长度进行了控制充分发挥SIMD的优势说直白一点就是在条件允许的情况下一次拷贝数量多【16字节128位】这个和平台支持有关 2、函数和库调用开销库函数需要调用过程这个也浪费时间。这个库调用过程在编译选择优化的过程中优化难度也比较大不如在DPDK中直接调用特别是使用 static __rte_always_inline静态内联时这在网上有很多优化的比较自己也可以试一试。 3、整体上来说数据量越大上面的优化越优势越大否则优势则不明显。 上述的比较是针对库glibc以及DPDK相比而言的至于个人优化过的则不在此范畴之内。另外随着技术的进步如果用高版本的glibc并开启优化后可能效果差别也不大这个没有进行比较。 有兴趣可以看看rte_mov256等几个函数。 需要说明的是对于某一类函数没有普遍最优之说。只有场景条件限制下的最合适。也就是说DPDK的拷贝函数不代表此函数比glibc中的拷贝函数优秀只是说明此函数在DPDK的应用场景下更合适。 最后总结一下针对内存拷贝的优化点 1、减少拷贝过程中的附加处理如字节对齐 2、在平台允许情况下使用最大带宽拷贝最大数量 3、使用平坦顺序内存并使用分支预测减少分支跳转如是否有范围重叠等 4、有可能的情况下使用non-temporal访存执令 5、使用加速拷贝的一些指令string操作指令等。 6、处理大内存M以上和小内存K以下的不同场景这个在一些常用框架中都会处理
三、总结
性能和效率的提升是一个系统工程。它可能会从一个点开始然后不断的影响别的点然后这些点又互相影响最后蔓延到整个系统形成一个量变到质变的过程。计算机应用也不外乎这样。 DPDK中通过Linux内核的一些设计如大页通过一种工程优化的手段来提高网络通信的效率但反过来内核也会借鉴DPDK的一些特点来吸收到内核中去。同样DPDK的出现对硬件本身的设计也提出了虚拟化的相关等要求。硬件水平的提高又可以提高DPDK的性能。 国内的缺少的不是后面的一系列动作缺少的恰恰是开始那个点那个用于爆发的创新点。