【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定义即可。

参考资料


<<:  Unity - 互动功能(终章)

>>:  Day 30. Hugo 系列文回顾,铁人赛反省与获得

[Day 26] Final Project (2/5) — 准备开始

接下来几天的文章会像料理节目一样,用我事先准备好的材料 (模型、App...) 来进行说明,底下就来...

【PHP Telegram Bot】Day23 - Inline mode(内联模式):在输入框使用机器人

虽然还有另一个按纽,但是今天我想先来玩玩内联模式 前置作业 还记得 Day04 - Telegra...

【领域展开 18 式】 我的 Bluehost 帐密不是我 WordPress 的帐密

因缘际会,写这次铁人赛内容时会使用不同台电脑做登入操作 WordPress 上的功能,因此会开设无痕...

Day18 - CheckBox

CheckBox中文叫核取方块 看过很多次这种元件,但一直不知道中文叫什麽 这也是一种选择的元件 和...

後记

这是铁人赛的最後一篇文章,我想在这个结尾分享为什麽我会写这个主题,这是因为几个月前的某一天我正在与跨...