KEEP K.I.S.S.

tk's blog

用 C 扩展写 Lua closure

这个是《programming in lua》中的有的一个例子,通过 closure(闭包)来实现一个 tuple(元组)。

元组是一种具有匿名字段的常量记录。可以用一个数字索引来检索某个字段,或者一次性检索所有字段。

这里的实现将元组表示为函数,元组的值存放在函数绑定的 upvalue 中。大概的 Lua 代码示意如下:

local tuple = {}
function tuple.new(...)
    local t = {...}
    return function(index)
        if index then
            if type(index) ~= "number" then error("invalid index type") end
            if index < 0 then error("index is out of range") end
            return t[index]            
        else
            return unpack(t)
        end
    end
end

闭包工厂 tuple.new 中的 table t 就是用于存放元组内容的 upvalue,这个 tuple 的使用如下:

local x = tuple.new(10, "hi", {}, 4)
print(x(1))
print(x(2))
print(x())

当用一个数字参数来调用此函数时,它会返回指定的字段。当不用参数来调用此函数时,则返回所有的字段。输出为:

10
hi
10 hi table: 0041B870 4
 
下面使用 Lua 的 C 扩展来实现这个 tuple:
#include <lua.h>
#include <lualib.h>
#include <lauxlib.h>

static int
t_tuple(lua_State *L) 
{
    int op = luaL_optint(L, 1, 0);
    if (op == 0) { /* 无参数? */
        int i;
        /* 将所有合法 upvalue 压入栈中 */
        for (i = 1; !lua_isnone(L, lua_upvalueindex(i)); i++)
            lua_pushvalue(L, lua_upvalueindex(i));

        return i - 1; /* 栈中值的数量 */
    }
    else { /* 获取 'op' 字段 */
        luaL_argcheck(L, 0 < op, 1, "index out of range");
        if (lua_isnone(L, lua_upvalueindex(op))) { /* 无此字段 */
            lua_pushnil(L);  /* 返回 nil 表明不存在 */
            return 1;
        }
        lua_pushvalue(L, lua_upvalueindex(op));
        return 1;
    }
}

static int 
t_new(lua_State *L)
{
    lua_pushcclosure(L, t_tuple, lua_gettop(L));
    return 1;
}

static const struct luaL_Reg tuplelib[] = {
    {"new", t_new},
    {NULL, NULL}
};

int luaopen_tuple(lua_State *L)
{
    luaL_register(L, "tuple", tuplelib);
    return 1;
}

编译成“tuple.dll”(windows 下)然后仍然用上面的 tuple 使用代码测试,不过要先“require 'tuple'”来加载这个扩展。DLL 中的需要导出 lutopen_tuple 这个函数符号。

t_new 是创建元组的工厂函数。将需要作为 upvalue 的参数实现压入栈中,然后调用 lua_pushcclosure 来创建一个基于 t_tuple 的 closure 。lua_gettop 返回栈的大小,在这里也就等于已经压入栈中作为 upvalue 的数量。lua_upvalueindex 是一个用来索引 upvalue 的宏,当索引一个不存在的 upvalue ,结果会是一个类型为 LUA_TNONE 的伪值(pseudo-value),当索引超出当前栈范围时候,也会得到这样的 LUA_TNONE 的伪值。不能以负数来调用 lua_upvalueindex ,所以做了范围检查,luaL_argcheck(L, 0 < op, 1, "index out of range") 第二个参数是个条件表达式,第三个是检查的参数数量,最后一个是检查失败传递的消息。

用于检测可选参数的函数是 luaL_optint ,它允许参数不存在,如果不存在,则返回一个指定的默认值,也就是最后一个参数。

Ruby 中的 Array Shuffle 算法

其实就是数组的洗牌算法,洗牌算法主要是生成一组互不相同的伪随机数列,可用于随机排序等等。

如果单纯依赖 C 库的 rand 去生成随机数来做的话,容易发生碰撞,尤其在需要生成的数列个数比较大的情况下。

Ruby 的 Array 有一个 shuffle 的方法,用于生成随机排序的元素新数组:

(1..12).to_a.shuffle 

=> [12, 9, 6, 5, 3, 1, 8, 4, 11, 7, 10, 2]

其实现如下:

static VALUE
rb_ary_shuffle_bang(VALUE ary)
{
    VALUE *ptr;
    long i = RARRAY_LEN(ary);
    rb_ary_modify(ary);
    ptr = RARRAY_PTR(ary);
    while (i) {
        long j = (long)(rb_genrand_real()*i);
        VALUE tmp = ptr[--i];
        ptr[i] = ptr[j];
        ptr[j] = tmp;
    }
    return ary;
} 

很简单,用 C 写个示例如下:

#include <stdlib.h>
#include <stdio.h>
#include <time.h>

#define ARRAY_SIZE(a)  ((sizeof(a) / sizeof(*(a))))

