一次性能优化经历

自从上次修改backlog之后, Silly的IO能力,就一直以少量(约4~6K)的差距落后于redis,却一直找不到原因。

这次打算从头做一次profile来问题到底出在哪。

先用GNU提供的gprof分析一下C代码是否有值得优化的地方,结果发现CPU使用率最高的地方是luaVM内部和malloc/free。

我们所有的业务逻辑全在lua层做的,而IO线程与worker(lua)线程进行交互时是通过malloc来实现的。这几乎表明C代码几乎已经没有优化的余地了。

但是有个好消息,就是gprof不去profile系统调用和so,也许还有机会。

压测时,先使用top看一下每个cpu core的使用率,包括用户态与内核态。

用户态过高一般是应用逻辑代码消耗的,内核态则有可能是因为系统调用过多,上下文切换过频繁,等其他原因。

不过top只能看到当时的cpu状态,不太好看出整个测试区间,cpu消耗的曲线。可以改用’sar -u -P ALL 1’每隔一秒打印出cpu的使用情况。

通过观察发现,有一个线程的内核态有70+%之多,相比Redis来讲高出不少。

再使用vmstat命令查看in(系统中断)/cs(上下文切换), 可以确认在整个压测区间in和cs显著升高,推测应该是系统调用造成的。

为了近一步确认这些‘中断和上下文切换’是由Silly造成的,使用’pidstat -w -p PID 1’来打印出某一个线程的上下文切换频率。

当确认之后,再使用’strace -p $PID -c -f’来收集此进程所有系统调用次数。再根据收集到的信息有针对性的优化。

如果以上都做了,还是没有什么可以优化的余地。没关系,我们还有一个神器perf来查看Cache命中率,分支预测失败率,CPU调度迁移等与cpu密切相关的信息。

如果以上都已经做还是没有找到优化空间。

还有一个很常见但很容易被人忽略的因素,就是CPU的用户态和内核态都很低。

这种情况下,一般是程序或集群间有队列(这个队列可能是socket等一切有FIFO性质的设施),队列的一端处理过慢(比如由于某种原因,处理端被卡住了,而又不耗cpu) ,而队列的产生端在产生完请求之后,由于一直没有收到回应,也一直在idle中。

整个表现,看上去特别诡异,就像是突然间机器空载了一样。


最后,当我们发现应用层代码实在无法优化之后,别着急,也许还有最后几个免费午餐你还没有吃。

jemalloc

一款很优秀的内存分配器,即使对多线程也有很好的表现。以此次优化silly为例,把内存分配器换成jemalloc 5.0之后,请求处理速度有显著提升。

__builtin_expect

GNU内建函数,可以用来向GCC暗示哪个分支更高概率的被执行,以便GCC可以生成更好的代码,以方便CPU做分支预测。当我们的分支判断成功与失败的概率有显著差别时(比如异常处理),可以用来提高性能,至于能提高多少,要看具体情况。其中一种情况的测如见上篇

cpu affinity

linux内核向应用程序提供了一些接口,可以让我们微调内核,包括调度算法。cpu affinity可以修改进程或线程的cpu亲和力。以暗示内核,最少可能选成cpu迁移,cpu迁移数据可以通过perf工具来获取。



发表评论