注:本文内容引用自张洋老师的微博https://weibo.com/thinksoft,他现在是云和恩墨分布式存储研发专家。
前段时间写了几篇文章简单介绍了:SPDK存储性能开发套件、缓存优化以及性能优化的一些基本原理。本文打算介绍在实践中,实际遇到的一些问题以及解决方法。看看如何在短短10个工作日,将IOPS从5万提升到50万以上的。
Day1。编写测试程序,按照7:3的读写比例,4K随机,内存盘。在笔记本上测试单线程IOPS大概在2~4.5万左右,笔记本CPU频率不稳定。
Day2。单线程上不来,那么自然想到要尝试多核多线程。将程序改造为多线程后,还做了另外一个优化,将底层存储上的对象打开以后,缓存起来,不再重复打开。并减少线程之间的消息投递。
Day3。多线程性能测试,CPU频率超常发挥4.0G时,4线程可以跑到18万 IOPS。但是笔记本发热以后,CPU频率稳定到3.0G时,IOPS稳定在13万的样子。
Day4。转移到服务器上测试8线程,IOPS在17万,服务器的单核性能居然比不上笔记本(只有笔记本7成的样子)。好在服务器相当稳定,不会发生降频问题。(原来笔记本的CPU主频都是坑人的,好的时候4.0G以上,差的时候可以给你降到1.8G,你说气人不。)
Day5。通过perf工具检查热点函数,发现CRC和内存拷贝比较耗时。尝试优化CRC,采用Intel SSE优化无果,反而负优化(估计代码写得不好,或者是库函数已经够优化的了)。
Day6。利用strace工具检查程序中有无系统调用,发现没有系统调用。统计有无线程切换,结果是基本没有线程切换。再次用perf工具检查热点函数,优化热点函数后,性能无明显提升。去掉CRC,IOPS达到21万;后来发现程序居然没有优化编译(菜了),改为O2,IOPS达到23万。
Day7。review代码,将线程模型改造得更紧凑,无明显提升。再次利用perf查看函数热点,发现有很多openssl的函数调用。虽然单个函数占用的CPU比例并不高,但是全部的openssl库函数合在一起就很可观了。从线程堆栈发现,热点函数为MD5函数。
Day8。梳理Md5函数的调用流程。发现一个4K的IO,会导致一个16K的额外IO,并做了Md5校验。再次用到缓存将这些个16KB的元数据缓存起来,一来减少了内存拷贝,同时减少了Md5校验。IOPS突飞猛进,直接到了48万。
Day9。优化函数热点,强制内联一些频繁调用的函数。内联了perf检查到的热点函数以后,IOPS到了近52万。
Day10。一些实验,例如将哈希表的大小从1024改为65536,预期是冲突链短了,查询更快,性能更好。实际上性能居然有20%的下降。反其道而行之,将哈希表大小改为1,此时哈希表等价于一个有序数组,性能有10%的下降。猜测原因是CPU缓存命中率降低。还是老老实实改回1024。假设哈希表是一个长方形,长是冲突链,宽是哈希的大小。需要评估数据量的大小,来决定长是多少,宽为几何。尽量满足CPU能够高效访问。
总结来看,
1)此次优化对影响优化最明显的是缓存。缓存之后,减少了IO数量,和部分特别耗CPU的流程,性能提升显著。
2)线程间的消息交互以及等待要尽量的少,互不影响,独立高速。
3)要注意CPU访问数据的局部性原理,例如哈希表不可过大;高频率调用的函数,强制内联使得CPU访问程序二进制代码时,更加高效。
-------------------------------
云和恩墨 张洋 2021.9.17