遭遇lj_str_new

2021-12-14 09:04:26 浏览数 (1)

话说前几天我刚通过 mlcache 优化了热数据的问题,屁股还没坐热乎呢,就发现系统性能又下降了,本着自己挖的坑含泪也要填上的原则,我再一次开始了性能调优之旅。

对某个 nginx 进程执行 perf top

毫无疑问,从 perf top 结果来看,lj_str_new 已经成为了性能最大的短板。不过我们还是要搞一个 lua 语言级别的火焰图看着才靠谱,于是有了下图:

优化前的火焰图

不出所料,lj_str_new 非常宽,不过没有更详细的调用栈信息,不方便判断问题到底出在哪,而且我的代码在优化前并没有遇到类似的问题,到底 lj_str_new 是什么玩意?还得从 lua 中的字符串说起,引自「如何编写正确且高效的 OpenResty 应用」:

Lua 跟其他大部分语言有一点不一样,就是它的字符串是不可变的。需要对实际的字符串内容做 hash,然后用它查找该内容是否已经创建了对应的实例。既然说到做 hash,那么自然得提到 hash 碰撞。对于那些 hash 值一样的字符串,LuaJIT 把它们存储在链表里。如果许多字符串有着一样的 hash 值,那么这个链表就会很长,原来 O(1) 的开销会退化为 O(n)。这就是所谓的 hash 碰撞。不幸的是,LuaJIT 的默认的字符串 hash 函数就有这样的问题。

看到这里,我已经猜到了问题的原因是我错误的使用了 mlcache,前面提到我通过 mlcache 优化了热数据的问题,实际上当时我为了多缓存一些数据,把 lru_size 设置为了一百万,可这一百万个 key 就是一百万个字符串啊,可想而知随着字符串越来越多,hash 碰撞就会越来越严重,也难怪火焰图中看不到 lj_str_new 详细的调用栈信息,因为任何一个字符串操作都可能有问题,系统性能必然下降。

解决方法很简单,把 lru_size 改小点儿,本例中我设置为 1000,只缓存最热的数据:

优化后的火焰图

仔细对比优化前后两张火焰图,你会发现 lj_str_new 几乎看不到了,收工。

0 人点赞