luajit在处理某些字符串场景时出现性能问题

601 views
Skip to first unread message

mengqi wu

unread,
Jun 15, 2016, 3:58:46 AM6/15/16
to openresty, mengq...@alibaba-inc.com
问题描述:
luajit在处理以下场景字符串时,出现数十倍性能差距,复现场景(test.lua用于执行,test.sh用于生成测试数据):

处理相似的1W行数据,性能出现巨大差距,差距在几十倍量级:

test.lua
#!/home/mengqi.wmq/cpu/luajit-2.0.4/src/luajit

local f = io.open(arg[1], 'r')
local body = f:read("*all")
f:close()

function parse(body)
    local n = string.find(body, '\n');
    local last = 0;
    while n do

        local line = string.sub(body, last, n-1) --this line is performance bottleneck

        --print(line)

        last = n+1
        n = string.find(body, '\n', last)
    end
end

parse(body)

test.sh
#!/bin/sh

file_ok=1W_body_ok
file_err=1W_body_err

rm $file_err >& /dev/null
rm $file_ok >& /dev/null

for((i=1;i<=10000;i++));
do
    echo ${i}"9999""abcdefghijklmnopqrstuvwxyz|abcdefghijklmnopqrstuvwxyz" >> $file_ok
    echo "9999"${i}"abcdefghijklmnopqrstuvwxyz|abcdefghijklmnopqrstuvwxyz" >> $file_err
done

问题原因:
经调查,问题原因,在string.sub中,需要调用luajit中lj_str.c中的lj_str_new函数,该函数对字符串的创建采用了合并机制,在查找已存在串时,在上述场景中出现n级别循环,从而导致,string.sub操作,从n级别,升级至n平方级别,严重影响性能。

请问,有没有已知方法可以规避该问题,或者,必须在luajit中做修改?

Auto Generated Inline Image 1

lhmwzy

unread,
Jun 15, 2016, 4:11:09 AM6/15/16
to open...@googlegroups.com
能否用ngx.re.sub代替string.sub?

--
--
邮件来自列表“openresty”,专用于技术讨论!
订阅: 请发空白邮件到 openresty...@googlegroups.com
发言: 请发邮件到 open...@googlegroups.com
退订: 请发邮件至 openresty+...@googlegroups.com
归档: http://groups.google.com/group/openresty
官网: http://openresty.org/
仓库: https://github.com/agentzh/ngx_openresty
教程: http://openresty.org/download/agentzh-nginx-tutorials-zhcn.html

mengqi wu

unread,
Jun 15, 2016, 5:04:18 AM6/15/16
to openresty
该问题是lj_str_new造成的,瓶颈似乎不在sub

在 2016年6月15日星期三 UTC+8下午4:11:09,lhmwzy写道:

mengqi wu

unread,
Jun 15, 2016, 5:10:38 AM6/15/16
to openresty


每次的字符串创建,都将在上述while循环中做n级别循环,当字符串数量上万时,创建字符串会在while中循环上万次查找

在 2016年6月15日星期三 UTC+8下午5:04:18,mengqi wu写道:

mengqi wu

unread,
Jun 15, 2016, 5:13:40 AM6/15/16
to openresty



在 2016年6月15日星期三 UTC+8下午5:10:38,mengqi wu写道:

DeJiang Zhu

unread,
Jun 16, 2016, 11:16:09 AM6/16/16
to open...@googlegroups.com
Hello

在 2016年6月15日 下午3:58,mengqi wu <wmq...@163.com>写道:

问题原因:
经调查,问题原因,在string.sub中,需要调用luajit中lj_str.c中的lj_str_new函数,该函数对字符串的创建采用了合并机制,在查找已存在串时,在上述场景中出现n级别循环,从而导致,string.sub操作,从n级别,升级至n平方级别,严重影响性能。

确实 LuaJIT 中 lj_str_new 在某些场景下会成为瓶颈
因为 Lua 中的 string 有 inner 的处理,即:相同的字符串,只存储一份,所有字符串挂在字符串表里
所以,新建字符串的时候,需要先判断时候已经存在

然而,判断有两步:
第一步是先根据 哈希查找
第二步是在哈希相同的列表里再进行字符串比较(先比较长度再比较内容)

所以,在现实应用里 lj_str_new 一般不会是性能瓶颈
 

请问,有没有已知方法可以规避该问题,或者,必须在luajit中做修改?

最好的办法就是把对应的字符串处理操作移到 C 里来完成
比如,这个 PR [1] 就帮助我们的一个业务的 CPU 使用下降一半

建议在优化之前,先用火焰图找出真实系统的性能瓶颈点 :)

mengqi wu

unread,
Jun 27, 2016, 4:14:00 AM6/27/16
to openresty
感谢,瓶颈就是来自于hash的查找,可能是由于hash算法的原因,在之前的样本中,会出现,严重hash冲突,导致查找过程成为串,目前暂时用的在串前面加随机数,最后再去掉的方式,来hack这个问题

在 2016年6月16日星期四 UTC+8下午11:16:09,doujiang写道:

roid...@gmail.com

unread,
Dec 12, 2016, 6:10:10 AM12/12/16
to openresty
请问所说的PR [1] 链接在哪?

骆国辉

