Minimising Closures

lua-users home
wiki

Overview

Closures are formed whenever a function statement or expression is encountered during execution. This binds the actual function with lexically-scoped variables to form a closure.

Efficiency

If a closure-generating operation occurs repeatedly but the function itself does not actually make use of lexical scoping then it is more efficient to move the closure operation outside of the loop.

Note: This depends on the way closures are implemented. In the future clever compiling might make this unnecessary. (In Lua 5, every closure has its own "function environment" (i.e. globals). So even if there are no upvalues, the closure might not be the same every time it is created. --RiciLake)

Comments

In the following code...

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()

How many copies are created of the 'update' function at the marked line? One, shared among e1, e2, and e3, or three, one for each of e1, e2, and e3?

I am planning on doing this sort of thing quite a bit in my current Lua project and I'm concerned a bit about memory efficiency and such. -- NickDavies?

A closure is created in every function call to create() but this only creates one function. -- NickTrout

That seems a fairly definite statement that closures are not shared, even if they are the same (although at least the function code is shared). If efficiency is a problem then you'd be best off creating a single closure and passing it around. Eg:

-- 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

The key here is that (as far as I understand it) a closure is created each time the 'function' expression/statement is encountered. So if you only execute it once then you save memory duplication.

Binding a 'function' expression more than once is only needed if it wraps lexically scoped variables within its closure, otherwise it is inefficient (even if it might look neater).

Eg, if you had something like:

function create(init)

  local e = {}

  e.x = 0

  e.reset = function temp(self) self.x = init end

  return e

end

then you'd need the separate closure, since each separate closure instance refers to a different 'init' variable from the surrounding lexical scope!

So:

e1 = create(111)

e2 = create(222)

e1:reset() -- resets to '111'

e2:reset() -- resets to '222'.

-- PeterHill

It is worth saying that a closure does not take up much more space than a lexically bound variable. Personally, I would go for readability rather than efficiency in this case, because in the long run readability is efficiency. But if you really wanted to minimise allocations, you might want to write the above as follows:


do

  local reset



  function reset(self)

    self.x = self[reset]

  end



  function create(init)

    return {

      x = 0,

      [reset] = init,

      reset = reset

    }

  end

end

Here we use the fact that every closure is unique in order to use a unique key in the newly-created table, which won't interfere with any other key which might get created later. This not only avoids creating the closure, but also avoids creating the lexically bound variable. Of course, it does add a key-value pair to the table, but in most cases that will turn out to be free. If you were even worried about that (perhaps the object actually has exactly eight keys so the extra key will force it to have room for 16), you could put the constant reset function into a metatable:


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

Now, we use a table as a unique key. Personally I don't find this sort of thing attractive, because it is really hard to read, and it is generally better to hide internal variables in upvalues, where they are really hidden and access is faster. In this case, of course, it might not even be desirable to try to hide the init value; it could be part of the exposed object interface. The technique of putting common object functions in a metatable can save quite a bit of space and time if there are a lot of functions. -- RiciLake


RecentChanges · preferences
edit · history
Last edited August 4, 2007 8:04 pm GMT (diff)