7

LuaJIT FFI 文档提到从 C 调用回 Lua 代码相对较慢,建议尽可能避免使用它:

不要对性能敏感的工作使用回调:例如考虑一个数值积分例程,它需要一个用户定义的函数来积分。从 C 代码调用用户定义的 Lua 函数数百万次是个坏主意。回调开销对性能绝对有害。

对于新设计,请避免使用推送式 API(C 函数为每个结果重复调用回调)。而是使用拉式 API(重复调用 C 函数以获得新结果)。通过 FFI 从 Lua 到 C 的调用比反过来要快得多。大多数设计良好的库已经使用拉式 API(读/写、获取/放置)。

但是,它们并没有说明来自 C 的回调有多慢。如果我有一些我想加速使用回调的代码,如果我重写它以使用拉式 API,我大概可以期望多少加速?有没有人有任何基准比较使用每种 API 风格的等效功能的实现?

4

4 回答 4

10

在我的计算机上,从 LuaJIT 到 C 的函数调用有 5 个时钟周期的开销(值得注意的是,与通过普通 C 中的函数指针调用函数一样快),而从 C 调用回 Lua 有 135 个周期的开销,慢 27 倍。话虽如此,需要从 C 到 Lua 的一百万次调用的程序只会给程序的运行时增加约 100 毫秒的开销;虽然在主要对缓存内数据进行操作的紧密循环中避免 FFI 回调可能是值得的,但如果调用回调的开销,例如,每个 I/O 操作一次,与I/O 本身的开销。

$ luajit-2.0.0-beta10 callback-bench.lua   
C into C          3.344 nsec/call
Lua into C        3.345 nsec/call
C into Lua       75.386 nsec/call
Lua into Lua      0.557 nsec/call
C empty loop      0.557 nsec/call
Lua empty loop    0.557 nsec/call

$ sysctl -n machdep.cpu.brand_string         
Intel(R) Core(TM) i5-3427U CPU @ 1.80GHz

基准代码:https ://gist.github.com/3726661

于 2012-09-15T06:59:56.093 回答
7

两年后,我根据Miles 的回答重新制定了基准,原因如下:

  1. 看看他们是否随着新的进步而有所改进(在 CPU 和 LuaJIT 中)
  2. 为带有参数和返回的函数添加测试。回调文档提到,除了调用开销之外,参数编组也很重要:

    [...] C 到 Lua 的转换本身具有不可避免的成本,类似于 lua_call() 或 lua_pcall()。参数和结果编组增加了成本 [...]

  3. 检查 PUSH 样式和 PULL 样式之间的区别。

我的结果,在Intel(R) Core(TM) i7 CPU 920 @ 2.67GHz

operation                  reps     time(s) nsec/call
C into Lua set_v          10000000  0.498    49.817
C into Lua set_i          10000000  0.662    66.249
C into Lua set_d          10000000  0.681    68.143
C into Lua get_i          10000000  0.633    63.272
C into Lua get_d          10000000  0.650    64.990
Lua into C call(void)    100000000  0.381     3.807
Lua into C call(int)     100000000  0.381     3.815
Lua into C call(double)  100000000  0.415     4.154
Lua into Lua             100000000  0.104     1.039
C empty loop            1000000000  0.695     0.695
Lua empty loop          1000000000  0.693     0.693

PUSH style               1000000    0.158   158.256
PULL style               1000000    0.207   207.297

这个结果的代码在这里

结论:当与参数一起使用时,Lua 中的 C 回调具有非常大的开销(您几乎总是这样做),因此它们真的不应该在关键点使用。不过,您可以将它们用于 IO 或用户输入。

我有点惊讶 PUSH/PULL 样式之间的差异如此之小,但也许我的实现并不是最好的。

于 2014-11-27T18:08:08.447 回答
7

由于这个问题(以及整个 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 日志,它们是我在优化方面的“上帝之言”。

于 2017-11-29T19:52:31.927 回答
5

如以下结果所示,存在显着的性能差异:

LuaJIT 2.0.0-beta10 (Windows x64)
JIT: ON CMOV SSE2 SSE3 SSE4.1 fold cse dce fwd dse narrow loop abc sink fuse
n          Push Time        Pull Time        Push Mem         Pull Mem
256        0.000333         0                68               64
4096       0.002999         0.001333         188              124
65536      0.037999         0.017333         2108             1084
1048576    0.588333         0.255            32828            16444
16777216   9.535666         4.282999         524348           262204

可以在此处找到此基准测试的代码。

于 2012-09-08T11:05:19.373 回答