lua-users home
lua-l archive

[Date Prev][Date Next][Thread Prev][Thread Next] [Date Index] [Thread Index]


Hi Everyone,

I have some questions about function/closure construction overhead and how to most efficiently define OO "member functions". I am working on some micro-benchmarks to help inform my decisions but so far the results have been inconclusive so I'd like to ask some questions.

Ultimately these questions are about me trying to get a better intuition about how to write efficient Lua code. I've given more background below if you're interested, but the questions stand alone.

<background>
I'm trying to wrap my head around the best way to implement some OO class constructor functionality for small classes.

In my situation there are many different "subclasses" but each object instance has only 3 member methods and a few instance variables. Often 2 of the methods are no-ops. Sometimes there is no class state, or it is used only by one method. Sometimes the methods execute lambdas passed as constructor parameters. So I am considering when and how to use closures vs metatables vs assigning the methods directly into each instance table.

Sometimes it seems like it would be most concise to declare all the functions inline in my constructor, but I fear this will be more expensive than declaring member functions up-front globally, or in a global table and then assigning them in to each instance.

I have around 100 of these classes to implement so I'd prefer to work out the correct implementation approach before I start.

In the end, object method execution speed is more important that construction speed.
</background>

So now the specific questions:

1.

Aside from the visibility of __y_bar below, do the two functions X, Y do the same thing? or do X (and Z) construct a new closure every time they are called? (even though their inner functions don't capture any variables from the enclosing scope)

function X()
  return { foo = function(self) print "x" end }
end
X():foo()

function __y_bar(self) print "y" end
function Y()
  return { foo = __y_bar }
end
Y():foo()


I also tried this:

function Z()
  local function __z_bar(self) print "z" end
  return { foo = __z_bar }
end
Z():foo()

Z runs slower than X or Y, suggesting that there is some runtime overhead in creating a named local variable (which surprises me, seems like a simple optimisation).

Does the ''function() print "z" end'' bit get evaluated every time Z is called? or is it "precompiled" so that assigning a local function like this is no different that assigning from the global scope as in:
local __z_bar = math.sin


1a.

Can I assume that the following generate exactly the same code? or are they subtly different?

r = {}
function r:foo() end

r = { foo = function(self) end }

local function _foo( self ) end
r = { foo = _foo }
-- no further use of _foo

local _foo = function(self) end
r = { foo = _foo }
-- no further use of _foo


2.

Another question is about capturing "instance state". Consider the constructors below. Assuming that my instance state will be only used by a single method foo(), would I be better off storing it in the self table, or capturing it with a closure? Are there space/time trade offs involved? What are they?

-- A: state x captured in closure
function ClassA( x )
  return { foo = function(self) print(x) end }
end
a = ClassA(1)
a:foo()


-- B: state x stored in object field,
-- function defined inline in every call to constructor
function ClassB( x )
  return { x_ = x,
    foo = function(self) print(self.x_) end }
end
b = ClassB(1)
b:foo()

Initial tests suggest that a:foo() is a little faster, and the ClassB() constructor execution is slower, presumably because it's more expensive to add a table entry ( x_ in ClassB() ) than capturing an upvalue into a closure (x in ClassA()).

I also wonder about memory usage: these are small "objects". The tables will usually have 3-6 fields. Is the memory overhead likely to be greater capturing a few upvalues in a closure, or using additional table fields?

I'm also considering using the standard "class object metatable" approach. I'm considering other options because I don't think metatable will be as concise, and sometimes I may want to construct per-instance closures with state (as above).


Sorry for the long message. Thanks for your attention.

Ross.