C++與Lua 交互 即時編譯器LuaJIT

2022-01-01 林元皓
原由

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 luajitmakesudo 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 代碼:

..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倍吧!懶得測了,略過了...

相關焦點

  • luajit VS C,運行性能超過C?
    數組遍歷查找、賦值通過上圖可以看到性能排行  C-O3 > C-O2 = luajit = C-O1 > C-O1 分析結論:luajit代碼運行效率大致相當於 C-O2 這個優化級別,運行效率是C代碼未優化代碼的250%左右。
  • luajit 開啟FFI + jit之後彪悍的性能
    最近為了優化服務部署結構需要優化這種類似「sidecar」的協助程序,所以將這個C模塊的功能又重新使用lua實現了一遍。在這次改造實現的過程中使用了FFI + jit 技術,同時lua代碼結構和C的代碼結構、數據結構保持一致。年前最後一個周末我抽空做了lua和C的代碼性能測試。測試效果非常令人驚嘆!!!
  • c/c++和lua的交互使用分享
    前言:嵌入式開發過程中,我們會使用一些腳本工具輔助我們的工作,例如shel或者python、lua等,今天給大家分享一下,我在工作中用到的lua腳本交互使用。除了方便,也考慮到lua是一個輕量級的腳本,支持交互調用,比如說我們可以通過代碼內部執行調用lua腳本函數,也可以在lua執行代碼註冊進去的函數。這個比shell和python有很多優勢,shell只能在它腳本生成的終端去執行以及python也是類似,無法進行雙方的函數交互調用。而lua可以交互調用,所以很方便。
  • Nginx 安裝 Lua 支持
    nginx查看是否啟動了 nginx: ps -ef | grep nginx 編譯 Nginx + Lua編譯 Nginx 需要先準備好下面的這些工具,如果不確定是否已安裝,可以在編譯的時候根據出現的錯誤提示再進行安裝 yum install -y gcc g++ gcc-c++
  • 用好Lua+Unity,讓性能飛起來—LuaJIT性能坑詳解(上)
    它不是簡單的像C++編譯器那樣直接把整套代碼翻譯成機器碼就完事了,因為這麼做有三個問題:1. 編譯時間長,這點比較好理解。2. 更關鍵的是,作為動態語言,難以優化。雖然網上有一些不修改LuaJIT的方案(http://www.freelists.org/post/luajit/Performance-degraded-significantly-when-enabling-JIT,9),在Lua中調用LuaJIT的jit.opt的api嘗試將內存空間分配給LuaJIT,但根據我們的測試,在Unity上這樣做仍然無法保證所有機器上能夠不出問題,因為這些方案的原理要搶在這些內存空間被用於其他用途前全部先分配給
  • 在Nginx使用Lua擴展功能
    download/nginx-1.10.1.tar.gz$ wget http://luajit.org/download/LuaJIT-2.0.4.tar.gz$ wget https://github.com/simpl/ngx_devel_kit/archive/v0.3.0.tar.gz$ wget https://github.com/openresty/lua-nginx-module/
  • cocos2dx lua 反編譯
    一般情況是libcocos2dlua.so,IDA 打開,函數窗口直接搜索applicationDidFinishLaunching,就能帶你飛,可惜只有loadChunksFromZip,沒有setXXTEAKeyAndSign,這保存解密KEY的被編譯優化了。怎麼辦?IDA字符串窗口幫你忙,編譯器編譯代碼的時候都是就近原則,只要是差不多地方出現的字符串,都會被放在一起。
  • Lua 快速上手指南
    clone https://github.com/LuaJIT/LuaJIT.gitcd LuaJITMACOSX_DEPLOYMENT_TARGET=11.2 make && sudo make install安裝成功之後會提示創建連結到 bin 目錄, 最好創建一個, 這樣可以直接在命令行裡使用 luajit
  • Unity中C#與Lua的交互
    Lua是一種嵌入式腳本語言,可以方便的與c/c++進行相互調用。
  • Lua 級別 CPU 火焰圖介紹
    Lua 以簡單、內存佔用小和運行效率高而著稱,尤其是在使用LuaJIT這樣的的即時編譯器 (JIT) 的時候。但有些時候,在 OpenResty 或 Nginx 伺服器上運行的Lua 代碼也會消耗過多的 CPU 資源。通常這是由於程式設計師的編程錯誤,比如調用了一些昂貴的 C/C++庫代碼,或者其他原因。
  • 用好Lua+Unity,讓性能飛起來——Lua與C#交互篇
    但即使這樣,實際使用中我們會發現,比起Cocos2dx時代Luajit的發揚光大,現在Lua+Unity的性能依然存在著相當大的瓶頸。我們可以最終把它優化成:lua_isnumber + lua_tonumber 4次,全部完成。 二、在Lua中引用C#的Object,代價昂貴從上面的例子可以看到,僅僅想從gameobj拿到一個transform,就已經有很昂貴的代價。
  • Unity開發之UI與Lua知識匯總!
    5、lua本身的協程不支持常用的 WaitForSeconds 的功能。xlua通過Coroutine_Runner.cs 這個文件實現了這個功能。二、lua和C#如何進行交互1、通過lua state堆棧進行交互。
  • nginx+lua 入門
    的最新版本為 2.1.0-beta3,於 2017 年 5 月 1 號發布,我們就使用這個版本:# wget http:# mv LuaJIT-2.1.0-beta3 LuaJIT-2.1# cd LuaJIT-2.1# make -j 10# make install# cd /usr/local/bin && ln -sf luajit
  • UE4精品教程 | slua unreal分析(一)LuaActor概覽
    簡介Slua unreal是騰訊提出的unreal引擎插件,支持使用lua語言進行unreal項目腳本開發,方便遊戲高效迭代開發,上線熱更新。slua可以用lua代碼實現藍圖中的功能邏輯,lua相比藍圖在版本控制、熱更新、多人協作上更有優勢。
  • 【Android 原創】扒盡手遊的底褲——Cocos2dx與LuaJIT完全解密
    編譯修改Luajit-lang-toolkit解包後發現所有的png資源全部加密了打不開(嘿,但是音頻還沒有加密……不過並不會有人看嘛)。結合lib發現遊戲是cocos2x與Lua編寫。雖然Lua出了名的好逆,但是查看文件頭後發現是編譯後的LuaJIT……還是最新的2.1版。一番操作後找到一個項目 luajit-lang-toolkit(以下簡稱LLT),不過用起來有點小問題。
  • Lua table(表)
    v .. " " end print(output)end-- 插入和移除-- 以table的插入和移除操作為例子local lang = {"java","c","c++","lua","python"}print("在插入之前的值為:")print_table(lang)-- 在插入之前的值為:-- java c c++ lua python -- 在末尾插入table.insert
  • 騰訊開源手遊熱更新方案,Unity3D下的Lua編程
    開發中我們往往要用到很多東西,比如用PB和後臺交互,解析json格式的配置文件等等。雖說我們都可以在C#那找到相應的庫,然後通過xLua去使用這些庫,但這效率不高,最好能有相應Lua的庫。那版本我們把虛擬機切換到luajit,加入了lazyload技術,逐行語句的優化,甚至關鍵地方不用C#提供的容器,自己寫專用的(比Dictionary實測性能高4倍)。。。可以認為我們重做了一個xLua。最終他們的選型測試結論是選xLua。後來和一些項目的交流發現,項目組很關注gc alloc這指標,甚至比lua和C#間的互調性能指標還要看重。
  • OpenResty-Lua基礎
    這樣的過程是非常耗時,這樣的話可以在某些情況下,一些功能不方便重新編寫的話,那麼就可以使用lua進行開發,進行功能的擴展。這樣新增功能或者是修復bug,直接將lua代碼推送到應用端,服務就直接運行新的代碼了。
  • Lua_C_API
    Iterators and Closures(迭代器和閉包)Lua是一門嵌入式的和可擴展的語言,C和Lua之間有兩種交互方式,第一種在C中調用Lua的庫函數,第二種Lua中調用C的庫函數。應用代碼(Application Code)和庫代碼(Lib Code)同Lua交流使用相同的API,就是所謂的C_API,C_API是一組能和Lua進行交互的函數、常量和類型。Lua獨立的解析器(lua.c)為應用代碼的實現,標準庫(lmathlih.c等)提供了庫代碼的實現,可以學習借鑑。
  • 深入理解 Lua 虛擬機
    為了達到較高的執行效率,lua 代碼並不是直接被 Lua 解釋器解釋執行,而是會先編譯為字節碼,然後再交給 lua 虛擬機去執行。lua 代碼稱為 chunk,編譯成的字節碼則稱為二進位 chunk(Binary chunk)。