欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

ODP/DPDK代码级性能优化总结Tips

程序员文章站 2022-04-29 21:53:06
...
ODP/DPDK代码级性能优化总结Tips

以下过程基于ARM 64位CPU, 仅供参考

ODP是Linaro基金下面的开源框架,类似于DPDK。最近用ODP程序DEMO公司SOC性能,性能不理想,优化了一圈又一圈,发现驱动水分很大,包括ODP框架本身。中间不听Architect的建议,自作主张用DPDK+ODP来展示一下我们的多样化驱动, 找到方案,开发中发现DPDK驱动性能也不理想,自己吹的牛,含着泪也要优化完。

前提:
这里主要用64B小包简单反射仪表进来的数据,目的是确认驱动性能最优。进一步读取报文内容会导致加载额外cacheline,性能会下降一些。10G网口小包线速14.88Mpps, 对于2G CPU来说134个时钟周期,平均到每个报文的指令数是关键。

Perf性能检测工具:
如果不能用ubuntu直接安装,比如自己编译的Kernel源码,到tools/perf下make, 生产的perf就是了,复制到usr/bin下面去。
perf list 可以列出你当前cpu支持的性能参数
perf stat -p 'pidof your_app` -e task-clock,...用来检测程序的性能参数
注意-p参数附加到现有进程,可以避免看到程序启动过程的影响。
-e 中要有task-clock这样可以看到更多的%和M/s的统计参数
如果你的cache miss rate大于5%, 输出会有颜色标识
Perf在跟踪cache load miss rate和write miss rate特别有用,没有被cache到的数据访用它能明显看出差别。

高精度时钟:
千万别用系统函数来去时间评估性能,系统开销太大。rte_rdtsc() 是个很好的实现,一条汇编指令。在我的环境下对性能影响微乎其微。用全局变量分别累计batch处理中rx/rd/tx/free时间,每个局部需要统计的代码段的累加时间都除以这个总数,以%显示出来,这样一两个指令的节省都能明显看到%在变化。下面是我用到的几个宏,哪段代码怀疑有问题马上加上去
PERF_VAR xxx; //定义 全局performance counter
PERF_START(); //用在需要统计的代码段开头
PERF_COUNT(xxx); //统计某计数器
PERF_DUMP(xxx, sum); //命令行调用或者exit时打印性能百分比
注意:x86下面貌似RTDSC命令非常耗时,如果对每个包做性能统计时间会非常离谱,建议对批做统计,需要统计的代码段分开。

Eclipse:
保存时自动编译,甚至ssh到设备kill & run,节省很多时间。性能调优需要不停的修改对比,过程自动化能节省很多时间。

Gdb:
ODP/DPDK类用户态框架就是调试方便,这点比bare metal和kernel类程序强很多,有问题马上用"-g -O0"编译调试。
有空用GDB对程序逐步跟踪一下,说不定有惊喜ODP/DPDK代码级性能优化总结Tips
            
    
    博客分类: odp dpdk per performancedpdkodpcache

GIT:
曾经周末在家改了两天code, 一次checkin, 结果因为改了太多关键地方,出错了要调试很久。后来老老实实改一点提交一点,这样哪一步出错很容易追溯。Git的本地分支和提交,应该好好利用起来。

Assert:
当年面试Java, 在白板上写个简单api,  写完之后两个面试官热泪盈眶,我们终于看到会用assert的人" 。底层api编写尽量少用if else检查,上面要判断返回值,整个代码会很难看。底层调用自己给自己用,这些约束在调试时满足就可以,调试期间通过宏使能检查。更要命的是这些额外指令会严重拖累性能,特别是每个包都会调用的函数。运行期间要用宏关闭。
在优化ODP Ring/Pool操作时,很多底层函数因为没有assert, 程序出错只能单步跟踪,费时费力。

编译:
除了-O3还要打开-mtune。有时候perf看到iTLB miss很大,用-Os会明显减小代码尺寸,可以尝试。最后还是要加上-g反汇编看看结果。

Batch:
Batch处理是新网络处理框架的精华之一,也是性能优化的关键。Batch要用在各个方面。比如ring填充,分配一个报文写一个就不如一次分配一批然后批量写进去。虽然Pool里面用了hugepage, 又用了per core的缓存,每单次操作还是要判断并更新指针,最少几个指令。如果一次分批一批,平均到每个报文就一两个而已。
CPU可以缓存数据和指令,D-cache和i-Cache, 一般d-cache可以人为prefetch, i-cache则不行,所以对于几十K的i-cache尽量趁热把它利用好,batch操作完再进入下一段代码。

Pool:
Pool还能优化。比如大家排队打饭,每人递过去一个容器(数组),大师傅给你装进去。养猪的算法更高效,每头猪分配一段空间自己吃去。也就是把内存段的位置返回,不需要再一个个复制到接收数组里面。Pool cache是数组,快用完了再去大池子里分配,用这种返回指针方式更高效,少一次内存复制。
dpdk缺省的pool是ring实现,cons和prod在两个方向上,如果操作频繁相当于要不停遍历整个pool, cache利用不好。如果使用stack pool实现,放进来的马上分配出去会很好的利用缓存。stack设计没有ring的四中组合,只有一种加锁方式。


For循环:
一般常用的For循环对简单的循环体来说效率不高,指令都被浪费在i++和判断上,空间换时间的算法参考:DEQUEUE_PTRS()和rte_memcpy(),  ODP pool里面更是一次switch 32个来优化内存复制

