LuaJIT 介绍

What’s JIT?

Just-In-Time(JIT)是指在程序运行时进行代码编译的技术,像 Java、Python(PyPy)、LuaJIT 都引入了这种技术。

一般 JIT 编译器与解释器一同工作,大部分时间代码由解释器转换成机器码运行,当某些代码运行的次数超过设定的阈值时,就会触发 JIT 编译器工作,把这些热点代码编译为机器码,当下次运行到这些代码时,就不用解释器进行解释转换了,可以直接运行机器码来提高程序的运行速度。

JIT 工作原理

How does work of LuaJIT?

LuaJIT 是一种即时(JIT)编译器。函数是按需编译的,即当它们首先运行时。这既确保了应用程序的快速启动,也有助于避免无用的工作。例如,未使用的函数根本不会被编译。

当启动 LuaJIT 时,一切都像在标准 Lua 中一样进行:初始化 Lua 核心,加载标准库并分析命令行。然后通常会加载第一个 Lua 源代码文件并将其转换为 Lua 字节码。

example.lua:

local s = "hello,world!"

for i=1,10000 do
    for j=1,10000 do
        string.find(s, "ll", 1, true)
    end
end

上面代码会被先转换成 LuaJIT 自己定义的字节码,可以用下面的命令来查看:

luajit -bl example.lua
-- BYTECODE -- example.lua:0-8
0001    KSTR     0   0      ; "hello,world!"
0002    KSHORT   1   1
...

然后这些字节码再交给解释器去执行,当执行达到阈值设定时,就会触发 JIT 编译器的工作,LuaJIT 会先将它转换成 IR 中间码,然后转换成对应平台的机器码。

LuaJIT 编译流程

Not Yet Implemented(NYI)

在 LuaJIT 中,当 JIT 编译器编译成功后就会生成一个 trace 类型的 GC 对象,但是并不是所有的代码 LuaJIT 都能够成功编译。

当 LuaJIT 遇到不支持的函数或代码(一般叫它:NYI)时,就会中止当前的编译工作,重新回退到解释器执行的模式去。

例如上面的 string.find() 函数,只有 LuaJIT 2.1 以上的版本才支持,可以添加 -jv 选项来显示 JIT 编译器进度的详细信息:

luajit -v
# LuaJIT 2.0.5 -- Copyright (C) 2005-2017 Mike Pall.

luajit -jv example.lua
# [TRACE --- example.lua:4 -- NYI: FastFunc string.find at example.lua:5]

当 LuaJIT 版本为 2.0.5 时,就会提示 NYI: FastFunc string.find,意思是 LuaJIT 编译器不支持编译 string.find()

切换到 luajit-2.1.0-beta3 后:

luajit-2.1.0-beta3 -jv example.lua
# [TRACE   1 example.lua:4 loop]
# [TRACE   2 (1/3) example.lua:3 -> 1]

TRACE 后面接着数字,说明 JIT 编译成功了。

How does maintain speed of LuaJIT?

要保证 LuaJIT 的运行速度,就要避免使用 NYI 函数。如果调用 C 函数的话,尽量使用 LuaJIT 的 ffi 库来调用。

尽量保持使用最新的 LuaJIT 版本,LuaJIT 的 2.1 版本加入了很多原先不支持的 NYI 函数,例如 string.find() 等。

Speed Test between JIT and None-JIT

由于 LuaJIT 2.0.5 的 JIT 不支持 string.find(),不会触发 JIT 的编译工作,所以可以用上面的代码,分别用 LuaJIT 2.0.5LuaJIT 2.1.0-beta3 来测试有 JIT 和没有 JIT 之间代码执行的速度:

LuaJIT 2.0.5(无 JIT):

time luajit-2.0.5 example.lua
# luajit-2.0.5 example.lua  2.14s user 0.00s system 99% cpu 2.140 total
# luajit-2.0.5 example.lua  2.17s user 0.00s system 99% cpu 2.169 total
# luajit-2.0.5 example.lua  2.17s user 0.00s system 99% cpu 2.176 total

LuaJIT 2.1.0-beta3(有 JIT):

time luajit-2.1.0-beta3 example.lua
# luajit-2.1.0-beta3 example.lua  0.03s user 0.00s system 99% cpu 0.026 total
# luajit-2.1.0-beta3 example.lua  0.02s user 0.00s system 99% cpu 0.024 total
# luajit-2.1.0-beta3 example.lua  0.03s user 0.00s system 99% cpu 0.026 total

两者的速度相差了差不多 100 倍