nature

nature is api gateway, just show you for how to bulid api gateway

View on GitHub

什么是 JIT?

自从 OpenResty 1.5.8.1 版本之后,默认捆绑的 Lua 解释器就被替换成了 LuaJIT,而不再是标准 Lua。单从名字上,我们就可以直接看到这个新的解释器多了一个 JIT,接下来我们就一起来聊聊 JIT

先看一下 LuaJIT 官方的解释:LuaJIT is a Just-In-Time Compiler for the Lua programming language。

LuaJIT 工作原理

1、 LuaJIT 的组成

LuaJIT 的运行时环境包括一个用 手写汇编实现的 Lua 解释器 和一个可以 直接生成机器代码的 JIT 编译器

2、 工作原理

工具和内部对象结构

1、 查看工具和内容解析

2、 不足之处

LuaJIT 的 JIT 编译器的实现目前还不完整,有一些基本原语它还无法编译,比如:

所以当 JIT 编译器在当前代码路径上遇到了它不支持的操作,便会立即终止当前的 trace 编译过程(这被称为 trace abort),而重新退回到解释器模式。

JIT 编译器不支持的原语被称为 NYI(Not Yet Implemented)原语。比较完整的 NYI 列表在这篇文档里面:http://wiki.luajit.org/NYI

3、如何避坑

所谓 让更多的 Lua 代码被 JIT 编译,其实就是帮助更多的 Lua 代码路径能为 JIT 编译器所接受。这一般通过两种途径来实现:

对于第 2 种方式,春哥一直在推动公司(CloudFlare)赞助 Mike Pall 的开发工作。 不过有些原语因为本身的代价过高,而永远不会被编译,比如基于经典的 lua_CFunction 方式实现的 Lua C 模块(所以需要尽量通过 LuaJIT 的 FFI 来调用 C)。

而对于第 1 种方法,我们如何才能知道具体是哪一行 Lua 代码上的哪一个 NYI 原语终止了 trace 编译呢?

答案很简单。就是使用 LuaJIT 安装自带的 jit.vjit.dump 这两个 Lua 模块。这两个 Lua 模块会打印出 JIT 编译器工作的细节过程。

4、实例分析

在 Nginx 的上下文中,我们可以在 nginx.conf 文件中的 http {} 配置块中添加下面这一段:

init_by_lua_block {
    local verbose = false
    if verbose then
        local dump = require("jit.dump")
        dump.on(nil, "/tmp/jit.log")
    else
        local v = require("jit.v")
        v.on("/tmp/jit.log")
    end

    require("resty.core")
}

那一行 require("resty.core") 倒并不是必需的,放在那里的主要目的是为了尽量避免使用 ngx_lua 模块自己的基于 lua_CFunction 的 Lua API,减少 NYI 原语。

在上面这段 Lua 代码中,可以下分为如下两种情况:

这里我们主要以 jit.v 模块为例。 在启动 Nginx 之后,应当使用 abweighttp 这样的工具对相应的服务接口进行预热,以触发 LuaJIT 的 JIT 编译器开始工作(还记得刚才我们说的 “热函数”“热循环” 吗?)。 预热过程一般不用太久,跑个二三百个请求足矣。当然,压更多的请求也没关系。完事后,我们就可以检查 /tmp/jit.log 文件里面的输出了。

jit.v 模块的输出里如果有类似下面这种带编号的 TRACE 行,则表示成功编译了的 trace 对象,例如:

5、 探查字节码的工具

那么我们如何知道 51 字节码究竟是啥呢?我们可以用 nginx-devel-utils 项目中的 ljbc.lua 脚本来取得 51 号字节码的名字:

# /usr/local/openresty/luajit/bin/luajit-2.1.0-alpha ljbc.lua 51
opcode 51:
FNEW

我们看到原来是用来(动态)创建 Lua 函数的 FNEW 字节码。

ljbc.lua 脚本的位置是:

https://github.com/agentzh/nginx-devel-utils/blob/master/ljbc.lua

非常简单的一个脚本,就几行 Lua 代码。

这里需要提醒的是,不同版本的 LuaJIT 的字节码可能是不相同的,所以一定要使用和你的 Nginx 链接的同一个 LuaJIT 来运行这个 ljbc.lua 工具,否则有可能会得到错误的结果。

