« 心跳服务器 | 返回首页 | 明天去旅游了 »

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

@zzz654321 lua 并非线程安全模型,不可能在多线程环境下并发的传递数据。 你的做法即使采用某些方法正确运行,也不会取得你希望的多核收益。
现在用到 luajit2, 希望可以多线程, 具体如下: //简单的 LUA 多线程支持, sthread // sthread.run 开启新线程运行一个闭包, 返回线程 ID // 线程结束时, 返回值必须为一个闭包, 此闭包返回真正的返回值 // 使用 sthread.ret(ID ) 取出线程的返回闭包 // 如果线程正在运行, 返回空 // 如果线程闭包内出现错误, 返回值错误原因字符串 实现是用 lua_dump 闭包, 然后在线程的 lua_state 中 lua_load 来载入执行, 主要就卡在如何把闭包的 upvalue 传到 线程的 lua_state 中去, 请帮忙花时间指教一下, 谢谢
不能随意的时候?? 使用、?
table 不如 upvalue 轻量。表现在三个地方: 1. 访问效率更低 2. 占用更多内存 3. table 本身需要参于 gc 。
>"但是,往往 C 对象会对 lua 中其它对象做使用上的引用,而 userdata 本身不具备这个能力。" 为什么不直接用table呢? 个人觉得比upvalue要好一些. table里保留一个对userdata的引用便是了 - 在从栈里拿arg时, 取出保留的userdata就可以了.

Post a comment

非这个主题相关的留言请到:留言本