前几天写代码lua时,由于涉及到大量的数值逻辑计算,所以性能至关重要。经过一番研究和调试,总结了如下提高lua代码执行效率的方法:
- 使用缓存
这个不论是在lua,在任何语言的程序中都适用,脚本语言显得尤为突出。使用缓存来优化,提高程序性能是一个很大的主题,这里不再详细论述。只需记得,当使用重复数据(或有重叠)时一定要缓存起来,以供后面使用,而不是每次都重新计算,这样会大大提高效率,尤其是涉及到繁杂的计算时。
- 尽量不要在返回值中使用table
当我们需要返回多个值的时候,我们会考虑到返回一个table,类似C++里面返回一个结构体(或指针)一样。例如我们经常要判断函数是否执行成功,所以一般除了返回一个想要的结果外,还有返回一个bool值来表明函数是否执行成功。例如:
function f()
...
local ret = ...
local ok = ...
return {Ret = ret, Ok = ok}
end
遗憾的是,这种写法虽然清晰,但是会有一定性能损耗。我做了如下实验,一个函数返回table,一个函数返回多个值,看看这两种方法的执行时间:
function func1()
return 1, 2
end
function func2()
return {a = 1, b = 2}
end
local count = 20000000
for i = 1, count do
local a, b = func1() --①
--local a, b = func2() --②
end
执行函数①所要的时间为:
执行函数②所要的时间为:
我们可以看出其差别还是挺大的。
- 不要使用字符串作为比较对象
我们看如下例子:
function func3(param)
if param == 1 then
return 1
elseif param == 2 then
return 2
end
end
function func4(param)
if param == 'test_first' then
return 1
elseif param == 'test_two' then
return 2
end
end
local count = 20000000
local function test3()
for i = 1, count do
local p = 1
local a = func3(p)
end
end
local function test4()
for i = 1, count do
local p = 'first'
local a = func4(p)
end
end
test3()
--test4()
他们执行的时间分别是(考虑到杠精,这里多次执行,取平均时间):
大约提升了10%的执行时间。
- 不要使用面向对象技巧
在lua中使用面向对象的技巧很是常见,他可以提高代码的可读性,有利于代码的模块化还有封装。但是究竟面向对象技巧会有多少性能损失呢,请看如下代码:
function table.deepcopy(t, nometa)
local lookup_table = {}
local function _copy(t,nometa)
if type(t) ~= "table" then
return t
elseif lookup_table[t] then
return lookup_table[t]
end
local new_table = {}
lookup_table[t] = new_table
for index, value in pairs(t) do
new_table[_copy(index)] = _copy(value)
end
if not nometa then
new_table = setmetatable(new_table, getmetatable(t))
end
return new_table
end
return _copy(t)
end
local Object = {}
function Obj.func() end
function Object:new (data)
data = data or {}
setmetatable(data, {__index = self})
return data
end
function Object:new2 (data)
data = data or {}
local copy = table.deepcopy(self)
setmetatable(data, {__index = copy})
return data
end
local count = 2000000
for i = 1, count do
--local o = Object
--local o = Object:new()
local o = Object:new2()
end
上面涉及到两种面向对象的写法,一种是共享函数式的面向对象写法,另一种是数据分离式的面向对象写法。共享函数的面向对象写法不能在table中包含字段,否则会导致字段被所以实例共享,关于他们的区别我会另写一篇文章来讲述。总之尽量不要用到面向对象的方式来生成新的实例,因为他们的效率相差很大,从上图可以看出有几十倍甚至百倍的差距。
注意,我上面讲的的都是比较极端情况下优化代码的方法,比如代码中百万级别的循环。大多时候是不需要这么做的,因为单次运算时间是很短的,不会成为效率瓶颈。况且上面提到的方法有可能使代码变得不那么好读,没有面向对象也使代码逼格不能提升。总之,在实际项目中要灵活选择和使用,不要一味追求效率使代码失去了可读性,更不要放弃优化代码让项目失去价值。
不太影响性能的地方
写了几年的代码,我多多少少都会关注代码的质量,所以我常常会思考各种写法的优劣。代码的效率是一个很重要的指标,所以我会过多的关注代码的性能。在lua的时候,为了考虑到封装性,我常常将函数中的一大段逻辑写成一个内部函数,即闭包函数,而不是一个对等的外部函数。那么他们会有效率上的差别吗?由于这个与具体逻辑关系密切,各种场景下运行的时间各有差异,但是总体来说他们之间的执行效率差距是很小的,在考虑执行时间效率这个因素时可以忽略这一点。
还有在遍历数组类型的table时,用pairs和ipairs执行时间效率差距也是很小的,根据需要随便选一个就好,但是如果遍历map型table时,就只能用pairs了。
欢迎加入QQ群 858791125 讨论skynet,游戏后台开发,lua脚本语言等问题。