百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术文章 > 正文

观察者:Lua JIT详解(观察者the one)

cac55 2024-10-13 01:16 23 浏览 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

相关推荐

让组策略保护Windows XP的安全

默认安装完WindowsXP之后,我们的WindowsXP并不很安全。因此,我们有必要对系统进行一些修修补补,一般情况下我们都要动用到注册表。诚然,修改注册表是一种非常有效的方法,但是它需要一定的...

你造吗?十种方式保护你免受"零日攻击"

|责编:王迪WindowsXP的寿终正寝,数据安全问题又再一次成为人们关注的焦点。近日,微软透漏,一个基于InternetExplorer的“零日攻击”给用户带来了严重破坏。“零日攻击”一种利用...

特立独行——打造游戏专用独立系统

大部分人的电脑是为了学习和工作用的,所以,如果你是一个游戏迷,那么推荐你安装一个独立系统专用于游戏,做到工作娱乐两不相扰。方案1:游戏专用移动WindowsXP目的:解决游戏兼容性问题喜欢玩游戏的都...

驰为VX8 3G Win8入门教程篇

距离Win8.1的正式发布也将近1年了,凭借着Win8.1在移动便携以及娱乐办公上的优势,现在的Win8平板越来越受到消费者的追捧,而驰为VX83G就是其中一款,搭载了卓越的英特尔Z3735G四核芯,...

易淘收银软件说明

易淘收银系统,简称易淘收银,专为小型及连锁零售、餐饮行业打造。基于SaaS模式,智能便捷,无需维护,轻量级设计却功能强大,简约而不失专业,助力门店高效管理收银。1、前台系统:收银客户端;2、后台系...

CAD打不开怎么办?原因可能是电脑中毒了,6步就能完美解决问题

一、问题描述我的CAD安装后无法打开,安装过程中没有出现任何问题,但是安装后打开就出现一个对话框“DBXCAS0”点击后又出现“FATALERROR:UnhandledAccessViola...

腾讯QQ6.1正式版发布更新

2014-07-2405:12:00作者:张林【中关村在线软件资讯】7月24日消息:腾讯QQ官网小幅更新了QQ6.1正式版,最新版本号升级至11905,继续主打扁平化、炫酷登录窗口、支持同步最近一...

Win10等网页版OneDrive无法登陆怎么办?

IT之家(www.ithome.com):Win10等网页版OneDrive无法登陆怎么办?Win10之家报道,微软OneDrive云网盘是跨平台的数据同步和存储服务,支持WindowsPC(如Wi...

经典回顾:折戟沉沙的Windows Longhorn有着惊艳的登录屏幕

尽管微软原先计划让WindowsLonghorn继承WindowsXP操作系统的衣钵,但这个充满雄心壮志的操作系统项目最终还是未能迎来曙光,而是被微软用WindowsVista取而代...

电脑怎么优化

电脑配置和宽带流量也是硬件,但这些要求其实并不需要很高,关键还是怎么去安全使用电脑并进行有效的优化。电脑的应用和优化处理一、电脑的应用和优化处理二、目前,大家使用的个人电脑,配置方面均没多大问题,比如...

怎么安装usb驱动

USB驱动主要是针对WIN98时代的说法,如今WINXP已集成大部分USB驱动,通常都能识别。只有极少数情况下,例如手机、打印机或扫描仪等办公设备的USB驱动可能无法自动识别。1、USB驱动偶尔无法...

普通话考试多名考生信息被泄露,接投诉后涉事网站被限制访问

“陕西普通话成绩查询网(sxpth.cn)”泄露个人信息网站截图网传图片显示,407名普通话考试考生的姓名、身份证号码等个人信息疑似被泄露。9月26日下午,涉事网站sxpth.cn的域名注册商——成...

电脑伪技巧——个人电脑无需设置登录密码

默认情况下,我们每次登录系统都要输入登录账户对应的密码才能进入桌面。有些朋友觉得这样很麻烦,由于电脑只是自己使用,还不如不要设置密码,这样每次可以自动登录。大家知道,账户密码是系统验证用户合法性的唯一...

Windows 10/11 自带远程桌面:实用技巧与操作指南

Windows10/11自带远程桌面:实用技巧与操作指南在当今快节奏的数字时代,远程访问和控制计算机的需求日益增长。微软在Windows10和Windows11中内置了远程桌面功能,为用户提供...

不升级系统的5大原因造吗?

2015-01-2405:54:00作者:陈占伟Windows10系统的发布,让人们重新将目光聚焦到生命力长久的Windows系统之上。如今操作系统越来越多,似乎Windows升级的获得的关注度...

取消回复欢迎 发表评论: