由于这个问题(以及LJ总体)给我带来了巨大的痛苦,我想在这里提供一些额外的信息,希望能帮助未来的某个人。
“回调”并不总是慢
LuaJIT FFI文档中所说的“回调很慢”是指通过FFI将由LuaJIT创建并传递给期望函数指针的C函数的回调。这与其他回调机制完全不同,特别是与使用API调用回调的标准lua_CFunction相比,具有完全不同的性能特征。
因此,真正的问题是:我们何时使用Lua C API实现涉及pcall等内容的逻辑,而不是将所有内容保留在Lua中?始终如一地考虑性能,但尤其是在跟踪JIT的情况下,必须进行分析(-jp)以了解答案。无论如何,必须进行性能分析。
我曾经遇到过看起来相似但性能却截然不同的情况;也就是说,我遇到过代码(不是玩具代码,而是在编写高性能游戏引擎的上下文中的生产代码),当仅结构化为Lua时表现更好,以及代码(看起来结构相似)在介绍调用lua_CFunction时表现更好,该函数使用luaL_ref来维护对回调和回调参数的句柄。
优化LuaJIT而没有测量是愚蠢的行为
跟踪JIT已经很难理解了,即使您是静态语言性能分析方面的专家。它会打破您对性能的所有认识。如果将记录的IR编译而不是编译函数的概念已经摧毁了一个人对LuaJIT性能的理解力,那么通过FFI调用C基本上是免费的(当成功JITed时),但在解释时可能比等效的lua_CFunction调用昂贵一个数量级……这肯定会将情况推向极端。
具体而言,您上周写的系统远远优于C等效系统,但由于在与该系统相邻的位置引入了NYI,因此本周可能会崩溃,这可能来自看似无关的代码区域,现在您的系统正在回退并且性能完全被摧毁。更糟糕的是,也许您非常清楚什么是NYI,但是您添加了足够的代码以使其超过JIT的最大记录IR指令、最大虚拟寄存器、调用深度、展开因子、侧面跟踪限制等。
需要注意的是,虽然“空”基准有时可以提供非常一般的见解,但更重要的是在LJ(由于上述原因)中,代码应该在 上下文 中进行分析。编写LuaJIT的代表性性能基准非常困难,因为跟踪本质上是非局部的。在使用LJ的大型应用程序中,这些非局部交互会产生巨大的影响。
TL;DR
地球上只有一个人真正了解LuaJIT的行为。他的名字叫Mike Pall。
如果你不是Mike Pall,请不要假设关于LJ的行为和性能的任何内容。使用-jv(详细模式;查看NYIs和fallbacks),-jp(分析器!与jit.zone组合使用以进行自定义注释;使用-jp=vf查看由于fallbacks而导致的解释器花费的时间百分比),当您 需要真正知道发生了什么时,使用-jdump(跟踪IR& ASM)。测量、测量、测量。除非来自该人或您已在特定使用情况下测量过它们(在这种情况下,它不是一般化),否则不要轻信LJ性能特征的概括。并且请记住,正确的解决方案可能全部使用Lua,也可能全部使用C,也可能是Lua->C通过FFI,也可能是Lua->lua_CFunction->Lua,......你明白了。
从一个多次被愚弄而认为自己已经理解了LuaJIT的人的角度来看,接下来的一周就被证明是错误的,我真诚地希望这些信息对某个人有所帮助:) 个人而言,我不再对LuaJIT进行“有根据的猜测”。我的引擎每次运行都会输出jv和jp日志,对于优化而言,它们是“上帝的话语”。