unread,
Dec 12, 2016, 6:40:53 AM12/12/16
to openresty
碰到类似的性能问题,通过火焰图分析,60%的时间都在执行lj_str_new函数,调用方法是大量字符串拼接操作,如"a="..a .. "&b="..b"&c="..c这种形式的操作。 不知道对于这种比较长的字符串操作有什么好方法? 用 table.concat不太方便,因为分隔符有两个。


------------------ 原始邮件 ------------------
发件人: "roidinev";<roid...@gmail.com>;
发送时间: 2016年12月12日(星期一) 晚上7:10
收件人: "openresty"<open...@googlegroups.com>;
主题: Re: [openresty] luajit在处理某些字符串场景时出现性能问题

廖曲淘

unread,
Dec 12, 2016, 7:49:53 AM12/12/16
to open...@googlegroups.com

我也有处理这样的,我是先构造过一个table,将每一个key ,value存在新的table里面,然后再用table.concat



订阅: 请发空白邮件到 openresty+subs...@googlegroups.com
发言: 请发邮件到 open...@googlegroups.com
退订: 请发邮件至 openresty+unsub...@googlegroups.com

--
--
邮件来自列表“openresty”,专用于技术讨论!
订阅: 请发空白邮件到 openresty+subs...@googlegroups.com
发言: 请发邮件到 open...@googlegroups.com
退订: 请发邮件至 openresty+unsub...@googlegroups.com

Zexuan Luo

unread,
Dec 12, 2016, 10:47:42 PM12/12/16
to openresty, 2544...@qq.com
我之前遇到同样的问题,处理办法是把分隔符也当作数据 concat 进去。
像这样:
local _from = new_tab(3, 0)
_from[1] = '('
_from[3] = ')'
...

然后:
_from[2] = sql
query._from = table.concat(_from)

在 2016年12月12日星期一 UTC+8下午7:40:53,骆国辉写道:

骆国辉

unread,
Dec 12, 2016, 11:43:03 PM12/12/16
to Zexuan Luo, openresty
按照table.concat(_from) 这个方法,我测试了一遍,很不幸,和用..直接拼接的效率相比,table.concat差不多甚至更差。


------------------ 原始邮件 ------------------
发件人: "Zexuan Luo";<spacewa...@gmail.com>;
发送时间: 2016年12月13日(星期二) 中午11:47
收件人: "openresty"<open...@googlegroups.com>;
抄送: "骆国辉"<2544...@qq.com>;
主题: Re: 回复: [openresty] luajit在处理某些字符串场景时出现性能问题

DeJiang Zhu

unread,
Dec 13, 2016, 1:44:07 AM12/13/16
to open...@googlegroups.com
Hello

在 2016年12月12日 下午7:10, <roid...@gmail.com>写道:
请问所说的PR [1] 链接在哪?

sorry, 居然忘了贴链接...

这里主要是利用 cosocket send 可以直接数组形式的 table
这个时候,cosocket 会在 C 里面拼接 buf,从而避免 Lua 字符串的 internal 处理逻辑
(table.concat 也是需要在 Lua 里新建一个大字符串的)
 
订阅: 请发空白邮件到 openresty+subscribe@googlegroups.com
发言: 请发邮件到 open...@googlegroups.com
退订: 请发邮件至 openresty+unsubscribe@googlegroups.com

Zexuan Luo

unread,
Dec 13, 2016, 2:02:17 AM12/13/16
to openresty, spacewa...@gmail.com, 2544...@qq.com

这取决于要拼接的字符串的多少了:)
我上面也只是举一个简化的例子
另外上面 table.concat 里面部分数据(分隔符)是不变的,拼接用 table 也可以复用
在 2016年12月13日星期二 UTC+8下午12:43:03,骆国辉写道:

骆国辉

unread,
Dec 13, 2016, 2:52:07 AM12/13/16
to openresty
非常感谢,查了下,tcp.send 一个 table时,会调用 ngx_http_lua_socket_tcp_send 函数,检测到参数为 LUA_TTABLE 类型时,再调用 ngx_http_lua_copy_str_in_table 将table转为字符串。
总结来说,就是要写个c接口,传入table,返回string。 效果还需验证。


------------------ 原始邮件 ------------------
发件人: "DeJiang Zhu";<douji...@gmail.com>;
发送时间: 2016年12月13日(星期二) 下午2:44
收件人: "openresty"<open...@googlegroups.com>;
主题: Re: [openresty] luajit在处理某些字符串场景时出现性能问题

DeJiang Zhu

unread,
Dec 14, 2016, 10:28:34 AM12/14/16
to open...@googlegroups.com
Hello

在 2016年12月13日 下午3:52,骆国辉 <2544...@qq.com>写道:
非常感谢,查了下,tcp.send 一个 table时,会调用 ngx_http_lua_socket_tcp_send 函数,检测到参数为 LUA_TTABLE 类型时,再调用 ngx_http_lua_copy_str_in_table 将table转为字符串。
总结来说,就是要写个c接口,传入table,返回string。 效果还需验证。

你是用在 cosocket 场景么,直接 send table 不就好了?
不过是其他场景的话,你可以自己参照着来处理,那时才需要自己写 c
 


------------------ 原始邮件 ------------------
发件人: "DeJiang Zhu";<douji...@gmail.com>;
发送时间: 2016年12月13日(星期二) 下午2:44
收件人: "openresty"<openresty@googlegroups.com>;
主题: Re: [openresty] luajit在处理某些字符串场景时出现性能问题
Reply all
Reply to author
Forward
0 new messages