6、对比实验

我们实际做个对比实验,看看 JIT 带来的好处:

# cat test.lua
local s = [[aaaaaabbbbbbbcccccccccccddddddddddddeeeeeeeeeeeee
fffffffffffffffffggggggggggggggaaaaaaaaaaabbbbbbbbbbbbbb
ccccccccccclllll]]

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

# time luajit test.lua
5.19s user
0.03s system
96% cpu
5.392 total

#  time lua test.lua
9.20s user
0.02s system
99% cpu
9.270 total

本例子可以看到效率相差大约 9.2/5.19 ≈ 1.77 倍,换句话说标准 Lua 需要 177% 的时间才能完成同样的工作。估计大家觉得这个还不过瘾,再看下面示例代码:

文件 test.lua:


local loop_count = tonumber(arg[1])
local fun_pair = "ipairs" == arg[2] and ipairs or pairs

local t = {}
for i=1,100 do
    t[i] = i
end

for i=1,loop_count do
    for j=1,1000 do
        for k,v in fun_pair(t) do
            --
        end
    end
end
执行参数 执行结果
(下面三行是 ipairs 测试结果)  
time lua test.lua 1000 ipairs 3.96s user 0.02s system 98% cpu 4.039 total
time luajit test.lua 1000 ipairs 0.10s user 0.00s system 95% cpu 0.113 total
time luajit test.lua 10000 ipairs 0.98s user 0.00s system 99% cpu 0.991 total
(下面两行是 pairs 测试结果)  
time lua test.lua 1000 pairs 3.97s user 0.01s system 99% cpu 3.992 total
time luajit test.lua 1000 pairs 1.54s user 0.01s system 99% cpu 1.559 total

从这个执行结果中,大致可以总结出下面几点:

可以被 JIT 编译的元操作

下面给大家列一下截止到目前已经可以被 JIT 编译的元操作。 其他还有 IO、Bit、FFI、Coroutine、OS、Package、Debug、JIT 等分类,使用频率相对较低,这里就不罗列了,可以参考官网:http://wiki.luajit.org/NYI

基础库的支持情况

函数 编译? 备注
assert yes  
collectgarbage no  
dofile never  
error never  
getfenv 2.1 partial 只有 getfenv(0) 能编译
getmetatable yes  
ipairs yes  
load never  
loadfile never  
loadstring never  
next no  
pairs no  
pcall yes  
print no  
rawequal yes  
rawget yes  
rawlen (5.2) yes  
rawset yes  
select partial 第一个参数是静态变量的时候可以编译
setfenv no  
setmetatable yes  
tonumber partial 不能编译非10进制,非预期的异常输入
tostring partial 只能编译:字符串、数字、布尔、nil 以及支持 __tostring元方法的类型
type yes  
unpack no  
xpcall yes  

字符串库

函数 编译? 备注
string.byte yes  
string.char 2.1  
string.dump never  
string.find 2.1 partial 只有字符串样式查找(没有样式)
string.format 2.1 partial 不支持 %p 或 非字符串参数的 %s
string.gmatch no  
string.gsub no  
string.len yes  
string.lower 2.1  
string.match no  
string.rep 2.1  
string.reverse 2.1  
string.sub yes  
string.upper 2.1  

函数 编译? 备注
table.concat 2.1  
table.foreach no 2.1: 内部编译,但还没有外放
table.foreachi 2.1  
table.getn yes  
table.insert partial 只有 push 操作
table.maxn no  
table.pack (5.2) no  
table.remove 2.1 部分,只有 pop 操作
table.sort no  
table.unpack (5.2) no  

math 库

函数 编译? 备注
math.abs yes  
math.acos yes  
math.asin yes  
math.atan yes  
math.atan2 yes  
math.ceil yes  
math.cos yes  
math.cosh yes  
math.deg yes  
math.exp yes  
math.floor yes  
math.fmod no  
math.frexp no  
math.ldexp yes  
math.log yes  
math.log10 yes  
math.max yes  
math.min yes  
math.modf yes  
math.pow yes  
math.rad yes  
math.random yes  
math.randomseed no  
math.sin yes  
math.sinh yes  
math.sqrt yes  
math.tan yes  
math.tanh yes  

lua 语言目录

总目录