暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

C++与Lua 交互 即时编译器LuaJIT

林元皓 2021-10-25
3883

原由

C++ 与 Lua 奠定了程序的灵活性,但是 Lua 本身是一个脚本语言,脚本语言是一种解释性的语言。

它不像 c\c++ 等可以编译成二进制代码,以可执行文件的形式存在,脚本语言不需要编译,可以直接用,由解释器来负责解释。

在需要高频交互时,Lua 是满足不了的,因此在此引入了 即时编译器 LuaJIT

LuaJIT 对 Lua 的支持也停留在 Lua 5.1 版本,因此这个专栏的 Lua 选择 就停留在 Lua 5.1.5 中...

即时编译器

什么是JIT(Just In Time)呢?

程序运行通常有两种方式:静态编译和动态解释,即时编译混合了二者。即时编译是动态编译的一种形式,是一种优化虚拟机运行的技术。

即时编译器会将频繁执行的代码编译成机器码缓存起来,下次调用时将直接执行机器码。相比原生逐条执行虚拟机指令效率更高。而对于那些只执行一次的代码仍然逐条执行。

值得注意的是,即时编译带来的效率提升,并不一定能抵消编译效率的下降。因为当虚拟机执行指令时并不会立即用JIT进行编译,由于只有部分指令需要JIT进行编译,JIT将决定那些代码将被编译。而延迟编译则有助于JIT选择一个最佳的解决方案。

为什么要使用JIT呢?

对于静态编译的缺点是不够灵活、无法支持热更,而且平台兼容性差。而对于动态解释而言,效率低和代码暴露是其主要缺陷。即时编译混合了动态解释和静态编译,在执行效率上要高于解释执行却低于静态编译。安全性上一般都会将源代码转换成字节码。而无论是源码或是字节码,本质上都是资源,因此可采用热更新机制。在兼容性上,由于虚拟机的存在,可以处理不同平台的差异,对用户保持透明。

LuaJIT

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

LuaJIT 编译

LuaJIT 的版本:LuaJIT 2.0.5

Win10 下使用 VS2017 编译:

在系统的开始菜单中 -> Visual Studio 2017 -> Visual Studio Tools -> 根据平台选择控制台x64 Native Tools Command Prompt for VS 2017 或者 x86 Native Tools Command Prompt for VS 2017切换至 LuaJIT 的 src 目录,运行 msvcbuild.bat
将生成的 luajit.exe、lua51.dll、lua51.lib、luajit.lib、jit 复制到工程需要链接的目录下

Linux 下使用终端编译:

cd luajit
make
sudo make install

Interpreter 模式使用

Interpreter 模式:需要预先将 Lua 文件翻译成字节码文件。不需要代码层的修改。

luajit.exe、lua51.dll、lua51.lib、luajit.lib、jit文件夹 和 Lua脚本,都在同一目录。使用 luajit.exe 编译 Lua 文件,命令 luajit –b source_file out_file
使用 luajit.exe 批量编译 Lua 文件批处理代码: for /r %%v in (\*.lua) do luajit -b %%v %%v
 做成批处理放在 与 luajit.exe 同级目录下,然后你把需要编译的 lua 文件夹拷贝到 这里,双击你的批处理,会在你的lua文件夹所有.lua 文件 替换成编译后的二进制文件。你直接拿过去用就可以了,特别方便。

JIT 模式使用

JIT 模式:需要向操作系统申请可执行内存空间来存储转换后的机器码,需要代码中开启 JIT 模式。

我们以 《C++ 与 Lua 交互 一套完善的框架》为例,将 LuaJIT 引入到工程中:

修改工程配置

附加包含目录:..\include;..\..\luajit\src;

附加依赖项:lua51.lib、luajit.lib

修改 ScriptVM.cpp 代码:

..
// 添加 LuaJIT 头文件
extern "C"
{
#include "lua.h"
#include "lualib.h"
#include "lauxlib.h"
#include "luajit.h"
}
...