static void
shuffle(int *arr, size_t len)
{
    int i;
    i = len;
    while (i) {
        size_t j = (size_t)(rand() % i);
        i--;
        int tmp = arr[i];
        arr[i] = arr[j];
        arr[j] = tmp;       
    }
}

int main(int argc, char *argv[])
{
    int array[] = {
        1, 2, 3, 4, 5, 6,
        7, 8, 9, 10, 11, 12
    };
    srand(time(NULL));
    shuffle(array, ARRAY_SIZE(array));
    for (int i = 0; i < ARRAY_SIZE(array); i++) {
        printf("%d, ", array[i]);
    }
    printf("\n");
    return 0;
}

屏幕 IO 是很慢的

今天测试写的一个 lua 脚本,用于文件解析和输出报表,然后发现输出很慢,差不多要 400s 左右的时间,输出的行数越 10万行,总数据量 1.2MB。

然后发觉自己在运行命令里关掉了 stdout 的 buffer,但是开启后并没有多大的速度提升,然而,如果把输出重定向(cmd /c xxx > output.txt)后,速度有了质变,只需要 0.31s,其中 IO 只用了 0.17s。

然后我就 google 了一下,发现了这个解释,:http://stackoverflow.com/questions/3857052/why-is-printing-to-stdout-so-slow-can-it-be-sped-up(Why is printing to stdout so slow? Can it be sped up?)

大意就是 stdout(仅指屏幕 IO) 的实现里大多都是无缓冲或者小缓冲(行缓冲),所以大量的输出时候会很慢。

-------

多谢 依云兄的指正

给 lua 写 C 模块示例

主要内容出在《programming in lua》第二版 26章

本例是在 VS2008下,配合 lua on windows 包内的 lua 库使用。

1. 配置好 VS 的lua 环境,主要是引用的头文件目录,库目录,然后将 dll 加入 path 环境。

2. 新建一个 Win32 DLL 空项目(例如 "l_lib",这也是本例所提供的模块名),配置项目 链接器》输入》附加依赖库:lua5.1.lib

3. 添加一个 C 源文件:

#include <math.h>
#include <lua.h>
#include <lauxlib.h>
#include <lualib.h>

static int l_sin(lua_State *L) 
{
    double d = luaL_checknumber(L, 1);
    lua_pushnumber(L, sin(d));
    return 1;
}

static const struct luaL_Reg l_lib[] = {
    {"l_sin", l_sin},
    {NULL, NULL}
};

int luaopen_l_lib(lua_State *L)
{
    luaL_register(L, "l_lib", l_lib);
    return 1;
} 

4. 给项目添加一个模块定义文件(.def),用于导出dll 符号:

    LIBRARY "l_lib"
    EXPORTS
        luaopen_l_lib
 
5. 编译生成: l_lib.dll
​6. 编写一个测试的 lua 文件,与生成的 dll 在同一目录:
require 'l_lib'
print(l_lib.l_sin(1)) 
 
运行如果没有报错说明一切OK
 
------------------------------------- 分割线 ----------------------------------
如果只是给个步骤不给解释是不好玩的。
所有注册到 Lua 中的函数都具有相同的原型,该原型就是定义在 lua.h 中的 lua_CFunction:
typedef int (*lua_CFunction)(lua_State *L);
从 C 语言的观点来看,这个 C 函数仅有一个参数,即 Lua 的状态。它返回一个整数,表示其压入栈中的返回值数量。因此,这个函数无须在压入结果前清空栈。在它返回后,Lua 会自动删除栈中结果之下的内容。
上面C文件里的 l_sin 函数就是我们要提供的模块内部函数,其原型与 lua_CFunction 一致。
 
lua_open_l_lib 是模块被 require 时调用的函数,是一个模块的主函数,它的原型也是与 lua_CFunction 一致,它的职责是注册模块中所有的 C 函数,并且还应初始化模块中所有需要初始化的东西。这个函数的名字是特定的,与模块名称有关。
 
luaL_Reg 结构数组是包含模块中所有函数以及名称的容器,luaL_Reg 结构有两个字段,一个函数名字符串(提供给调用模块的用户)以及一个函数指针。该数组中最后一个元素总是 {NULL, NULL} 以此标识结尾。该结构体是在辅助库内定义的。
 
模块主函数中调用了辅助库的 luaL_register 用于注册模块,它根据给定的名称("l_lib")创建一个table,并用数组 l_lib 中的信息填充这个table。在luaL_register 返回时,它会把这个table留在栈中。最后主函数返回1,表示将这个table返回给Lua。
 
通常一个模块只有一个公共函数,就是模块主函数,其他函数都应该是私有的,在 C 中就是 static。

 

lua 之爱

《programming in lua》第二版的中文版这本书快看完了,中间夹杂着英文版的看,因为翻译并非完美,而且英文版的也很容易看懂。

这里是我的读书笔记:http://book.douban.com/people/tisyang/annotation/3076942/ 但并非每个要点都会有笔记。

lua 是一个值得学习的语言而且也不会需要过多时间,这本书是学 lua 一定要看的,云风 大神也这样推荐

愈学愈爱