Page 1 of 1

Metatables, NOT

Posted: Tue Oct 11, 2016 7:04 pm
by ssilk
I come from this thread: viewtopic.php?f=25&t=33905


I have (not for everyone interesting) some results: I wanted to create an entity-manager with lua metatales. But before that I made some tests about performance.

First some code:

Implemenation with metatable:

Code: Select all

t={}  -- basetable
mt={} -- metatable
mt.mytab={}
mt.__newindex=function(t,k,v)
    rawset(mt.mytab, k, {val = v, valid = math.random()})
end
mt.__index=function(t,k)
    local tab = rawget(mt.mytab, k)
    if tab and tab.valid < 0.05 then
        return tab.val
    end
    return false
end
setmetatable(t,mt)

for i=1, 1000000 do
    t[math.random(1, 20000000)] = i
end

count = 0
for i=1, 20000000 do
    if t[i] then
        count = count + 1
    end
end

print(count)
Implemenation without metatable:

Code: Select all

bla = {}
bla.t={}  -- basetable
for i=1, 1000000 do
    bla.t[math.random(1, 20000000)] = {
        val = i,
        valid = math.random()
    }
end


count = 0
for i=1, 20000000 do
    if bla.t[i] and bla.t[i].valid < 0.05 then
        count = count + 1
    end
end

print(count)

With metatable: 6.161s

Without metatable: 3.557s

Conclusion: Metatables makes Lua slower by 100%.
Consequnce: No way to use metatables for a game as Factorio is. :)

Well, I think most of you did know that, but maybe for others it's new. So I wanted that to share with you

Re: Metatables, NOT

Posted: Tue Oct 11, 2016 8:13 pm
by Adil
Now try that with some api-calls in the cycle.
Metatable is one additional pointer redirection, and, in your example, two additional function calls per step in each cycle.
When you add function calls to the straightforward imperative implementation the gap is starting to decrease:
code
Also, math.rand() is a rather odd sighting in the benchmark.

With those remarks in mind, if you yarn for performance, indeed you should try express all your code without additional functions, however, it probably won't matter much for anywhere except the on_tick event.
And yes, fancy operator overloading is not a good idea.

Addendum:
ssilk wrote: Well, I think most of you did know that, but maybe for others it's new. So I wanted that to share with you
Apparently, Choumiko didn't know that. :P

Re: Metatables, NOT

Posted: Tue Oct 11, 2016 9:36 pm
by aubergine18
Adil wrote:And yes, fancy operator overloading is not a good idea.
http://lua-api.factorio.com/latest/LuaC ... ustomTable

It's a great idea, if it's done properly in the right context. It always depends on the specific nature of the code, how often it's called, etc.

Re: Metatables, NOT

Posted: Wed Oct 12, 2016 2:21 am
by Afforess
Metatables are just a poor idea because they aren't serialized. That one issue alone makes them not worth the hassle in most mods. Worse performance is just insult to injury.

Re: Metatables, NOT

Posted: Wed Oct 12, 2016 2:50 am
by aubergine18
Afforess wrote:Metatables are just a poor idea because they aren't serialized. That one issue alone makes them not worth the hassle in most mods. Worse performance is just insult to injury.
It is trivial one line of code to re-apply the metatable, there's even an event specifically aimed at enabling that: script.on_load().

Their performance for the most part is not an issue - it's only if you start using them (or anything else for that matter) in an on_tick event handler. But for all other events, particularly if only accessing the metamethods sparingly, there's really nothing bad about them. Even in situations where they are used extensively, you'd still get better performance improvements, in most cases, by refining the code they contain rather than the metamethods themselves.

The performance stats in OP are also somewhat skewed IMO. Local references to tables and functions would improve performance (although local ref to math.random could cause desyncs, at least in older versions of factorio it did). There's also no need to use rawget in that particular example, there's always alternate ways to implement things.

Re: Metatables, NOT

Posted: Wed Oct 12, 2016 4:23 pm
by Rseding91
aubergine18 wrote:... (although local ref to math.random could cause desyncs, at least in older versions of factorio it did).
That won't cause desyncs. I don't think it ever has...

Re: Metatables, NOT

Posted: Fri Oct 14, 2016 5:57 am
by Daid
Your test isn't just testing metatables or not. It's also testing function calls vs inline code. And with so little things to do, inline code is always a lot faster.

Your code with the metatable is also wrong, as it sets the data on the metatable, which means you cannot share the metatable on multiple tables, which is an important idea of the metatables.

If you add back the function call overhead:

Code: Select all

t={}  -- basetable
function set(t,k,v)
    rawset(t, k, {val = v, valid = math.random()})
end
function check(t,k)
    local tab = rawget(t, k)
    if tab and tab.valid < 0.05 then
        return tab.val
    end
    return false
end

for i=1, 1000000 do
    set(t, math.random(1, 20000000), i)
end

count = 0
for i=1, 20000000 do
    if check(t, i) then
        count = count + 1
    end
end

print(count)
I see 6.92 seconds for the metatable version and 5.99 for the function version.


Also, in 99% of your code, performance is not the issue.

Re: Metatables, NOT

Posted: Mon Oct 24, 2016 9:14 pm
by ssilk
Thanks for answering. I forgot to read the board.
Daid wrote:Your test isn't just testing metatables or not. It's also testing function calls vs inline code. And with so little things to do, inline code is always a lot faster.
Of course. That's the point of that tests: Using of some kind of "clean" concept, vs. ugly, but fast access.
Your code with the metatable is also wrong, as it sets the data on the metatable, which means you cannot share the metatable on multiple tables, which is an important idea of the metatables.
Hm, ah, good to know, that this works also like so. But I thought the point of this is, that I don't need to use function and use the metatable as some kind of "trigger".
Also, in 99% of your code, performance is not the issue.
Running through thousands of entries each tick is. :)

Re: Metatables, NOT

Posted: Thu Oct 27, 2016 9:14 pm
by Olipro
Doing a quick test of 10M iterations, calling a function that's directly in a table took about 0.55s. This then increased to 0.66 when performed on an empty table with its __index metamethod set to another table. Making __index a function that performed the table load and returned it resulted in the total time going to just over 1s.

So very unscientifically, it's costing my test system about 10ns per function call for the worst case - which if we assume that you're doing maybe 10 function calls on a game tick that's running at the worst case of every 1/60th of a second, the cost of your function calls is running you the tiniest fraction of the available time between game ticks.

If using metatables enables you to write better, more maintainable code, just use them rather than worrying about premature optimisation such as this.