Inline和函数指针:
-O3基本上会帮你自动inline,不过最好还是objdump看看汇编,有没有surprise. 函数指针会影响性能,比如dpdk里面的callback, 如果不用建议在.config里面关掉。

Likely/Unlikely:
虽然cpu分支预测已经做得很好,自己预测的分支更准。

Prefetch
记得以前写过一个顺序大内存访问程序,步长小于cacheline的时候性能基本一样,大于cacheline的时候性能严重恶化。这里有三个个有趣因素:同一cacheline访问时间消耗很小,因为数据已经在L1里面。超过一定数量的连续内存访问后,d-cache会通过预测提前加载后序内存,性能很好。超过一定步长预测程序就傻掉。所以要提高性能,一定要减小cache miss! 用Perf经常记录cache miss百分比。Prefetch用的不好,性能不仅没提升,D-cache反而因挤出有用数据,不如prefetch_non_tempral。


内存对齐:
一个struct包含u64, u8, u8,那么这个结构的数组操作会快吗?No, 改成u64, u32, u32更快。

Struct写:
还是上面那个结构数组,写入前两个字段。这也能优化?能:把不用的那个字段写0下去。What,加了一条指令,你有病吗?你有药吗?因为写内存是write-back,cache line(64B)读上来,合并再回写内存,如果全覆盖就不用读了吧。PS, 俺家丫头生病的时候就可以理直气壮的问:这回知道谁有病了吧?我有药哦。。。

寄存器变量:
一些经常读写的内存可以存放到register变量,比L1还快。。
记得ARM有16B寄存器,一直没试试在赋值清零的时候会不会节省指令。

if分支:
能少尽量少,特别是主分支上

mbuf字段顺序:
meta里面有128B, 两个cacheline大小,把收发包常用的字段的集中到前面,只操作一个CL。perf观测到cache r/w miss rate明显下降。

指针:
64位系统里面的8B内存指针真是浪费,因为现在都是hugepage, 很多地址都是连续的,特别是pool里面,所以mbuf可以用idx来替代,而且可以很短。一般网卡的回送数据用来查找对应的mbuf, 短地址就可以减少一次内存查找。

Pool cache size:
这是每个core专有的cache, 访问快,容量不够要去大池访问,所以大小要能容纳常用存取,尽量不要touch大池子。
Rx/tx队列不能贪多,够用就好否则超出Cache能力反而降低性能

少用内存:
特别是per packet的内存,能cache也不好,有些数据可以合并到mbuf里面,或者作为网卡的回送数据。上面提到短索引替代64位指针,如果用在mbuf里面可以节省很多字节。
这里有个很好的文章关于内存访问时间:CPU与内存的那些事

CPU利用率统计:
有个简单办法,没收到报文时进入一个空转,指令数和普通报文处理差不多。否则性能统计上看RX会占用很多时间。还要统计一下Batch没填满的比例,能大概看出负载状况。

DPDK配置:
这些是gdb单步出来的ODP/DPDK代码级性能优化总结Tips
            
    
    博客分类: odp dpdk per performancedpdkodpcache 如果你的应用没那么复杂可以用spsc, 或者手工调用api
CONFIG_RTE_MBUF_DEFAULT_MEMPOOL_OPS="ring_sp_sc"
CONFIG_RTE_MBUF_REFCNT_ATOMIC=n
CONFIG_RTE_PKTMBUF_HEADROOM=0?
CONFIG_RTE_ETHDEV_RXTX_CALLBACKS=n


参考链接(刚找到的,应该早点搜搜看):
http://dpdk.org/doc/guides/prog_guide/writing_efficient_code.html
http://events.linuxfoundation.org/sites/events/files/slides/DPDK-Performance.pdf
https://software.intel.com/en-us/articles/dpdk-performance-optimization-guidelines-white-paper

结果:
经过优化的ODP/DPDK在ARM SOC上达到线速,但是即使只读报文中的一个字节,因为加载了一个CL, 性能还是会降低一些,但是比没优化之前还是提高了很多。X86本身比较彪悍,公司网卡没优化也能跑到线速,看不出差别,后面会找25G双口网卡测试

功耗:
这种PMD模式框架本质上是个死循环,不知道能不能在没收到数据的时候进入WFI/WFE,  等待硬件唤醒

展望:
Intel开源做的很好,代码精炼,越来越多的公司在用DPDK做逐渐复杂的事情,大多数还在网络应用层面。个人觉得应该把这种编程模型和性能压缩思想上升到应用层面,比如数据库、存储、Web、再成熟就进FPGA,最终固化为ASIC,成本应该逐数量级降低。
注意:mtcp+dpdk的应用不应该局限为模拟socket接口,用DPDK里面的批量处理思想,对上层应用包括web server做批量化改造. 刚看到fd.io的TLDK, intel参与的开源udp/tcp框架

DPDK性能优化没有那么神秘,分享一下笔记,欢迎交流经验:steeven@gmail.com 微信号: steeven_li
ODP/DPDK代码级性能优化总结Tips
            
    
    博客分类: odp dpdk per performancedpdkodpcache


2016平安夜

12/28补充:X86下的性能结果也出来了,about 50% improvement! 同时也看到x86 cpu性能确实比ARM至少好处两倍以上。
  • ODP/DPDK代码级性能优化总结Tips
            
    
    博客分类: odp dpdk per performancedpdkodpcache
  • 大小: 39.5 KB