iT邦幫忙

第 12 屆 iT 邦幫忙鐵人賽

DAY 30
1
Software Development

30天 Lua重拾筆記系列 第 30

【30天Lua重拾筆記30】進階議題: 與C交互(+Python)

補齊遺失副本,時間線回歸/images/emoticon/emoticon08.gif
本文同步發表於個人網站

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
  1. 首先先取得需要執行的Lua函數--div,他會被放至於堆疊頂部。
  2. 然後在推入兩個要傳入的參數。
  3. 呼叫執行函式,並說明輸入2個參數,預取得1個回傳值。
  4. 從堆疊頂部取得一個回傳值。
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定義即可。

參考資料


上一篇
【30天Lua重拾筆記28】進階議題: Meta Programming
下一篇
【30天Lua重拾筆記31】進階議題: 記憶體回收&弱表
系列文
30天 Lua重拾筆記36

1 則留言

0
雷N
iT邦新手 4 級 ‧ 2020-10-13 20:29:12

恭喜完賽

lagagain iT邦新手 4 級 ‧ 2020-10-13 23:25:16 檢舉

/images/emoticon/emoticon12.gif

GrantLi iT邦新手 5 級 ‧ 2020-10-13 23:50:05 檢舉

恭喜完賽 最後一天還是一樣認真/images/emoticon/emoticon39.gif

我要留言

立即登入留言