隨手扎
【30天Lua重拾筆記30】進階議題: 與C交互(+Python)
Hello, Lua & C
現在,我們來嘗試從C去執行一個Lua程式,Lua程式就用最簡單的Hello,並命名為hello.lua
print "Hello"
然後來寫C程式 – hello_C.c
。
引入標頭檔
需要下載含有標頭檔和函式庫的版本
#include "lua.h"
#include "lauxlib.h"
建立Lua虛擬機
// new a lua VM
lua_State *L = luaL_newstate();
打開預設的所有函式庫
通常而言,不會全部開啟所有功能。
這只是範例,讓hello.lua
檔案擁有所有能力。
// open all libraries
luaL_openlibs(L);
執行Lua檔案
luaL_dofile(L, "hello.lua");
完整C程式
#include "lua.h"
#include "lauxlib.h"
int main(int argc, char *argv[]){
// new a lua VM
lua_State *L = luaL_newstate();
// open all libraries
luaL_openlibs(L);
// dofile
luaL_dofile(L, "hello1");
return 0;
}
Hello
如此一來可以隨意更動
hello.lua
檔案,而無須重新編譯C程式。
目錄結構:
- :root:
- hello_C.c
- hello.lua
CHello - 從Lua執行C函數
接者要來嘗試從C實現以下Lua函數:
function hello(name)
print("Hello, World")
end
不過在此之前,得先了解到,Lua與C交互,是抽象在一個平坦的記憶體堆疊空間,通常總是從堆疊上放存取會放置資料。寫過組合語言的可能會這種模式會有些熟悉。
實現CHello
int CHello(lua_State *L){
const char *name = lua_tostring(L,-1); // 從堆疊頂部取得一個字串
lua_pop(L, 1); // 將字串彈出堆疊
printf("Hello, %s\n", name);
return LUA_OK;
}
最好還去檢查輸入的參數型別是否正確。
在錯誤處理曾經處理過。
註冊CHello
lua_register(L, "CHello", CHello);
在Lua使用CHello
CHello("World")
完整C程式
#include "lua.h"
#include "lauxlib.h"
#include <stdio.h>
int CHello(lua_State*);
int main(int argc, char *argv[]){
// new a lua VM
lua_State *L = luaL_newstate();
// open all libraries
luaL_openlibs(L);
// regist C function to Lua
lua_register(L, "CHello", CHello);
// dofile
luaL_dofile(L, "chello.lua");
return 0;
}
int CHello(lua_State *L){
const char *name = lua_tostring(L,-1); // 從堆疊頂部取得一個字串
lua_pop(L, 1); // 將字串彈出堆疊
printf("Hello, %s\n", name);
return 0; // no return. 多回傳值時,填入回傳數目。這個函數沒有回傳值,故為0。
}
目錄結構
- :root:
- chello.lua
- C_hello.c
使用Lua函數
在C無法除0:
double x = 1.0 / 0; // Error: 無法除0
雖然在C寫
1.0 / 0
會報錯,但是可以寫1 / 0.0
。這與C自動轉型有關。
Lua在這部份處理的更人性化,但儘管Lua可以直接寫1 / 0
,你仍應該知道背後的原因。
不過在Lua可以除0,會得到無限大:
x = 1 / 0 -- inf
雖然Lua的數字也有限制,但可能比C來的更安全。現在,希望使用部份Lua數值計算的能力:
function div(a,b)
return a/b
end
- 首先先取得需要執行的Lua函數–
div
,他會被放至於堆疊頂部。 - 然後在推入兩個要傳入的參數。
- 呼叫執行函式,並說明輸入2個參數,預取得1個回傳值。
- 從堆疊頂部取得一個回傳值。
lua_Number LuaDiv(lua_State *L, lua_Number a, lua_Number b){
int get_type = lua_getglobal(L, "div"); // get Lua Function
/* check th global variable -- div, is a Lua Function
if(get_type == LUA_TFUNCTION){
printf("[in C] is Lua Function.\n");
}
if(lua_isfunction(L, -1)){ // check top of stack is Lua Function
printf("[in C] top of stack is Lua Function.\n");
}
*/
lua_pushnumber(L, a); // pass paramter
lua_pushnumber(L, b); // pass paramter
lua_call(L, /*nargs = */ 2, /* nresult = */ 1); //兩個輸入,一個回傳值。
lua_Number result = lua_tonumber(L, -1); // 取得回傳值
lua_pop(L, 1);
return result; // 回傳結果
}
完整C函數
#include "lua.h"
#include "lauxlib.h"
lua_Number LuaDiv(lua_State *L, lua_Number a, lua_Number b);
int main(int argc, char *argv[]){
// new a lua VM
lua_State *L = luaL_newstate();
// open all libraries
luaL_openlibs(L);
// dofile
luaL_dofile(L, "div.lua"); // load Lua's div function
double r = LuaDiv(L, 1, 0);
printf("1 / 0 = %f\n", r);
return 0;
}
lua_Number LuaDiv(lua_State *L, lua_Number a, lua_Number b){
int get_type = lua_getglobal(L, "div"); // get Lua Function
/* check th global variable -- div, is a Lua Function
if(get_type == LUA_TFUNCTION){
printf("[in C] is Lua Function.\n");
}
if(lua_isfunction(L, -1)){ // check top of stack is Lua Function
printf("[in C] top of stack is Lua Function.\n");
}
*/
lua_pushnumber(L, a); // pass paramter
lua_pushnumber(L, b); // pass paramter
lua_call(L, /*nargs = */ 2, /* nresult = */ 1); //兩個輸入,一個回傳值。
lua_Number result = lua_tonumber(L, -1); // 取得回傳值
lua_pop(L, 1);
return result; // 回傳結果
}
在Python使用Lua
本小節參考:Lua:一個Python的秘密武器
我很好奇Lua到底能否作為Python的加速,照著Lua:一個Python的秘密武器做了一次。
import time
import random
size = 5000_000
st = time.time()
a = [random.randint(1, size) for _ in range(size)]
b = [random.randint(1, size) for _ in range(size)]
print("Pure Python init", time.time() - st)
def test():
for i in range(size):
if a[i] != b[i]:
a[i] = a[i] + b[i]
st = time.time()
test()
print("Pure Python Sum", time.time() - st)
python
以下是使用Python 3.8.2,於LinuxMint 20.3執行的結果。
Pure Python init 29.92748188972473
Pure Python Sum 2.812898635864258
lua
接著是使用lupa
的結果
from lupa import LuaRuntime
lua = LuaRuntime()
lua_code = r'''
function (size)
a = {}
b = {}
st = os.clock()
for i=0, size-1 do
a[i] = math.random(size)
end
for i=0, size-1 do
b[i] = math.random(size)
end
print("Lua init: " .. (os.clock() - st))
st = os.clock()
for i = 0, size - 1 do
if a[i] ~= b[i] then
a[i] = a[i] + b[i]
end
end
print("Lua sum: " .. (os.clock() - st))
end
'''
test = lua.eval(lua_code)
size = 5000_000
test(size)
Lua init: 2.650081
Lua sum: 1.75244
pypy3
Pure Python init 4.170244216918945
Pure Python Sum 0.04040670394897461
小節
我沒有去嘗試Numpy和C的版本。其實還有Cython的方式可以加速Python程式。但就我看法,要為了加速而使用Lua有點不太有價值。在我的測試中,Lua初始化快了許多,但Lua的數值型態與Python的也有差異。Python的整數可以到非常之大,端看記憶體大小。當然,如果你確定你的資料值域,有可能使用Lua進行加速是恰當的。
userdata
userdata
是從C語言定義的資料型態。分成兩種形式:
- full userdata
- light userdata
full userdata完全有Lua掌控,包含創建與記憶體回收。相對的light userdata其實就只是一個C指標而已。
本系列未打算對userdata
多做講解,各位只要了解到其有兩種型態,並且所有操作,其實都是由C定義即可。
參考資料
LINE Pay贊助 信用卡小額贊助