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对程序逐步跟踪一下,说不定有惊喜 :)
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里面可以节省很多字节。
这里有个很好的文章关于内存访问时间:[url=http://www.cnblogs.com/xkfz007/archive/2012/10/08/2715163.html]CPU与内存的那些事[/url]
CPU利用率统计:
有个简单办法,没收到报文时进入一个空转,指令数和普通报文处理差不多。否则性能统计上看RX会占用很多时间。还要统计一下Batch没填满的比例,能大概看出负载状况。
DPDK配置:
这些是gdb单步出来的 :) 如果你的应用没那么复杂可以用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性能优化没有那么神秘,分享一下笔记,欢迎交流经验:
[email protected] 微信号: steeven_li
[img]http://dl2.iteye.com/upload/attachment/0122/1826/8f43158e-e1db-3a0a-91a9-201268a9d5eb.png[/img]
2016平安夜
12/28补充:X86下的性能结果也出来了,about 50% improvement! 同时也看到x86 cpu性能确实比ARM至少好处两倍以上。