LuaJIT FFI回调性能

LuaJIT FFI 文档提到,从 C 回调到 Lua 代码的调用相对较慢,并建议尽可能避免这种情况:

不要使用回调进行性能敏感的工作:例如,考虑一个需要对用户定义的函数进行积分运算的数值积分例程。在 C 代码中调用一个用户定义的 Lua 函数数百万次是个坏主意。回调开销对性能的影响将是十分不利的。

对于新设计,避免使用 PUSH-STYLE 的 API(C 函数重复调用每个结果的回调)。 相反,使用 PULL-STYLE 的 API(重复调用 C 函数以获取新的结果)。通过 FFI 从 Lua 到 C 的调用比另一种方式快得多。 大多数设计良好的库已经使用了 PULL-STYLE 的 API(读/写,获取/放置)。

然而,他们没有提供从 C 回调的速度相对缓慢的任何感觉。如果我有一些我想加速的代码,并且使用回调,如果我重写它以使用 PULL-STYLE 的 API ,我可以期望得到多少加速呢?有人有比较使用每种 API 样式实现等效功能的基准测试吗?

点赞
用户837856
用户837856

下面是这些结果所显示的显著的表现差异:

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          推时间          拉时间          推内存          拉内存
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-08 11:05:19
用户64474
用户64474

在我的电脑上,从LuaJIT调用C语言函数的开销为5个CPU时钟周期(值得注意的是,与通过指向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-15 06:59:56
用户786559
用户786559

两年后,我重新进行了 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样式              1000000    0.158   158.256
PULL样式              1000000    0.207   207.297

这个结果的代码在这里

结论:使用带有参数的 C 回调到 Lua 具有非常大的开销(这几乎是你经常做的),因此它们在关键点上真的不应该使用。不过,您可以将它们用于 IO 或用户输入。

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

2014-11-27 18:08:08