LuaJIT is a Just-In-Time Compiler for Lua programming language
LuaJIT的运行环境包括一个用手写汇编实现的Lua解释器和一个可以直接生成机器代码的JIT编译器。Lua代码在被执行之前总是会先被lfn
生成LuaJIT自己定义的字节码ByteCode
。
开始时Lua字节码总是被LuaJIT的解释器解释执行,LuaJIT的解释器会在执行字节码时同时记录一些运行时的统计信息,如每个Lua函数调用入口的实际运行次数,还有每个Lua循环的实际执行次数。当这些次数超过某个预设的阈值时,便认为对应的Lua函数入口或对应的Lua循环足够的热,此时便会触发JIT编译器开始工作。
JIT编译器会从热函数的入口或热循环的某个位置开始尝试编译对应的Lua代码路径,编译的过程是把LuaJIT字节码先转换成LuaJIT自己定义的中间码(IR),然后再生成针对目标体系结构的机器码,如x86_64指令组成的机器码。如果当前Lua代码路径上的所有操作都可以被JIT编译器顺利编译,则这条编译过的代码路径便被称为一个trace
,在物理上对应一个trace
类型的GC对象,即参与Lua GC的对象。
即时编译器
什么是JIT(Just In Time)呢?
程序运行通常有两种方式:静态编译和动态解释,即时编译混合了二者。即时编译是动态编译的一种形式,是一种优化虚拟机运行的技术。
即时编译器会将频繁执行的代码编译成机器码缓存起来,下次调用时将直接执行机器码。相比原生逐条执行虚拟机指令效率更高。而对于那些只执行一次的代码仍然逐条执行。
值得注意的是,即时编译带来的效率提升,并不一定能抵消编译效率的下降。因为当虚拟机执行指令时并不会立即用JIT进行编译,由于只有部分指令需要JIT进行编译,JIT将决定那些代码将被编译。而延迟编译则有助于JIT选择一个最佳的解决方案。
为什么要使用JIT呢?
对于静态编译的缺点是不够灵活、无法支持热更,而且平台兼容性差。而对于动态解释而言,效率低和代码暴露是其主要缺陷。即时编译混合了动态解释和静态编译,在执行效率上要高于解释执行却低于静态编译。安全性上一般都会将源代码转换成字节码。而无论是源码或是字节码,本质上都是资源,因此可采用热更新机制。在兼容性上,由于虚拟机的存在,可以处理不同平台的差异,对用户保持透明。
JVM JIT
即使编译可以分为2种:方法即时编译Method JIT
和跟踪编即时译Trace JIT
。
以Java为例,实际上是指的是JIT的一个变种:自适应动态编译
简单来说可分为2个步骤
- 跟踪热点函数或
trace
,编译成机器码执行,并缓存以供下次使用。 - 非热点函数解释执行
那么为什么只编译热点函数呢?
对于只执行一次的代码而言,解释执行其实是比JIT编译执行要快,对于那些代码JIT编译在执行反而得不偿失。而对于只执行少量次数的代码,即使编译带来的速度的提升也未必能抵消最初编译带来的开销,只有对频繁执行的代码,即使编译才能保证有正面的收益。
LuaJIT
LuaJIT is a Just-In-Time Compiler for the Lua programming language
LuaJIT是Lua的即时编译器,简单来说,LuaJIT是一个高效的Lua虚拟机。LuaJIT是一个跟踪JITTraceJIT
而非方法JITMethodJIT
,其工作方式并不是检测和优化整个热点方法而是检测并优化热点跟踪或执行路径。
Lua是如何找到它希望去编译的trace
的呢?LuaJIT使用一个散列表维护相应指令(跳转、调用)的热度,除了捕获指令(Lua并没有解决冲突),由于热度是启发式的且并不稠密,也就是说不太可能发生一个程序中的所有跳转都是热点的情况。所以在实践中,这个做法执行的很好。当执行跳转或调用的时候,解释器更新并检查热度计数器。
LuaJIT中存在2种工作模式
JIT模式
JIT模式即即时编译模式,该模式下会将代码直接翻译成机器码,并向操作系统申请可执行内存空间来存储转换后的机器码。执行时直接执行机器码,所以效率是最高的。但是在iOS、XBox、PS4等平台上,鉴于自身安全原因都是不授权分配可执行内存空间的,所以这些平台下就不能使用JIT模式。Interpreter模式
翻译器模式,该模式下会将代码先翻译成字节码,然后将字节码翻译成机器码,所以无需向操作系统申请可执行内存空间。所以几乎所有平台都支持此模式,但是性能相比JIT模式而言还有一定的差距。
LuaJIT的工作方式
LuaJIT采用TraceCompiler方案也就是追踪编译方案,LuaJIT会先用Interpreter模式将代码转换成字节码。然后在支持JIT的平台上将经常执行的代码开启记录模式并记录这些代码实际运行每一步的细节,最后被LuaJIT优化以及JIT化。
LuaJIT的优点在于支持JIT执行效率更高,字节码文件支持反编译。其缺点在于对64位支持不够好,相比较而言也没有原生Lua成熟。
Lua与LuaJIT有何区别呢?
- 哈希算法不同,导致表的编译顺序不同。
- LuaJIT新增了转义字符,且处理转义字符的方式不同。
- LuaJIT内存上线是4G
- 函数中的局部变量最大限制上LuaJIT要小于Lua
- 在iOS设备上是不支持JIT功能的
Lua主要由3部分构成:语法实现、库函数、字节码。而LuaJIT由4部分组成:语法实现、TraceJIT编译器、库函数、字节码。
- TraceJIT 编译器中的
trace
实际上是一段线性的字节码序列,热点trace
被编译成机器码,非热点trace
解释执行。 - 字节码(ByteCode)“基本上(LuaJIT使用了uleb128)”可认为是虚拟机
VM
的指令码,其优点在于:减少了文件大小、生成函数原型更快、增加被破解的难度、对源码轻微的优化。 - LuaJIT的库函数包括原生库(经强化过的原生库)、
bit
、ffi
、jit