lua cclosure 的 upvalue 数量限制
最近写的代码中出了一个奇怪的 bug ,很难调试出来。经过一个晚上的挣扎,终于发现了问题。
第一个问题,在 C 函数中,不能随意的时候 lua_State 中的虚拟机堆栈,如果需要大量使用堆栈,应该先调用 lua_checkstack 。少量使用堆栈,(在 LUA_MINSTACK 20 )之下时则没有问题。这个问题其实在文档里有写,我看过忘记了 :( 不过我个人还是觉得 lua_checkstack 的语义有点奇怪,从字面上看,这个 api 不应该有副作用。它能增加可用堆栈的大小违背了 checkstack 的词义。
第二个问题,当从 lua 调用 C 函数时,当参数数量不足的时候,并不会填入 nil 作为缺省参数。比如,写了一个 C 函数,接受两个参数。当 lua 中调用这个 C 函数时,如果仅传入一个参数,那么在 C 中 stack 上 index 2 位置的值并不一定是 nil 。这个时候我们应该用 lua_gettop 得到准确的参数个数以做适当的处理,或者直接在进入 C 函数时调用一次 lua_settop(L,2) 强制堆栈扩展到两个。
第三个问题,就是一开始最为迷惑我的问题。在生成 cclosure 的时候,upvalue 不能超过 255 个。而这一点并没在文档中说明,运行时压入超过 255 个 upvalue 也不会报错。知道仔细查看源码才发现其中的秘密。
按 lua 的设计,upvalue 的个数应该只受内存大小的限制。但是我的程序在生成一个拥有 258 个 upvalue 的 cclosure 时,upvalue 的个数变成了奇怪的 2 个。我的直觉告诉我,2 == 258%256 。查看源码证实了我的想法,cfunction 的 upvalue 的数量用了一个 byte 记录。究其原因,在于 lua 的 object 用了好几个标记量方便虚拟机运作(比如 gc 用到的 mark 位),当 upvalue 的数量也用一个 byte 的时候,刚好四个 byte 凑齐一个 32bit 字。
为什么我会用到如此之多的 upvalue ,这里就牵扯到最近发现的一个给 lua 写 C 扩展的技巧。
一般,C 对象保存在 lua 中都采用 userdata 的形式。userdata 会参与 gc 的过程,所以不必考虑内存释放的问题。但是,往往 C 对象会对 lua 中其它对象做使用上的引用,而 userdata 本身不具备这个能力。以前我的做法是,使用 lua_ref 得到一个整数引用,放在 userdata 中,到 userdata 的 __gc 被触发或者用户主动释放时,作 lua_unref 操作。这样做不太美观,也有一点点效率问题。
最近发现,简单的 C 扩展,我们完全可以用 C closure 实现,把扩展数据放在 upvalue 中。引用的 lua 对象可以直接放进 upvalue ,而其他 C 数据,可以再生成一个 userdata 写入 upvalue 。这个 closure 可以根据调用时的输入参数决定对它内部的数据做何种操作。
用这个技术,我实现了一个简单的循环队列,它会比用 lua table 实现的队列稍微高效一些。使用时,可以调用 C 扩展函数创建一个队列对象(其实是一个 c closure),然后用这个 closure 做进队列和出队列的操作。当传入参数的时候,就把参数进队列,不传参数的时候就把队首的元素出队列。同时可以根据返回值知道队列时候为空和为满。
具体的实现可以看 我的 wiki 上一篇文章
Comments
Posted by: Cloud | (5) December 11, 2012 05:36 PM
Posted by: zzz654321 | (4) December 11, 2012 03:50 PM
Posted by: zzz | (3) September 22, 2006 02:05 PM
Posted by: Cloud | (2) September 21, 2006 10:55 PM
Posted by: al_lea | (1) September 21, 2006 09:01 PM