Re: LuaJIT-friendly API and data structure design

  • From: Mike Pall <mike-1502@xxxxxxxxxx>
  • To: luajit@xxxxxxxxxxxxx
  • Date: Sun, 22 Feb 2015 18:21:50 +0100

Luke Gorrie wrote:
> Curious: How do we define "inner loop" for practical purposes?

Well, the innermost control flow construct that loops back at some
point. But please note, this may encompass multiple inlined functions.

> For example, which uses of 'p' would be considered inner loops here? (one,
> both, or unknown? suppose that x() and y() are not branchy.)
> 
> p = ...
> for i = 0, 100 do
>   x(p)
>   for j = 0, 100 do
>     y(p)
>   end
> end

y(p) is part of the innermost loop. So whatever dispatch or type
check overhead this implies, can usually be hoisted before the
inner loop, right after x(p) (I'm simplifying things here).

But x(p) plus the overhead of y(p) cannot be further hoisted out
of the outer loop in the current trace compiler architecture.

> It sounds like FFI methods would give us the benefit of local caching
> without the syntactic overhead? That sounds tempting..

Yes.

> For manual caching I am concerned about tiny decision overload and
> boilerplate. Tiny decision overload as in constantly wondering "is foo.bar
> worth caching in this module?" and boilerplate in the sense of copy/pasting
> a bunch of lines like these:
> 
> local bit_band, bit_bor, bit_lshift, bit_rshift = bit.band, bit.bor,
> bit.lshift, bit.rshift
> local packet_length, packet_data, packet_free = packet.length, packet.data,
> packet.free
> local link_empty, link_full, link_receive, link_transmit = link.empty,
> link.full, link.receive, link.transmit

Yes, I hate that, too. It's near the top of my list of things,
where Lua could really benefit from a change in the language
semantics.

I've previously thrown the idea around of repurposing the
(ill-conceived) default global scope to a kind of 'import scope'.
This would do automatic immutable imports of modules. E.g. this:

  local function foo(a, b)
    local y = math.sin(a) + math.cos(b)
    ...
  end

... could be automatically turned into something like that by the
bytecode compiler:

  -- BEGIN autogenerated code
  local INTERNAL_math = require("math")
  local INTERNAL_math_sin = INTERNAL_math.sin
  local INTERNAL_math_cos = INTERNAL_math.cos
  -- END autogenerated code

  local function foo(a, b)
    local y = INTERNAL_math_sin(a) + INTERNAL_math_cos(b)
    ...
  end

Of course, it would be implemented more efficiently internally.
E.g. every bytecode file would have an import table and maybe even
an export table. Some more details need to be worked out, too.

One interesting side effect is that every module then has an
(implicitly generated) list of dependencies, which would greatly
simplify code analysis tools, module distribution and application
deployment.

Most typos would end up referencing an undefined import and would
be caught statically (by a development tool or while loading the
module) and not dynamically at runtime, as it is now.

--Mike

Other related posts: