观察者:Lua JIT详解(观察者the one)
cac55 2024-10-13 01:16 32 浏览 0 评论
自从 OpenResty 1.5.8.1 版本之后,默认捆绑的 Lua 解释器就被替换成了 LuaJIT,而不再是标准 Lua。单从名字上,我们就可以直接看到这个新的解释器多了一个 JIT ,接下来我们就一起来聊聊 JIT 。
先看一下 LuaJIT 官方的解释:LuaJIT is a Just-In-Time Compilerfor the Lua programming language。
LuaJIT 的运行时环境包括一个用手写汇编实现的 Lua 解释器和一个可以直接生成机器代码的JIT 编译器。
Lua 代码在被执行之前总是会先被 lfn 成 LuaJIT 自己定义的字节码(Byte Code) 。
一开始的时候,Lua 字节码总是被 LuaJIT 的解释器解释执行。LuaJIT 的解释器会在执行字节码时同时记录一些运行时的统计信息,比如每个 Lua 函数调用入口的实际运行次数,还有每个 Lua 循环的实际执行次数。当这些次数超过某个预设的阈值时,便认为对应的 Lua 函数入口或者对应的 Lua 循环足够的“热”,这时便会触发 JIT 编译器开始工作。
JIT 编译器会从热函数的入口或者热循环的某个位置开始尝试编译对应的 Lua 代码路径。编译的过程是把 LuaJIT 字节码先转换成LuaJIT 自己定义的中间码(IR) ,然后再生成针对目标体系结构的机器码(比如 x86_64 指令组成的机器码) 。
如果当前 Lua 代码路径上的所有的操作都可以被 JIT 编译器顺利编译,则这条编译过的代码路径便被称为一个“trace”,在物理上对应一个 trace 类型的 GC 对象(即参与 Lua GC 的对象) 。
你可以通过 ngx-lj-gc-objs 工具看到指定的 Nginx worker 进程里所有 trace 对象的一些基本的统计信息,
比如下面这一行 ngx-lj-gc-objs 工具的输出
102 trace objects: max=928, avg=337, min=160, sum=34468 (in bytes)
则表明当前进程内的 LuaJIT VM 里一共有 102 个 trace 类型的 GC 对 象,其中最小的 trace占用 160 个字节,最大的占用 928 个字节,平均大小是 337 字节,而所有 trace 的总大小是34468 个字节。
LuaJIT 的 JIT 编译器的实现目前还不完整,有一些基本原语它还无法编译,比如 pairs() 函数、unpack() 函数、string.match() 函数、基于 lua_CFunction 实现的 Lua C 模块、FNEW字节码,等等。所以当 JIT 编译器在当前代码路径上遇到了它不支持的操作,便会立即终止当前的 trace 编译过程(这被称为 trace abort) ,而重新退回到解释器模式。
JIT 编译器不支持的原语被称为 NYI(Not Yet Implemented) 原语。
所谓“让更多的 Lua 代码被 JIT 编译”,其实就是帮助更多的 Lua 代码路径能为 JIT 编译器所接受。这一般通过两种途径来实现:
1. 调整对应的 Lua 代码,避免使用 NYI 原语。
2. 增强 JIT 编译器,让越来越多的 NYI 原语能够被编译。
对于第 2 种方式,春哥一直在推动公司(CloudFlare) 赞助 Mike Pall 的开发工作。不过有些原语因为本身的代价过高,而永远不会被编译,比如基于经典的 lua_CFunction 方式实现的Lua C 模块(所以需要尽量通过 LuaJIT 的 FFI 来调用 C) 。
而对于第 1 种方法,我们如何才能知道具体是哪一行 Lua 代码上的哪一个 NYI 原语终止了trace 编译呢?答案很简单。就是使用 LuaJIT 安装自带的 jit.v 和 jit.dump 这两个 Lua 模块。
这两个 Lua 模块会打印出 JIT 编译器工作的细节过程。
在 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 代码中,当 verbose 变量为 false 时(默认就为 false 哈) ,我们使用 jit.v 模块打印出比较简略的流水信息到 /tmp/jit.log 文件中;而当 verbose 变量为 true 时,我们则使用 jit.dump 模块打印所有的细节信息,包括每个 trace 内部的字节码、IR 码和最终生成的机器指令。
这里我们主要以 jit.v 模块为例。在启动 Nginx 之后,应当使用 ab 和 weighttp 这样的工具对相应的服务接口进行预热,以触发 LuaJIT 的 JIT 编译器开始工作(还记得刚才我们说的“热函数”和“热循环”吗?) 。预热过程一般不用太久,跑个二三百个请求足矣。当然,压更多的请求也没关系。完事后,我们就可以检查 /tmp/jit.log 文件里面的输出了。
jit.v 模块的输出里如果有类似下面这种带编号的 TRACE 行,则指示成功编译了的 trace 对象,例如
[TRACE 6 shdict.lua:126 return]
这个 trace 对象编号为 6,对应的 Lua 代码路径是从 shdict.lua 文件的第 126 行开始的。
下面这样的也是成功编译了的 trace:
[TRACE 16 (15/1) waf-core.lua:419 -> 15]
这个 trace 编号为 16,是从 waf-core.lua 文件的第 419 行开始的,同时它和编号为 15 的trace 联接了起来。
而下面这个例子则是被中断的 trace:
[TRACE --- waf-core.lua:455 -- NYI: FastFunc pairs at waf-core.lua:458]
上面这一行是说,这个 trace 是从 waf-core.lua 文件的第 455 行开始编译的,但当编译到waf-core.lua 文件的第 458 行时,遇到了一个 NYI 原语编译不了,即 pairs() 这个内建函数,于是当前的 trace 编译过程被迫终止了。
类似的例子还有下面这些:
[TRACE --- exit.lua:27 -- NYI: FastFunc coroutine.yield at waf-core.lua:439]
[TRACE --- waf.lua:321 -- NYI: bytecode 51 at raven.lua:107]
上面第二行是因为操作码 51 的 LuaJIT 字节码也是 NYI 原语,编译不了。
那么我们如何知道 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 工具,否则有可能会得到错误的结果。
我们实际做个对比实验,看看 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
执行参数执行结果
time lua test.lua 1000 ipairs 3.96s user 0.02s system 98% cpu 4.039 total
time lua test.lua 1000 pairs 3.97s user 0.01s system 99% cpu 3.992 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
time luajit test.lua 1000 pairs 1.54s user 0.01s system 99% cpu 1.559 total
从这个执行结果中,大致可以总结出下面几个观点:
在标准 Lua 解释器中,使用 ipairs 或 pairs 没有区别;
对于 pairs 方式,LuaJIT 的性能大约是标准 Lua 的 4 倍;
对于 ipairs 方式,LuaJIT 的性能大约是标准 Lua 的 40 倍。
可以被 JIT 编译的元操作
下面给大家列一下截止到目前已经可以被 JIT 编译的元操作。 其他还有 IO、Bit、FFI、Coroutine、OS、Package、Debug、JIT 等分类,使用频率相对较低,这里就不罗列了。
基础库的支持情况
函数 编译? 备注
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
相关推荐
- 苹果新macOS、新Mac还没出,但已经有新版虚拟机软件Parallels Desktop 19
-
自从苹果电脑全面转向ARM架构芯片之后,想在新款Mac电脑上安装Windows或Linux系统,就只能依靠虚拟机软件了,其中ParallelsDesktop应该是比较多Mac用户选择使用的一款,现在...
- 这个开源神器可快速帮你安装 MacOS 虚拟机
-
大家好,我是JackTian。安装Windows和Linux操作系统是最熟悉不过的必备技能了。那么,给大家推荐一个非常实用的开源脚本:macos-guest-virtualbox.sh,帮你...
- 如何在VMware虚拟机上安装运行Mac OS系统??
-
想在自己的Windows电脑上安装一个MacOS体验一下苹果系统的小伙伴,教程来了!!!一、安装前准备虚拟机运行软件:VMwareWorkstationPro,版本:16.0.0。(可以注册)VM...
- 效率!MacOS下超级好用的Linux虚拟工具:Lima
-
对于MacOS用户来说,搭建Linux虚拟环境一直是件让人头疼的事。无论是VirtualBox还是商业的VMware,都显得过于笨重且配置复杂。今天,我们要介绍一个轻巧方便的纯命令行Linux虚拟工具...
- 普通电脑安装苹果MacOS+Windows10双系统,这次可不是虚拟机
-
上篇文章中说到,有一朋友因为工作需要,得临时使用苹果系统,笔者给他用VmwareWorkStation安装了一个苹果系统的虚拟机,结果装是装上了,但是发现调整分辨率有点小问题,文件传输也不方便。虽说...
- 官方证实苹果M1芯片不支持Windows 11
-
中关村在线消息:近日根据微软官方透露,目前已经确定Windows11不支持运行在苹果M1芯片上,这意味着过往在Mac电脑上安装Windows系统的做法在M1芯片的Mac电脑上并不适用。不过此前有网友...
- 这可能是 Mac 共享文件最详细的教程了
-
如果希望让一台Mac访问另一台Mac上的文件,就可以使用Mac的文件共享功能。而且不仅是Mac之间,甚至用iPhone、iPad、WindowsPC都可以访问Mac的共享文件...
- 在 M1/M2 Mac 上,让 Windows 11 免费“跑”起来
-
自从苹果在产品中逐步使用自研的M系列芯片淘汰掉英特尔芯片之后,很多事情都发生了改变。作者|KirkMcElhearn和JoshuaLong译者|弯月出品|CSDN(ID:CS...
- VMware Workstation克隆虚拟机后修改ip地址和mac地址
-
VMwareWorkstation克隆虚拟机,登录之后发现,克隆虚拟机不仅用户名相同,连ip地址、mac地址也是相同的,很显然访问相同ip地址的虚拟机是会出现ip地址冲突的。一、修改IP地址这就需要...
- VirtualBox7中安装macOS big sur,在windows10&11上「保姆级教程」
-
macOSBigSur是苹果公司研发的桌面端操作系统,于北京时间2020年6月23日在2020苹果全球开发者大会上发布。BigSur采用全新的精美设计,为主要app如Safari浏览器...
- 最强mac虚拟机Parallels Desktop 16 有哪些重要的新增功能?
-
ParallelsDesktop16正式发布,软件带来了一些显着的新功能和性能增强,包括对macOSBigSur的全面支持。当苹果推出macOSBigSur时,它终止了对Par...
- 关于在MacOS安装虚拟机的全过程(macos 安装虚拟机)
-
哈喽大家好,我是咕噜美乐蒂,很高兴又见面啦!下面美乐蒂将详细地给大家介绍一下在macOS上使用VMwareFusion创建虚拟机并安装操作系统的步骤:一、确认虚拟化支持:首先,确认你的Ma...
- macOS上也能轻松运行Win系统的虚拟机,你还不知道吗?
-
在macOS系统上运行Win系统的方式,虚拟机篇吉安光头强原创你是否曾经为了在Mac上运行Windows系统而烦恼不用着急,下面我将分享一种简单易行的方法,让你轻松在Mac上运行Windows系统准备...
- Mac M芯片上安装统信UOS 1070arm64虚拟机
-
原文链接:MacM芯片上安装统信UOS1070arm64虚拟机Hello,大家好啊!今天给大家带来一篇关于如何在苹果M系列芯片的Mac电脑上,通过VMware安装ARM64版统信UOS1070...
- 虚拟机不好用?Mac mini 多配一台Windows电脑,用远程桌面更好!
-
最近新入手了MacminiM4款,这里来更新一下相关问题,对于还没有购买Macmini,但是又想要用苹果电脑的朋友,一些参考,我觉得还是挺有用的!Macmini选择哪个渠道购买好?现在比较划算...
你 发表评论:
欢迎- 一周热门
- 最近发表
-
- 苹果新macOS、新Mac还没出,但已经有新版虚拟机软件Parallels Desktop 19
- 这个开源神器可快速帮你安装 MacOS 虚拟机
- 如何在VMware虚拟机上安装运行Mac OS系统??
- 效率!MacOS下超级好用的Linux虚拟工具:Lima
- 普通电脑安装苹果MacOS+Windows10双系统,这次可不是虚拟机
- 官方证实苹果M1芯片不支持Windows 11
- 这可能是 Mac 共享文件最详细的教程了
- 在 M1/M2 Mac 上,让 Windows 11 免费“跑”起来
- VMware Workstation克隆虚拟机后修改ip地址和mac地址
- VirtualBox7中安装macOS big sur,在windows10&11上「保姆级教程」
- 标签列表
-
- 如何绘制折线图 (52)
- javaabstract (48)
- 新浪微博头像 (53)
- grub4dos (66)
- s扫描器 (51)
- httpfile dll (48)
- ps实例教程 (55)
- taskmgr (51)
- s spline (61)
- vnc远程控制 (47)
- 数据丢失 (47)
- wbem (57)
- flac文件 (72)
- 网页制作基础教程 (53)
- 镜像文件刻录 (61)
- ug5 0软件免费下载 (78)
- debian下载 (53)
- ubuntu10 04 (60)
- web qq登录 (59)
- 笔记本变成无线路由 (52)
- flash player 11 4 (50)
- 右键菜单清理 (78)
- cuteftp 注册码 (57)
- ospf协议 (53)
- ms17 010 下载 (60)