補齊遺失副本,時間線回歸
。
本文同步發表於個人網站
現在,我們來嘗試從C去執行一個Lua程式,Lua程式就用最簡單的Hello,並命名為hello.lua
print "Hello"
然後來寫C程式 -- hello_C.c。
需要下載含有標頭檔和函式庫的版本
#include "lua.h"
#include "lauxlib.h"
// new a lua VM
lua_State *L = luaL_newstate();
通常而言,不會全部開啟所有功能。
這只是範例,讓hello.lua檔案擁有所有能力。
// open all libraries 
luaL_openlibs(L);     
luaL_dofile(L, "hello.lua");
#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程式。
目錄結構:
接者要來嘗試從C實現以下Lua函數:
function hello(name)
  print("Hello, World")
end
不過在此之前,得先了解到,Lua與C交互,是抽象在一個平坦的記憶體堆疊空間,通常總是從堆疊上放存取會放置資料。寫過組合語言的可能會這種模式會有些熟悉。

int CHello(lua_State *L){
  const char *name = lua_tostring(L,-1);  // 從堆疊頂部取得一個字串
  lua_pop(L, 1);  // 將字串彈出堆疊
  printf("Hello, %s\n", name);
  return LUA_OK;
}
最好還去檢查輸入的參數型別是否正確。
在錯誤處理曾經處理過。
lua_register(L, "CHello", CHello);
CHello("World")
#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。
}
在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
div,他會被放至於堆疊頂部。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;  // 回傳結果
}
#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;  // 回傳結果
}
本小節參考: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 3.8.2,於LinuxMint 20.3執行的結果。
Pure Python init 29.92748188972473
Pure Python Sum 2.812898635864258
接著是使用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
Pure Python init 4.170244216918945
Pure Python Sum 0.04040670394897461
我沒有去嘗試Numpy和C的版本。其實還有Cython的方式可以加速Python程式。但就我看法,要為了加速而使用Lua有點不太有價值。在我的測試中,Lua初始化快了許多,但Lua的數值型態與Python的也有差異。Python的整數可以到非常之大,端看記憶體大小。當然,如果你確定你的資料值域,有可能使用Lua進行加速是恰當的。
userdata是從C語言定義的資料型態。分成兩種形式:
full userdata完全有Lua掌控,包含創建與記憶體回收。相對的light userdata其實就只是一個C指標而已。
本系列未打算對userdata多做講解,各位只要了解到其有兩種型態,並且所有操作,其實都是由C定義即可。