Lua编码的那些陷阱

声明:本文从别处拷贝而来,感谢原文作者的分享。

1.字符串连接符 “..”

当需要把多个小字符串拼接成一个大串的时候,例如,从一个sql结果集中取某列元素并将该元素组成以‘/’分割的字符串。

如果sql结果集是上万行,那么就会发现效率越来越低。这性能越来越低的原因是什么呢?这就要去追溯lua的垃圾收集算法,当Lua虚拟机发现程序使用太多的内存,它就会遍历它所有的数据结构,并进行释放它认为是垃圾的数据。一般情况下,这个算法有很好的性能,但下面的那段代码loop使得算法的效率极其低下。假设在loop中间,buff的大小已经是一个10kb的字符串, 一个sql结果中的所需要串连的成员的大小为10bytes, 那么buff .. row_tab.name .. “/” 新建的buff大小为10k+11bytes, 并且从buff中将10KB的字符串copy到新串中,也就是说每一行,都要移动10KB的内存,串连100行的时候,Lua已经移动了1MB的内存。

local buff = ''
for _, item in pairs(sql_tab) do
    if buff ~= '' then
        buff = buff .. "/" .. item 
    else
        buff = item
    end 
end

也就是赋值语句中老的字符串变成了垃圾数据,两轮循环之后,将有两个老串包含超过20KB的垃圾数据,这个时候lua虚拟机会进行垃圾收集并释放这20KB的内存,问题在于,每两次循环就要进行一次垃圾收集,读取完一个sql结果集,所需的内存是原本内容的三倍。

而要获取最终的字符串,同时避免过多的垃圾回收,我们可以使用table.concat函数将一个列表的所有元素合并。

local buff = ''
local tmp_tab = {}
for _, item in pairs(sql_tab) do
    table.insert(tmp_tab, item)
end
buff = table.concat(tmp_tab, "/")

2.获取table的元素个数

当一个table为数组的时候,我们可能会使用以下两种用法中的一种,来获取该table的元素个数:

tab = {1, 4, 5, 8}
print(#tab)                    --=====> 4
print(table.getn(tab))         --=====> 4

tab[9] = 10
print(#tab)                    --=====> 4
print(table.getn(tab))         --=====> 4
print(tab[9])                  --=====> 10

修改table,发现返回的table元素依旧是4。
再把上面的代码修改为:

tab = {1, 4, 5, 8}
print(#tab)                    --=====> 4
print(table.getn(tab))         --=====> 4

tab[5] = 10
print(#tab)                    --=====> 5
print(table.getn(tab))         --=====> 5
print(tab[5])                  --=====> 10

可以看到,这段代码可以得到我们想要的结果。把table当数组使用的时候,该数组的大小不固定,可动态增长,在Lua中可以通过整数下标访问数组中的元素。当跳过数组下标时,像前一段代码一样,tab[5]、tab[6]、tab[7]、tab[8]的值均为nil,而table.getn和#tab两个函数一遇到为nil的时候就会返回了,就出现之前的结果(同样适用于table当集合使用,其结果返回为0)。

3.函数重名

在C语言或其他静态语言中,会对函数名进行检查,不允许重名的函数出现,但在lua,重名的函数是允许出现的,这就给我们的编码埋下一些隐患。当项目达到一定规模的时候,就很难保证不出现重名函数。那重名的函数会导致什么问题呢?我们看下面的代码:

function fun_a()
    print('a')
end

fun_a()                 --==========> a

function fun_a()
    print('b')
end

fun_a()                 --==========> b

原来lua虚拟机会把lua中的函数名都作为局部变量,存在局部变量表里,并在栈上开辟一个寄存器空间,在运行期,将新建一个closure,并存在已保留的寄存器里面。当有一个新定义的函数加入时,会新建一个closure,lua会把它压入栈,在调用该函数的时候,就会从栈顶开始找,找到匹配的函数名则返回。

4.堆栈溢出

我们在运行lua的时候,有可能遇到这样一种报错 “stack overflow”,先看看下面一段代码:

function func_r(a)
    a = a + 1
    if a > 100000 then
        print(a)
    else
        func_r(a)
    end
    return a
end

x = 1
func_r(x)

语法上的确没有任何问题,但在执行的时候就会出现 stack overflow 的报错。是什么原因导致堆栈溢出呢?这个就要追究到Lua源码:Lua虚拟机会对堆栈进行一系列的检查(函数:luaL_checkstack),错误类型就有:

“too many arguments”,

“assume array is smaller than 2^40 “,

“string slice too long”,

“too many captures”,

“too many arguments to script”,

“too many nested functions”

例如,上面的代码就属于递归嵌套次数太多,默认限制20000。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值