Lua开发中的常见优化点
Posted 橙子突然想到
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Lua开发中的常见优化点相关的知识,希望对你有一定的参考价值。
笔者经历的项目中有使用到Lua进行客户端逻辑的开发,在回顾内容时,大致浏览学习并整理了一些Lua开发中常见的优化点,分享给大家。本篇文章由笔者学习整理得出,如果有错误之处欢迎指出~
自5.0版起,Lua使用了一个基于寄存器的虚拟机。(与CPU 中真实的寄存器并无关联), 每个活动函数都有自己的寄存器, 由于Lua提供了大量的寄存器, 所以Lua在预编译时就能够在寄存器中存储局部变量, 使得在Lua中访问局部变量的速度远快于访问全局变量, 所以将全局变量/全局函数缓存为局部变量再进行使用可以提升代码的运行效率。
Lua中的类型table由hash部分与数组部分组, 当往table中插入新的元素但是table size不够时, 将会触发Rehash, 重新收集计算table中的元素并进行扩容以确保元素得以容纳, 所以在应用table时应该尽量避免触发Rehash:
使用元素初始化table而不是对空table逐一赋值, 例如:
-- 效率更高, 只构造一次, size=3
local a = {1, 2, 3}
-- 效率更低, 会触发三次Rehash, size变化: 0->1->2->4
local a = {}
a[1] = 1
a[2] = 2
a[3] = 3尽量使用大table代替小table, 或者尽量复用table, 避免频繁的table构造与Rehash, 例如:
-- 效率更高
local t = {}
for i = 1, 100000 do
t[i] = i
-- something else
end
-- 效率更低
for i = 1, 100000 do
local t = {}
t[i] = i
-- something else
end初始化的写法需要注意:
-- 识别为数组, 直接初始化为size=3的table
local t = {1, 2, 3}
-- 下面这个写法表示lua没有这么智能, 不会识别为数组
-- 而是会进行三次Rehash, 最终size=4
local t = {[1] = 1, [2] = 2, [3] = 3}企图通过置空表项来缩小table占用的内存空间是不现实的, 当空间不足时,table的空间重新计算会在Rehash时进行, 但是对table中某个键值对的值置为nil的行为却绝对不会导致Rehash, 这么做也是有原因的: 如果这种行为会导致Rehash, 那么以下这种写法就不再安全:
for k, v in pairs(t) do
-- 如果置空会导致Rehash, 这次遍历就会被破坏, 迭代器不再有效
t[k] = v
end另外一个原因认为, 对键值对的值赋值时不应该做多余的条件检查(比如检查赋值的值是否为nil然后缩减空间占用), 这种行为会极大的影响赋值效率。一种比较曲线救国的做法是往table中新插入足够多个nil元素, 触发Rehash, 此时table会收回所以nil占用的空间(不推荐这么做, 一方面你不会知道足够多指多少个, 另一方面这个做法比较耗时)。
Lua中的字符串拼接行为优化: Lua中的字符串都是内化的, 变量只能持有字符串的引用, 每当产生一个新的字符串时, Lua就会检测该字符串是否已经存在备份, 如果是, 重用拷贝, 如果不是, 进行创建。这使得在Lua中字符串的比较与赋值都因为是基于引用的操作而变得更快, 但是字符串拼接等可能产生新字符串的行为却会变得更慢(某些允许变量直接持有字符串buffer语言也许会使用buff拼接, 而Lua会完整创建一个新串), 所以尽量使用table.concat()来代替频繁的 .. 操作, table.concat()的行为类似于预先开辟一整块buffer, 然后将字符串拷贝到这整块buffer上, 最终只产生一个新串。示例:
-- 效率更高
local astrSum = {}
for line in io.lines() do
astrSum[#astrSum + 1] = line
end
local strSum = table.concat(astrSum, ",")
-- 效率更低
local strSum = ""
for line in io.lines() do
-- 持续产生新串!!!
strSum = strSum .. "," .. line
endtable插入元素的速率优化
往table的数组部分插入一个元素有很多中方法, 比如原生提供的table.insert(t, e)方法。根据LuaWiki上的优化建议 Lua 5.1 Optimization Notes:
Short inline expressions can be faster than function calls.
t[#t+1] = 0
is faster thantable.insert(t, 0)
也有直接使用t[#t + 1] = e的方式去插入元素的, 实际上这两种方式速率相差不是特别的大, 原因在于不管是table.insert内部的实现, 还是#t的实现, 最终都会调用到
lj_tab_len
去获取t的长度, 这个操作的时间复杂度为O(log n), 当连续插入大量的元素时, 插入操作的时间复杂度就会由O(n)->O(n logn), 所以计算长度的操作对于插入的时间复杂度影响还是比较大的。为了防止调用该操作, 我们可以自己维护数组的长度, 实例如下:-- 效率更好
lcoal t = {} -- maybe has some elements
local iCount = 1
for i = 1, 100000 do
t[iCount] = i;
iCount = iCount + 1
end
-- 效率更差
lcoal t = {} -- maybe has some elements
for i = 1, 100000 do
t[#t + 1] = i;
-- or worse: use fun call
-- table.insert(t, i)
end更进一步的, 可以提前对Lua table进行空间分配来避免若干次空间扩展。
当Lua用于配合Unity的游戏开发时, 也有一些指的注意和优化的点,下一次笔者会另外整理一篇文章来说一说。
以上是关于Lua开发中的常见优化点的主要内容,如果未能解决你的问题,请参考以下文章
Review代码思考:排行榜同积分按时间排序优化方案 | Lua开发实战