这是一个旧版本的
LuaTips于2006-03-07 23:05:45.
Lua 的 5.1 版本已经正式发布。现在,我们应该把全部讨论放在这个版本上。
应该尽量使用 local 变量而非 global 变量。这是
Lua 初学者最容易犯的错误。global 变量实际上是放在一张全局的 table 里的。global 变量实际上是利用一个 string (变量名作 key) 去访问这个 table 。虽然
Lua5 的 table 效率很高 ,但是相对于 local 变量,依然有很大的效率损失。local 变量是直接通过
Lua 的堆栈访问的。有些 global 变量的访问是不经意的,比如我们有双重循环操作一个迭代的 table:
for k1,v1 in pairs(tbl) do
for k2,v2 in pairs(v1) do
...
end
end
这里,pairs 其实是一个全局变量应用的函数。如果我们这样做:
do
local pairs=pairs
for k1,v1 in pairs(tbl) do
for k2,v2 in pairs(v1) do
...
end
end
end
效率会稍微提高一些。如果是单层循环,这样做就没有意义。因为 for ... in 循环中的 pairs 这个函数只会被调用一次,而不是每次循环都去调。我们的原则其实是,被多次读取的 global 变量,都应该提取出来放到 local 变量中。
应该尽量减少函数对 local 变量的可视范围,常用的技巧是使用 do ... end 。当定义一个 function 的时候,这个 function 向上所有可见的范围的 local 变量都会被作为 upvalue 和 function 本身绑定在一起,成为一个 closure 。太多的 local 变量虽然几乎没有速度损失,但是会带来额外的内存消耗。过多的内存消耗会过早引起 gc 。尤其是下面这样的例子:
function foo()
local a,b,c
...
return function() ... end
end
每次调用 foo() 时返回的 function 都会将它可见的 local 变量绑定在一起,形成新的 closure 。这些 local 变量包括 foo() 内的 a,b,c ,还包括 foo() 上面可见的一些 local 变量。这将有可能成为性能问题。btw, closure 和 function 是两个概念。这里是会产生一份 function ,但是 closure 再每次调用的时候都会产生一份新的。
有些时候,closure 的产生是可以避免的,这里有
一个例子 我摘抄下来:
function create()
local e = {}
e.x = 0
e.update = function(self) -- mark
self.x = self.x + 1
end
return e
end
e1 = create()
e2 = create()
e3 = create()
这里,mark 这行,会被调用三次,每次均产生一个新的 closure 用于绑定 e (不过 function 本身只产生了一份) 。这里,local e 显然对 e.update 这个函数本身没有用,我们只需要把函数提到前面去即可。
do
local function temp(self)
self.x = self.x + 1
end
function create()
local e = {
x = 0,
update = temp
}
return e
end
end
这样,就可以避免 closure 的产生了。因为 function temp 不需要携带任何 upvalue 。btw, 我再这里修改了对 e 这个table 的赋值,这将稍微优于前面的赋值方法。因为编译器会得到 table 的大小,而避免多次分配内存。
如果这个例子扩展开,我们必须让 function temp 知道某些 create 调用时传入的信息,如:
function create(init)
local e = {
x = 0,
reset = function temp(self) self.x = init end
}
return e
end
我们可以使用一个技巧,避免 function temp 绑定 upvalue init 。
do
local reset
function reset(self)
self.x = self[reset]
end
function create(init)
return {
x = 0,
[reset] = init,
reset = reset
}
end
end
这里,把 init 这个值放到了 table 本身,reset 函数可以通过 self 取出来。key 选用的是 function reset ,这绝对可以避免和其它的 key 冲突。唯一的问题是让 table 大了一些。可是,lua table 是 hash 部分是按 2 的幂递增的,通常这不会带来额外的负担。一旦这个额外的 key 真的成了负担,即,增加这么一个 key 就会让 table 变成两倍大小。那么,我们可以用 metatable 解决这个问题。
do
local funcs = {}
local meta = {__index = funcs}
function funcs.reset(self)
self.x = self[funcs]
end
function create(init)
local e={
x = 0,
[funcs] = init
}
setmetatable(e, meta)
return e
end
end