最小化闭包 |
|
闭包是在执行期间遇到函数语句或表达式时形成的。这将实际函数与词法作用域变量绑定在一起,形成一个闭包。
如果闭包生成操作重复出现,但函数本身实际上没有使用词法作用域,那么将闭包操作移出循环会更有效率。
注意:这取决于闭包的实现方式。将来,聪明的编译可能会使这变得不必要。(在 Lua 5 中,每个闭包都有自己的“函数环境”(即全局变量)。因此,即使没有上值,闭包在每次创建时也可能不相同。--RiciLake)
在以下代码中...
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()
在标记行创建了多少个“update”函数的副本?一个,在 e1、e2 和 e3 之间共享,还是三个,每个 e1、e2 和 e3 各一个?
我计划在我的当前 Lua 项目中做很多这样的事情,我有点担心内存效率之类的问题。-- NickDavies?
每次调用 create() 函数都会创建一个闭包,但这只会创建一个函数。-- NickTrout
这似乎是一个相当明确的声明,即闭包不会共享,即使它们是相同的(尽管至少函数代码是共享的)。如果效率是一个问题,那么最好创建一个单一的闭包并将其传递。例如
-- Using a 'do/end' block so that the local 'temp' does not pollute -- the name space. do -- The local variable 'temp' is assigned a closure. local function temp(self) self.x = self.x + 1 end -- Define the function 'create'. Every time it executes it -- assigns a reference to the closure stored in 'temp'. function create() local e = {} e.x = 0 e.update = temp return e end end
这里的关键是(据我了解)每次遇到“函数”表达式/语句时都会创建一个闭包。因此,如果您只执行一次,那么您就可以节省内存重复。
仅当“函数”表达式包装了其闭包内的词法作用域变量时,才需要多次绑定它,否则它效率低下(即使它可能看起来更整洁)。
例如,如果您有类似的东西
function create(init) local e = {} e.x = 0 e.reset = function temp(self) self.x = init end return e end
那么您将需要单独的闭包,因为每个单独的闭包实例都引用了周围词法作用域中的不同“init”变量!
所以
e1 = create(111) e2 = create(222) e1:reset() -- resets to '111' e2:reset() -- resets to '222'.
-- PeterHill
值得一提的是,闭包占用的空间并不比词法绑定的变量多多少。就我个人而言,在这种情况下,我会选择可读性而不是效率,因为从长远来看,可读性**就是**效率。但是,如果你真的想最大限度地减少分配,你可能想将上面的代码改写如下
do local reset function reset(self) self.x = self[reset] end function create(init) return { x = 0, [reset] = init, reset = reset } end end
这里我们利用了每个闭包都是唯一的这一事实,以便在新建的表中使用一个唯一的键,这不会与以后可能创建的任何其他键发生冲突。这不仅避免了创建闭包,而且还避免了创建词法绑定的变量。当然,它确实在表中添加了一个键值对,但在大多数情况下,这将是免费的。如果你甚至担心这一点(也许这个对象实际上只有八个键,所以额外的键将迫使它有 16 个键的空间),你可以将常量重置函数放到元表中
do local funcs = {} local meta = {__index = funcs} function funcs.reset(self) self.x = self[funcs] end function create(init) return setmetatable({ x = 0, [funcs] = init }, meta) end end
现在,我们使用一个表作为唯一的键。就我个人而言,我不喜欢这种方式,因为它真的很难读,而且通常最好将内部变量隐藏在 upvalue 中,在那里它们真正被隐藏,并且访问速度更快。在这种情况下,当然,可能甚至不希望尝试隐藏 init 值;它可能是公开对象接口的一部分。如果有很多函数,将公共对象函数放到元表中可以节省相当多的空间和时间。-- RiciLake