编程文汇

lua jit ffi对性能的影响

测试案例

访问struct字段,数组字符,table字段,等等,循环执行3000万次

字段访问

jit开关 纯c 纯lua/JS ffi->cstruct字段 ffi->cfunc->cstruct字段 访问lua string字符 table
关闭jit 30ms 208ms 1324ms 3189ms 714ms 398ms
开启jit 30ms 162ms 161ms 164ms 162ms 163ms
lua5.34 - 353ms
lua5.35 - 564ms
duk2.5JS - 9374ms 8265ms

在开启jit的情况下,ffi字段读取、函数调用,和本地对象一样快。
关闭jit时,本地程序的速度,是ffi的10-20倍左右。

函数调用

ffi c_int_add c_longlong_add c_pass_pointer_to_c
jit on 164ms 698ms 161ms
jit off 3189ms 3796ms 3308ms

与参数和返回值类型的复杂度有很大关系,如果参数类型是基本类型(包括指针),并且可以直接映射到lua数字,速度会和ffi字段访问一样快,如果有i64就会慢很多(时间增加到4倍)

结论:降低ffi访问频率,优先优化调用频率高的代码段。

就跨平台性能而言,降低ffi访问频率(包括cdata字段访问、ffi函数调用),毕竟字段访问是任何逻辑都不可缺少的高频操作,效率越高越好。
注意:这里是频率!频率低的可以不考虑性能问题。

  • 优先采用纯lua
  • 其次优先采用函数调用,因为函数调用可以把更多操作放入c,降低ffi使用频率。
  • 再次buf优先使用lua table,lua string,而不是char数组,在jit关闭时可以做一次memcpy,避免直接访问cdata char数组。也可以根据jit状态,做成cdata和string动态切换的方式。
  • 如果是纯服务器端,可以大量采用ffi cdata。
  • 如果是服务器端的调用频率更高,采用ffi更好。

测试代码

local ffi = require("ffi")
local Buf = require("ezglua.buf")
local c = require("ezglua.c")
local ffic = ffi.C

ffi.cdef [[
  void test_perf(ilong N);
]]

local netbuf = Buf.new();
local startTime = c.now()
local N = 30000000

ffi.C.test_perf(N)
print();

local tmp = {}
tmp.cc = 0;
startTime = c.now()
for i = 1, N do
  tmp.cc = i%188
end
print(tmp.cc)
print("local lua :\t", c.now() - startTime)
print();

startTime = c.now()
for i = 1, N do
  netbuf.rindex = i%188
end
print(netbuf.rindex)
print("ffi:field access  :\t", c.now() - startTime)
print();


tmp = 0
startTime = c.now()
for i = 1, N do
  tmp = ffic.iint_add(0, i%188)
end
print(tmp)
print("cfunc call :\t", c.now() - startTime)
print();

local s = string.rep("a", 1024)
local tmp = 0
startTime = c.now()
for i = 1, N do
  tmp = string.byte(s, i % 188+1)
end
print(tmp)
print("local  lua str :\t", c.now() - startTime)
print();

local arr = {}
local tmp = 0
startTime = c.now()
for i = 1, N do
  arr[i % 188 + 1] = i
end
print(arr[10])
print("local  lua table :\t", c.now() - startTime)
print();