LuaJIT FFI回调性能

时间:2012-09-08 08:09:40

标签: callback ffi luajit

LuaJIT FFI docs提到从C调用Lua代码的速度相对较慢,建议尽可能避免使用它:

  

不要将回调用于对性能敏感的工作:例如考虑一个数值集成例程,它采用用户定义的函数进行集成。从C代码调用用户定义的Lua函数数百万次是一个坏主意。回调开销对性能绝对不利。

     

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

然而,他们并没有给出任何来自C的回调次数多少的感觉。如果我有一些我希望加速使用回调的代码,那么如果我重新编写它以使用拉式API,我可以期待多少加速?有没有人有任何基准来比较使用每种API的等效功能的实现?

4 个答案:

答案 0 :(得分:6)

在我的计算机上,从LuaJIT到C的函数调用有5个时钟周期的开销(特别是,与通过普通C中的函数指针调用函数一样快),而从C调用回Lua的函数有135个循环开销,慢27倍。话虽这么说,需要从C进入Lua的百万次调用的程序只会给程序的运行时增加约100ms的开销;尽管在一个紧密循环中避免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

答案 1 :(得分:4)

这些结果显示出显着的性能差异:

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

可以找到此基准的代码here

答案 2 :(得分:4)

两年后,我从Miles' answer重新编写基准,原因如下:

  1. 看看他们是否通过新进展(在CPU和LuaJIT中)得到了改进
  2. 为具有参数和返回的函数添加测试。 The callback documentation提到除了呼叫开销之外,参数编组也很重要:
      

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

  3. 检查PUSH样式和PULL样式之间的区别。
  4. 我的结果,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
    

    此结果的代码为here

    结论:与参数一起使用时,进入Lua的C回调有很大的开销(这是你几乎总是这样做的),所以它们真的不应该用在关键点上。您可以将它们用于IO或用户输入。

    我有点惊讶PUSH / PULL风格之间差别不大,但也许我的实施并不是最好的。

答案 3 :(得分:2)

由于这个问题(以及一般的LJ)一直是我痛苦的根源,我想在戒指中加入一些额外的信息,希望将来可以帮助那些人。

'回调'并不总是很慢

LuaJIT FFI文档,当它说回调很慢时,'将非常具体地引用到由LuaJIT创建的回调的情况,并通过FFI传递给期望函数指针的C函数。这与其他回调机制完全不同,特别是与调用使用API​​调用回调的标准lua_CFunction相比,它具有完全不同的性能特征。

话虽如此,真正的问题是:我们何时使用Lua C API来实现涉及pcall等的逻辑,而不是保留Lua中的所有内容?与性能一样,但尤其是在跟踪JIT 的情况下,一个必须分析( - jp)才能知道答案。周期。

我看到类似的情况却落在了性能范围的两端;也就是说,我遇到了代码(不是玩具代码,而是在编写高性能游戏引擎的上下文中的生产代码),当结构化为Lua-only以及代码(似乎结构相似。

在没有测量的情况下优化LuaJIT是一个愚蠢的差事

即使您是静态语言性能分析专家,追踪JIT也很难理解。他们会把您认为对性能有所了解的所有内容都打包成碎片。如果编译记录的IR而不是编译函数的概念并没有消除推理LuaJIT性能的能力,那么当成功JIT时,通过FFI调用C的事实或多或少是免费的。但是,当解释时,它可能比等效的lua_CFunction调用更加昂贵......好吧,这肯定会推动这种情况超越边缘。

具体来说,你上周写的一个大大超过C等价物的系统本周可能因为你引入了一个跟踪所述系统的NYI,这可能来自一个看似正交的代码区域,现在你的系统正在退缩并消除性能。更糟糕的是,也许您已经清楚地意识到NYI是什么和不是什么,但是您已经将足够的代码添加到它超出JIT的跟踪距离中。最大记录的IR指令,最大虚拟寄存器,调用深度,展开因子,侧面跟踪限制......等等。

另外,请注意,虽然“空”'基准测试有时可以提供非常一般的见解,对于LJ(出于上述原因),代码在上下文中被分析更为重要。为LuaJIT编写具有代表性的性能基准是非常非常困难的,因为痕迹本质上是非本地的。在大型应用程序中使用LJ时,这些非本地交互会产生极大的影响。

TL; DR

这个星球上确实有一个的人真正了解LuaJIT的行为。他的名字叫Mike Pall。

如果你不是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日志,它们是上帝的话语'对我而言,在优化方面。