[Date Prev][Date Next][Thread Prev][Thread Next]
[Date Index]
[Thread Index]
- Subject: Re: Lua library api style recommendations
- From: Jorge Visca <xxopxe@...>
- Date: Fri, 5 May 2023 18:14:23 -0300
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]