24

遭遇lj_str_new

 3 years ago
source link: https://blog.huoding.com/2020/09/15/862
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

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

perf.png

对某个 nginx 进程执行 perf top

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

flamegraph1-1.png

优化前的火焰图

不出所料,lj_str_new 非常非常宽。不过我的代码在优化前并没有遇到类似的问题,lj_str_new 到底是什么玩意?引自「 如何编写正确且高效的 OpenResty 应用 」:

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

看到这里,我已经猜出了问题的原因是我错误的使用了 mlcache,前面提到我通过 mlcache 优化了热数据的问题,实际上后来我为了多缓存一些数据,把 lru_size 设置为了一百万,可这一百万个 key 就是一百万个字符串啊,可想而知随着字符串越来越多,hash 碰撞就会越来越严重,系统性能必然随之下降。

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

flamegraph2-1.png

优化后的火焰图

仔细对比优化前后两张火焰图,你会发现 lj_str_new 不见了,完美。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK