lua-users home
lua-l archive

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


On 5/5/23 13:37, nobody wrote:

...
  m.add = function( a, b,   c )
    if not c then  c = m.new( )  end
    -- now just assume that c exists
    c.n = a.n + b.n
    return c
  end
...

Yes, I've gravitated to this style myself when using "out" variables, because it does look most idiomatic (usually written "c = c or m.new()" ). It does incur in a extra assignment inside the default constructor just to be overwritten right next. Probably not an issue with a JIT as you say.



End result is that I can either use the functions in a "don't care about low-level efficiency stuff" way…

(snip)

…or in a precise way that avoids allocations wherever possible and looks a bit weird & verbose like asm:

  (snip)

This approach to provide separate user experiences result very natural when using separate api entries. If you have an "add" and a "set_add" methods, pointing the __add metamethod to "add" is very clear and compact, and does just what is supposed to do.

You can also say that while the "out" parameter style is asm inspired, the add/set_add style is somewhat like C++'s  operator pairs ( + vs += etc). I suppose the best option finally depends on how the library is used and its userbase.


(and LuaJIT can turn that into _crazy_ fast code! I once wrote a fairly simple ray-tracer in Lua/JIT and meticulously recycled coordinates/vectors/colors, and in the end it was faster than the C version.

This is my experience too, processing big datastructures.


Jorge
On 5/5/23 13:37, nobody wrote:
On 2023-05-05 17:09, Jorge Visca wrote:
And another alternative, with optional out parameter:

  m.add = function ( a, b, out )
    if out then
      out.n = a.n + b.n
      return out -- for concatenating calls
    else
      return m.new{ n=a.n+b.n }
    end
  end

The optional output variant can look rather assembly-like, and I've used it repeatedly in the past. I'd say don't worry about the `if`, especially if you care enough about speed that you fall back to LuaJIT (which will generally just get rid of that entirely.)

My style of writing these tends to be

  m.add = function( a, b,   c )
    if not c then  c = m.new( )  end
    -- now just assume that c exists
    c.n = a.n + b.n
    return c
  end

(otherwise you'll duplicate the "real" code in both branches…) If I *must* know data about the content for the constructor, I'll use a separate `local` function that does the main computations and then passes the results in a shape that I can pass to the ctor, in which case the duplication is avoided again. (In that case I also tend to have an `update` or `set` function, that takes args like the ctor and just reuses an existing value.)

End result is that I can either use the functions in a "don't care about low-level efficiency stuff" way…

  function foo( a, b )
    local c = (a + b) + a
    return c + c
  end

…or in a precise way that avoids allocations wherever possible and looks a bit weird & verbose like asm:

  function foo( a, b,   c )
    if not c then  c = m.new( )  end
    local d = tmp( m ) -- if it's worth keeping a cache of temp variables by type, else just m.new( )
    m.add( a, b,   c )
    m.add( c, a,   d )
    m.add( d, d,   c )
    release( d ) -- return to the cache, if you forget it just gets GC'd eventually
    return c
  end

(and LuaJIT can turn that into _crazy_ fast code! I once wrote a fairly simple ray-tracer in Lua/JIT and meticulously recycled coordinates/vectors/colors, and in the end it was faster than the C version. A good part of that's probably attributable to the C version having more features, but it's still a good example for LuaJIT being able to optimize this really really well.)

-- nobody

[code is unchecked, just written directly in the mail client… but hopefully correct enough to get the ideas across]