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呢?
對於靜態編譯的缺點是不夠靈活、無法支持熱更,而且平臺兼容性差。而對於動態解釋而言,效率低和代碼暴露是其主要缺陷。即時編譯混合了動態解釋和靜態編譯,在執行效率上要高於解釋執行卻低於靜態編譯。安全性上一般都會將原始碼轉換成字節碼。而無論是源碼或是字節碼,本質上都是資源,因此可採用熱更新機制。在兼容性上,由於虛擬機的存在,可以處理不同平臺的差異,對用戶保持透明。
LuaJITLuaJIT是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 luajitmakesudo make installInterpreter 模式使用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 代碼:
..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); } catch (const char *s) { lua_pushstring(L, s); } catch (std::exception& e) { lua_pushstring(L, e.what()); } catch (...) { lua_pushliteral(L, "caught (...)"); } return lua_error(L); }
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);
lua_register(m_pLua, "print", PrintStringList);
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_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); 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_setmode(m_pLua, -1, LUAJIT_MODE_ALLFUNC | LUAJIT_MODE_ALLSUBFUNC | LUAJIT_MODE_ON);
bool bIsSuccess = false;
int nSize1 = lua_gettop(m_pLua);
#if DEBUG_STACK printf("debug lua: stack size before ExecuteScriptFunc = %d\n", nSize1);#endif
va_list vl; int narg, nres; va_start(vl, sig);
if (modules.empty()) { 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 { std::vector<const char *>::const_iterator it = modules.begin(); 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; } } 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; } }
narg = 0; while (*sig) { switch (*sig++) { case 'd': case 'f': lua_pushnumber(m_pLua, va_arg(vl, double)); break; case 'i': lua_pushnumber(m_pLua, va_arg(vl, int)); break; case 's': lua_pushstring(m_pLua, va_arg(vl, char *)); break; case 'b': lua_pushboolean(m_pLua, va_arg(vl, bool)); break; case 'u': lua_pushlightuserdata(m_pLua, va_arg(vl, void *)); break; case 't': { 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: nres = strlen(sig); if (lua_pcall(m_pLua, narg, nres, 0) != 0) { report_last_error(m_pLua, true); goto failed; } nres = -nres; while (*sig) { switch (*sig++) { case 'd': 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': 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': 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': 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': 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': 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); lua_settop(m_pLua, nSize1);
#if DEBUG_STACK 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倍吧!懶得測了,略過了...