lua-users home
lua-l archive

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



Interesting discussion. I have been looking at ways to implement the "visitor" pattern in Lua, and my benchmarks look pretty similar.

Quinn's formulation:

>    function traverse(N)
>        return traversal_table[N:GetRuleName()](N);
>    end

obviously improves readability, but at the expense of an extra function call; in a simple evaluation benchmark (code is at http://lua-users.org/files/wiki_insecure/users/rici/traverse.lua), manually inlining this function call gave a saving of about 20%.

One can also give traversal_table a __call metamethod, and thereby avoid having two objects to deal with; this might or might not be worth the extra 5% cost.

A functional tuples-like implementation is almost as fast as the inlined traverse-call, and almost as readable as the standard traverse function, so it might be worthwhile. I did not try benchmarking with tuples-in-c, as per the discussion from last month, but I suspect it would slightly speed up the functional tuples implementation, making that the fastest.

The careful reader will notice that the standard OO-with-metatables pattern is not part of this benchmark. It didn't satisfy one of my requirements, which is that you should be able to implement new temporary visitors without namespace issues.

The benchmark is runnable from the above URL, but to give a feeling for how the code looks (the names refer to the benchmark description below), these are the first couple of lines of each mechanism, dealing with the "plus" operator:

"switcher":
  local eval, evaltab = switcher"eval"
  function evaltab:plus()  return eval(self.a) + eval(self.b) end

"ugly switcher":
  local evaltab = errtable"eval"
  function evaltab:plus()
    local a, b = self.a, self.b
    return evaltab[a.op](a) + evaltab[b.op](b)
  end

"metatable" (here self refers to the visitor object, not the visited object)
  evalmeta = traverser"eval"
  function evalmeta:plus(obj)  return self(obj.a) + self(obj.b) end

"if-then-else"
  local function eval(self)
    local op = self.op
    if       op == "plus"  then return eval(self.a) + eval(self.b)

"functional tuples":
  local feval = errtable"eval"
  function feval.plus(a, b)  return a(feval) + b(feval) end

And benchmark results on one of my machines (the other one is consistent, but slower):

rlake $ lua traverse.lua 1000000
These should all look the same:
(set c (+ (/ 21 a) (* b (- 10 (_ b))))) 42
(set c (+ (/ 21 a) (* b (- 10 (_ b))))) 42
(set c (+ (/ 21 a) (* b (- 10 (_ b))))) 42
(set c (+ (/ 21 a) (* b (- 10 (_ b))))) 42
(set c (+ (/ 21 a) (* b (- 10 (_ b))))) 42

Evaluate the _expression_ 1000000 times
using switcher took 11.48 seconds
using ugly switcher took 9.02 seconds
using metatable took 12.09 seconds
using if-then-else took 11.85 seconds
using functionals took 9.38 seconds