static int wrap_exceptions(lua_State *L, lua_CFunction f)
{
try {
return f(L); // Call wrapped function and return result.
}
catch (const char *s) { // Catch and convert exceptions.
lua_pushstring(L, s);
}
catch (std::exception& e) {
lua_pushstring(L, e.what());
}
catch (...) {
lua_pushliteral(L, "caught (...)");
}
return lua_error(L); // Rethrow as a Lua error.
}


bool ScriptVM::Init(void)
{
m_pLua = luaL_newstate();


lua_cpcall(m_pLua, luaopen_base, 0);
lua_cpcall(m_pLua, luaopen_io, 0);
lua_cpcall(m_pLua, luaopen_string, 0);
lua_cpcall(m_pLua, luaopen_table, 0);
lua_cpcall(m_pLua, luaopen_math, 0);
lua_cpcall(m_pLua, luaopen_debug, 0);
lua_cpcall(m_pLua, luaopen_os, 0);
lua_cpcall(m_pLua, luaopen_package, 0);


//luaopen_base(m_pLua);
//luaopen_io(m_pLua);
//luaopen_table(m_pLua);
//luaopen_math(m_pLua);
//luaopen_string(m_pLua);
//luaopen_debug(m_pLua);


//tolua_open_binding(m_pLua);


//lua_setgcthreshold(m_pLua, 200); //200k garbage collection threshold
lua_register(m_pLua, "print", PrintStringList);
//lua_register(m_pLua, "dofile", OverrideDofile);


// 添加 LuaJIT 的支持
lua_pushlightuserdata(m_pLua, (void *)wrap_exceptions);
luaJIT_setmode(m_pLua, 0, LUAJIT_MODE_ENGINE | LUAJIT_MODE_ON);


return true;
}


