Conversation
Under the luajit features, luaL_traceback resolved to the lua-compat-5.3 port, whose compat53_findfield walks every global and every field of every global table to name a frame. mlua builds a traceback for every callback error, so a pcall'd error on a VM with large global tables cost hundreds of microseconds. LuaJIT exports luaL_traceback itself (since before 2.0.0) and names frames from the call site without searching. Declare the native symbol under the luajit feature and compile the port and its helpers only for Lua 5.1.
Author
|
Ran into this when i was seeing huge variability in test run times, a specific test case with that was sometimes a few seconds, others 1.5 minutes. /cc ross/logit#499 |
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Under the
luajitandluajit52features,luaL_tracebackresolves to the lua-compat-5.3 portin
mlua-sys/src/lua51/compat.rs. LuaJIT exports its own, and the port's cost grows with thesize of the script's global tables. mlua builds a traceback for every callback error and every
error that escapes to Rust, including errors a script catches with
pcall, so ordinary errorhandling pays it.
This PR binds LuaJIT's
luaL_tracebackand compiles the port only for Lua 5.1.Why the port is slow
To name a frame,
compat53_pushfuncnamecallscompat53_findfieldover_Gto depth 2: everyglobal and every field of every global table. On
mainit tries the global name first for everyframe, so every traceback walks
_G, and a frame that isn't a global function (the main chunk,for one) walks all of it. Lua 5.4 searches
package.loadedinstead. LuaJIT searches nothing: itprints the call-site name, or the C function's address.
Measurements
Per caught error, min to max over 24 fresh VMs, each holding a 20,000-entry global table. Release
build, LuaJIT 2.1 from
luajit-src, x86-64 Linux.rust.refuseis a Rust function that returnsErr.mlua
main(mlua-sys 0.13.0):luaL_tracebackpcall(error, "x")(control, no callback)pcall(rust.refuse)pcall(function() rust.refuse() end)mlua 0.9.9 (mlua-sys 0.6.8):
luaL_tracebackpcall(error, "x")(control, no callback)pcall(rust.refuse)__newindexmetamethodThe control row's spread comes from the host's mixed core types.
Change
lua51/lauxlib.rs: declareluaL_tracebackin theexternblock under#[cfg(feature = "luajit")]. The signature matches the Lua 5.2-5.4 declarations, so no callerchanges.
lua51/compat.rs: put#[cfg(not(feature = "luajit"))]on the port'sluaL_traceback, thehelpers only it uses (
compat53_countlevels,compat53_findfield,compat53_pushglobalfuncname,compat53_pushfuncname,COMPAT53_LEVELS1,COMPAT53_LEVELS2), and theCStrandmemimports.Compatibility
LuaJIT versions. LuaJIT added
luaL_tracebackbefore 2.0.0 (commit "From Lua 5.2: AddluaL_traceback().", 2012-10-01,
v2.0branch), so every versionbuild/find_normal.rsacceptsfrom
pkg-config(2.0.4 to 2.2) exports it, as does theluajit-src2.1 vendored build.Stack use. LuaJIT grows the stack as it pushes and concatenates every 15 pieces, so
LUA_TRACEBACK_STACKstill covers it.Traceback text. Output follows LuaJIT's format, the one
debug.tracebackalready uses underLuaJIT. Code that parses traceback text for a qualified name gets the call-site name instead:
main)[C]: in function 'rust.refuse'if found in_G, else[C]: in ?[C]: at 0x55cd55a80520[C]: in function 'NAME'if found in_G, else[C]: in ?[builtin#N]:[C]: in function 'table.sort'[C]: in function 'sort'in local 'f',in method 'm'in function 'f'Testing
main:cargo test --features luajit,vendored,async,serde,macrospasses before and after (358passed, 8 ignored).
cargo clippyonmlua-sysis clean underluajit,vendored,luajit52,vendored, andlua51,vendored.[patch.crates-io]passes a downstreamapplication's 323 tests, including ones that assert on traceback content.
🤖 Generated with Claude Code