最小化闭包

lua-users home
wiki

概述

闭包是在执行期间遇到函数语句或表达式时形成的。这将实际函数与词法作用域变量绑定在一起,形成一个闭包

效率

如果闭包生成操作重复出现,但函数本身实际上没有使用词法作用域,那么将闭包操作移出循环会更有效率。

注意:这取决于闭包的实现方式。将来,聪明的编译可能会使这变得不必要。(在 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


最近更改 · 偏好设置
编辑 · 历史记录
最后编辑于 2007 年 8 月 4 日下午 9:04 GMT (差异)