...
void ScriptVM::ExecuteScript(const char * sScript, bool bAssertOnError)
{
// 添加 LuaJIT 的支持
    luaJIT_setmode(m_pLua, -1, LUAJIT_MODE_ALLFUNC | LUAJIT_MODE_ALLSUBFUNC | LUAJIT_MODE_ON);


int status = luaL_loadbuffer(m_pLua, sScript, strlen(sScript), sScript);
if (status)
{
report_last_error(m_pLua, bAssertOnError);
}
else
{
status = lua_pcall(m_pLua, 0, LUA_MULTRET, 0); /* call main */
if (status)
report_last_error(m_pLua, bAssertOnError);
}
}
...
bool ScriptVM::ExecuteScriptFunc(const std::vector<const char *>&modules, const char * func, bool bAllowNonexist, const char * sig, ...)
{
// 添加 LuaJIT 的支持
    luaJIT_setmode(m_pLua, -1, LUAJIT_MODE_ALLFUNC | LUAJIT_MODE_ALLSUBFUNC | LUAJIT_MODE_ON);


bool bIsSuccess = false;


//PROFILE("ExecuteScriptFunc");
int nSize1 = lua_gettop(m_pLua);


//debug
#if DEBUG_STACK
printf("debug lua: stack size before ExecuteScriptFunc = %d\n", nSize1);
#endif


va_list vl;
int narg, nres; /* number of arguments and results */
va_start(vl, sig);


//get the actual function
if (modules.empty()) //func is global
{
lua_getglobal(m_pLua, func);
if (!lua_isfunction(m_pLua, -1))
{
if (!bAllowNonexist)
error_msg(true, "ExecuteScriptFunc: Invalid function name: %s\n", func);
goto failed;
}
}
else
{
//trace down the modules
std::vector<const char *>::const_iterator it = modules.begin();
//get the global module name or the actual function name if there is no module
lua_getglobal(m_pLua, *it);
if (!lua_istable(m_pLua, -1))
{
if (!bAllowNonexist)
error_msg(true, "ExecuteScriptFunc: Invalid table name: %s\n", *it);
goto failed;
}


for (++it; it != modules.end(); ++it)
{
lua_pushstring(m_pLua, *it);
lua_gettable(m_pLua, -2);
if (!lua_istable(m_pLua, -1))
{
if (!bAllowNonexist)
error_msg(true, "ExecuteScriptFunc: Invalid table name: %s\n", *it);
goto failed;
}
}
//get the func
lua_pushstring(m_pLua, func);
lua_gettable(m_pLua, -2);
if (!lua_isfunction(m_pLua, -1))
{
if (!bAllowNonexist)
error_msg(true, "ExecuteScriptFunc: Invalid function name: %s\n", func);
goto failed;
}
}


/* push arguments */
narg = 0;
while (*sig) { /* push arguments */
switch (*sig++) {
case 'd': /* double argument */
case 'f': /* float argument */ // NieXu: Treat float as double, same as printf()
lua_pushnumber(m_pLua, va_arg(vl, double));
break;
case 'i': /* int argument */
lua_pushnumber(m_pLua, va_arg(vl, int));
break;
case 's': /* string argument */
lua_pushstring(m_pLua, va_arg(vl, char *));
break;
case 'b': /* boolean argument */
lua_pushboolean(m_pLua, va_arg(vl, bool));
break;
case 'u': /* light user data */
lua_pushlightuserdata(m_pLua, va_arg(vl, void *));
break;
case 't': /* type user data */
{
void* pData = va_arg(vl, void *);
const char* sType = va_arg(vl, const char*);
tolua_pushusertype(m_pLua, pData, sType);
break;
}


case '>':
goto endwhile;
default:
error_msg(true, "invalid option (%c)\n", *(sig - 1));
goto failed;
}
narg++;
luaL_checkstack(m_pLua, 1, "too many arguments");
}endwhile:
/* do the call */
nres = strlen(sig); /* number of expected results */
if (lua_pcall(m_pLua, narg, nres, 0) != 0) /* do the call */
{
report_last_error(m_pLua, true);
goto failed;
}
/* retrieve results */
nres = -nres; /* stack index of first result */
while (*sig)
{ /* get results */
switch (*sig++)
{
case 'd': /* double result */
if (!lua_isnumber(m_pLua, nres))
error_msg(true, "wrong result type,function name: %s\n", func);
*va_arg(vl, double *) = lua_tonumber(m_pLua, nres);
break;
case 'f': /* float result */
if (!lua_isnumber(m_pLua, nres))
error_msg(true, "wrong result type,function name: %s\n", func);
*va_arg(vl, float*) = (float)lua_tonumber(m_pLua, nres);
break;
case 'i': /* int result */
if (!lua_isnumber(m_pLua, nres))
error_msg(true, "wrong result type,function name: %s\n", func);
*va_arg(vl, int *) = (int)lua_tonumber(m_pLua, nres);
break;
case 's': /* string result */
if (!lua_isstring(m_pLua, nres))
error_msg(true, "wrong result type,function name: %s\n", func);
*va_arg(vl, std::string*) = lua_tostring(m_pLua, nres);
break;
case 'b': /* boolean argument */
if (!lua_isboolean(m_pLua, nres))
error_msg(true, "wrong result type,function name: %s\n", func);
*va_arg(vl, bool *) = (0 != lua_toboolean(m_pLua, nres));
break;
case 'u': /* light user data */
if (!lua_isuserdata(m_pLua, nres))
error_msg(true, "wrong result type,function name: %s\n", func);
*va_arg(vl, void **) = lua_touserdata(m_pLua, nres);
break;
default:
error_msg(true, "invalid option (%c)\n", *(sig - 1));
}
nres++;
}


bIsSuccess = true;
failed:
va_end(vl);
//clear the stack
lua_settop(m_pLua, nSize1);


#if DEBUG_STACK
//debug
int nSize2 = lua_gettop(m_pLua);
printf("debug lua: stack size after ExecuteScriptFunc = %d\n", nSize2);
if (nSize1 != nSize2)
stackDump(m_pLua);
#endif


return bIsSuccess;
}

LuaJIT 测试

添加时间测试函数类,测试一下 ComputingFormula() 和 ComparingCondition() 这两个函数的效率。

之前记得在下 Release x64 模式下,大概 LuaJIT 效率比 Lua 高 5到8倍吧!懒得测了,略过了...


文章转载自林元皓,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论