由于这个问题(以及整个 LJ)一直是我痛苦的根源,因此我想在戒指中提供一些额外的信息,希望它可以帮助将来的人。
“回调”并不总是很慢
LuaJIT FFI 文档中,当它说“回调很慢”时,非常具体指的是由 LuaJIT 创建的回调并通过 FFI 传递给需要函数指针的 C 函数的情况.这与其他回调机制完全不同,特别是与调用使用 API 调用回调的标准 lua_CFunction 相比,它具有完全不同的性能特征。
话虽如此,真正的问题是:我们什么时候使用 Lua C API 来实现涉及 pcall 等的逻辑,而不是把所有东西都保存在 Lua 中?与性能一样,尤其是在跟踪 JIT 的情况下,必须配置文件 (-jp) 才能知道答案。期间。
我见过看起来相似但性能范围相反的情况;也就是说,我遇到过代码(不是玩具代码,而是在编写高性能游戏引擎的上下文中的生产代码),当结构化为仅 Lua 时性能更好,以及代码(似乎 em> 结构相似)通过调用 lua_CFunction 引入语言边界时性能更好,该 lua_CFunction 使用 luaL_ref 来维护回调和回调参数的句柄。
在没有测量的情况下优化 LuaJIT 是一件傻事
跟踪 JIT 已经很难推理,即使您是静态语言性能分析方面的专家。他们把你认为你知道的关于性能的一切都拿走了,然后把它粉碎了。如果编译记录的 IR 而不是编译函数的概念还没有消除一个人推理 LuaJIT 性能的能力,那么通过 FFI 调用 C 的事实在成功 JIT 时或多或少是免费的,但可能是一个命令-解释时比等效的 lua_CFunction 调用更昂贵...嗯,这肯定会将情况推到边缘。
具体来说,你上周编写的一个性能大大优于 C 等效的系统本周可能会失败,因为你在所述系统中引入了一个 NYI,它很可能来自一个看似正交的代码区域,现在您的系统正在回退并抹杀性能。更糟糕的是,也许您很清楚什么是 NYI,什么不是 NYI,但是您在跟踪接近度中添加了足够代码,它超过了 JIT 记录的最大 IR 指令、最大虚拟寄存器, 调用深度, 展开因子, side trace limit...等。
另外,请注意,虽然“空”基准有时可以提供非常笼统的见解,但对于 LJ(出于上述原因)更重要的是,在上下文中对代码进行分析。为 LuaJIT 编写具有代表性的性能基准非常非常困难,因为就其性质而言,跟踪是非本地的。在大型应用程序中使用 LJ 时,这些非本地交互会产生巨大影响。
TL;DR
这个星球上确实有 一个 人真正了解 LuaJIT 的行为。他叫迈克·帕尔。
如果您不是 Mike Pall,不要对 LJ 的行为和表现做出任何假设。使用 -jv(详细;注意 NYI 和后备),-jp(分析器!结合 jit.zone 进行自定义注释;使用 -jp=vf 查看什么 %由于后备,您的大部分时间都花在了解释器上),并且,当您真的需要知道发生了什么时,-jdump(跟踪 IR 和 ASM)。测量,测量,测量。对 LJ 性能特征持保留态度,除非它们来自该人本人,或者您已经在您的特定用例中对其进行了测量(毕竟,在这种情况下,这不是一种概括)。请记住,正确的解决方案可能全部在 Lua 中,也可能全部在 C 中,可能是 Lua -> C 到 FFI,也可能是 Lua -> lua_CFunction -> Lua,......你明白了。
来自一个一次又一次被愚弄以为他已经理解 LuaJIT 的人,但在接下来的一周被证明是错误的,我真诚地希望这些信息可以帮助那里的人 :) 就个人而言,我根本没有不再对 LuaJIT 进行“有根据的猜测”。我的引擎每次运行都会输出 jv 和 jp 日志,它们对我来说是优化方面的“上帝之言”。