Lua编码的那些陷阱
2016-02-19 16:57
302 查看
声明:本文从别处拷贝而来,感谢原文作者的分享。
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的内存。
也就是赋值语句中老的字符串变成了垃圾数据,两轮循环之后,将有两个老串包含超过20KB的垃圾数据,这个时候lua虚拟机会进行垃圾收集并释放这20KB的内存,问题在于,每两次循环就要进行一次垃圾收集,读取完一个sql结果集,所需的内存是原本内容的三倍。
而要获取最终的字符串,同时避免过多的垃圾回收,我们可以使用table.concat函数将一个列表的所有元素合并。
2.获取table的元素个数
当一个table为数组的时候,我们可能会使用以下两种用法中的一种,来获取该table的元素个数:
修改table,发现返回的table元素依旧是4。
再把上面的代码修改为:
可以看到,这段代码可以得到我们想要的结果。把table当数组使用的时候,该数组的大小不固定,可动态增长,在Lua中可以通过整数下标访问数组中的元素。当跳过数组下标时,像前一段代码一样,tab[5]、tab[6]、tab[7]、tab[8]的值均为nil,而table.getn和#tab两个函数一遇到为nil的时候就会返回了,就出现之前的结果(同样适用于table当集合使用,其结果返回为0)。
3.函数重名
在C语言或其他静态语言中,会对函数名进行检查,不允许重名的函数出现,但在lua,重名的函数是允许出现的,这就给我们的编码埋下一些隐患。当项目达到一定规模的时候,就很难保证不出现重名函数。那重名的函数会导致什么问题呢?我们看下面的代码:
原来lua虚拟机会把lua中的函数名都作为局部变量,存在局部变量表里,并在栈上开辟一个寄存器空间,在运行期,将新建一个closure,并存在已保留的寄存器里面。当有一个新定义的函数加入时,会新建一个closure,lua会把它压入栈,在调用该函数的时候,就会从栈顶开始找,找到匹配的函数名则返回。
4.堆栈溢出
我们在运行lua的时候,有可能遇到这样一种报错 “stack overflow”,先看看下面一段代码:
语法上的确没有任何问题,但在执行的时候就会出现 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。
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。
相关文章推荐
- 详解Lua中的表的概念及其相关操作方法
- Lua编程示例(二):面向对象、metatable对表进行扩展
- 把Lua编译进nginx步骤方法
- Lua脚本自动生成APK包
- Lua中的元表(metatable)、元方法(metamethod)详解
- Lua中的metatable介绍
- Lua中ipair和pair的区别
- Lua中的函数精讲笔记
- 浅谈Lua的面向对象特性
- 详解Lua中的变量相关知识点
- Lua脚本语言入门笔记
- Lua脚本调用外部脚本
- 详解Lua中的if语句的使用方法
- Lua中调用函数使用点号和冒号的区别
- Lua中的闭合函数、非全局函数与函数的尾调用详解
- Lua中强大的元方法__index详解
- Lua中调用C++函数示例
- Lua面向对象之类和继承浅析
- Lua性能优化技巧(一):前言
- Lua中获取table长度问题探讨