由于这个问题(以及整个 LJ)对我来说一直是痛苦的根源,所以我想在戒指中添加一些额外的信息,希望它可以帮助将来的人。
“回调”并不总是很慢
LuaJIT FFI 文档,当它说“回调很慢”时,非常具体地指的是由 LuaJIT 创建并通过 FFI 传递给需要函数指针的 C 函数的回调的情况。这与其他回调机制完全不同,特别是与调用使用API调用回调的标准lua_CFunction相比,它具有完全不同的性能特征。
话虽如此,那么真正的问题是:我们什么时候使用 Lua C API 来实现涉及 pcall 等的逻辑,而不是把所有东西都保存在 Lua 中?与性能一样,但特别是在跟踪 JIT 的情况下,必须配置文件(-jp) 才能知道答案。时期。
我见过看起来相似但又落在性能范围两端的情况;也就是说,我遇到过代码(不是玩具代码,而是在编写高性能游戏引擎的上下文中的生产代码)在仅构建为 Lua 时性能更好,以及代码(看起来结构相似)通过调用 lua_CFunction 引入语言边界时性能更好,该 lua_CFunction 使用 luaL_ref 来维护回调和回调参数的句柄。
在没有测量的情况下优化 LuaJIT 是一件傻事
即使您是静态语言性能分析方面的专家,跟踪 JIT 已经很难推理。他们把你认为你知道的关于性能的一切都拿走了,然后把它粉碎了。如果编译记录的 IR 而不是编译函数的概念还没有消除一个人推理 LuaJIT 性能的能力,那么通过 FFI 调用 C 的事实在成功 JIT 时或多或少是免费的,但可能是一个命令-解释时比等效的 lua_CFunction 调用更昂贵...嗯,这肯定会将情况推到边缘。
具体来说,你上周编写的一个性能大大优于 C 等效的系统本周可能会失败,因为你在所述系统中引入了一个 NYI,它很可能来自一个看似正交的代码区域,而现在您的系统正在回退并破坏性能。更糟糕的是,也许您很清楚什么是 NYI,什么不是 NYI,但是您在跟踪接近度中添加了足够的代码,以至于它超过了 JIT 记录的最大 IR 指令、最大虚拟寄存器、调用深度、展开因子,侧迹限制...等。
另外,请注意,虽然“空”基准有时可以提供非常笼统的见解,但对于 LJ(出于上述原因),在 context 中对代码进行分析更为重要。为 LuaJIT 编写具有代表性的性能基准非常非常困难,因为就其性质而言,跟踪是非本地的。在大型应用程序中使用 LJ 时,这些非本地交互会产生巨大的影响。
TL;博士
这个星球上只有一个人真正了解 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 日志,它们是我在优化方面的“上帝之言”。