So... I got the trace log thingie going, and it provided a little insight. There were a bunch of aborted traces with fallbacks to the interpreter, even though there is not a single NYI call in the critical parts. I could eliminate all of these aborts by tweaking JIT options, cranking them up to much higher values.
I now have this in my conf.lua:
Code: Select all
local JIT_MAGIC = 5000
jit.opt.start(3,
'maxtrace=' .. JIT_MAGIC,
'maxrecord=' .. JIT_MAGIC * 4,
'maxirconst=' .. JIT_MAGIC * 4,
'maxside=' .. JIT_MAGIC,
'maxsnap=' .. JIT_MAGIC,
'maxmcode=' .. JIT_MAGIC * 4,
'maxside=' .. JIT_MAGIC,
'tryside=' .. JIT_MAGIC,
'maxsnap=' .. JIT_MAGIC,
'instunroll=' .. JIT_MAGIC,
'loopunroll=' .. JIT_MAGIC
)
The code runs well enough with these options, but there is still the occasional slowdown, and sometimes it's just crawling at 5% of the usual speed right from the start.
The GC counter is still spinning. Which is especially frustrating because I took great care to avoid garbage buildup, as well as small loops and branches.
What's left is aborts labeled as "too many snapshots". I couldn't get rid of those, no matter how large the maxsnap option was. And "leaving loop in root trace", no idea what to do about these. They seem to be a normal occurence. "loop unroll limit reached" was another abort reason. Seems harmless enough, and there were only two of them. There are not many loops in the code anyway.
And as previously mentioned, I invented a new tool to fight slowdowns: the no-op loop.
Code: Select all
for _ = 1, 100 do value = value end
I have two loops like this at random places in the code to prevent catastrophic slowdowns. I would love to understand what the hell is going on there. I wanted to isolate a test case but there's too much code to remove and too little determinism.