如何做protobuf如何用共享内存

云风的 BLOG: lua与虚拟机 Archives
我的图书馆
云风的 BLOG: lua与虚拟机 Archives
December 30, 2013
Lua 远程调试器
我们现在的手游完全用 Lua 开发,这就有了调试的需要。
今年曾写过一个,主要是用于服务器开发。服务器程序不适合完全 stop the world 慢慢调试,以输出 log 为主。但现在在客户端,那么一个类 gdb 的调试环境更好一些。
。从网易出来后没带代码,需要用就要重新写了。好在 lua 的 debug 接口非常全,今天花了 2 个小时就重新实现了一个简陋的雏形。
Posted by 云风 at 06:05 PM |
September 26, 2013
Lua 5.2 新增的分代 GC
以前我在 blog 写过
,而 Lua 5.2 对这部分代码改动颇多,暂时也没有精力更新这个系列,先挑重点写吧。
Lua 5.2 的 GC 的最大改进是增加了一种叫 generational 的模式,Lua 的官方文档里是这样解释的。
As an experimental feature in Lua 5.2, you can change the collector's operation mode from incremental to generational. A generational collector assumes that most objects die young, and therefore it traverses only young (recently created) objects. This behavior can reduce the time used by the collector, but also increases memory usage (as old dead objects may accumulate). To mitigate this second problem, from time to time the generational collector performs a full collection. Remember that this is an
you are welcome to try it, but check your gains.
Posted by 云风 at 03:11 PM |
August 26, 2013
去掉 full userdata 的 GC 元方法
根据 Lua 文档中的说法,lightuserdata 比 fulluserdata 要廉价一些。那么,其中的区别在哪里呢?
空间开销上,fulluserdata 是一个 GC 对象,所以比 lightuserdata 要多消耗一点内存,这点内存往往对程序不造成太大的影响。
时间开销上,fulluserdata 在访问它时和 lightuserdata 并无太大区别,它们都只能通过元方法才能在 Lua 中使用。所有 lightuserdata 共用一个元表,不如 fulluserdata 灵活,在元表访问效率上却是几乎相同的。对程序性能有影响的部分在于它们对 GC 环节的开销不同。
fulluserdata 本身是一个 GC 对象,所以在扫描的时候要复杂一些。它可能有附带的 uservalue 需要扫描,但不设置 uservalue 几乎就没有额外的扫描开销了。当 fulluserdata 有 gc 元方法后,就给 GC 流程增加了额外的负担。GC 模块需要额外记录一个链表来串接起所有有 gc 元方法的对象,推迟到 gc 的最后环节依次调用。
对于对延迟相当敏感的游戏程序来说,最容易造成运行过程中瞬间延迟增加,却又很难控制的部分就是 GC 了。所以我们在开发中经常需要关注怎样合理的使用 Lua 避免 GC 的负担过大。
Posted by 云风 at 04:57 PM |
July 25, 2013
coroutine 的回收利用
这几天在 lua 和 luajit 的邮件列表上有人讨论 coroutine 的再利用问题。
前几天有个用 skynet 的同学给我写了封邮件,说他的 skynet 服务在产生了 6 万次 timeout 后,内存上升到了 50M 直到 gc 才下降。
这些让我重新考虑 skynet 的消息处理模块。skynet 对每条消息的相应都产生了一个新的 coroutine ,这样才能在消息处理流程中,可以方便的切换出去让调度器调度。诸如 RPC/ socket 读写这些 api 才能在用起来看成是同步调用,却在实现上不阻塞线程。
读源码可知,lua 的 coroutine 非常轻量(luajit 的略重)。但依旧有一些代价。频繁的动态生成 coroutine 对象也会对 gc 造成一定的负担。所以我今天花了一点时间优化了这个问题。
简单说,就是用自己写的 co_create 函数替换掉 coroutine.create 来构建 coroutine 。在原来的主函数上包裹一层。主函数运行完后,抛出一个 EXIT 消息表示主函数运行完毕。并把自己放到池中。如果池中有可利用的旧 coroutine ,则可以传入新的主函数重新利用之。
为了简化设计,如果 coroutine 中抛出异常,就废弃掉这个 coroutine 不再重复利用。为了防止 coroutine 池引用了死对象,需要在主函数运行完后,把主函数引用清空,等待替换。
具体实现 。
ps. coroutine poll 故意没实现成弱表,而是在相应 debug GC 消息时再主动清空。
Posted by 云风 at 03:00 PM |
July 04, 2013
回调还是消息队列
前几天在做 Hive 的 socket 库的时候, 遇到一个问题很典型,我记得不是第一次遇到了。值得记录一下。
socket 底层有一个 poll 的 api ,通过 epoll 或 kqueue 或 select 取得一系列的事件。用 lua 怎么封装它呢?
一个比较直接的想法是注入一个 callback function ,对于每个事件回调一个 lua 函数。但这容易引起许多复杂的问题。因为回调函数很不可控,内部可能抛出异常,也可能引起函数重入,或是做了一些你不喜欢去做的事情。
如果面面俱到,就会让原本 C/Lua 边界的性能问题更加恶化。
所以,我采用了方案二:把所有事件以及相关数据全部返回,让后续的 Lua 代码去处理 C 层获取的所有事件。
这个方案也容易造成性能问题,那就是临时构件复杂数据结构,对 Lua VM 的 GC 造成的压力。
Posted by 云风 at 06:14 PM |
June 26, 2013
Hive , Lua 的 actor 模型
上个周末我一直在想,经过一年多在
上的开发,我已经有许多相关经验了。如果没有早期 erlang 版本的历史包袱以及刚开始设计 skynet 时的经验不足,去掉那些不必要的特性后的 skynet 应该是怎样的。
一个精简过代码的 skynet 不需要支持 Lua 之外的语言和通讯协议。如果某个服务的性能很关键,那么可以用 C 编写一个 Lua 库,只让 Lua 做消息分发。如果需要发送自定义协议的消息,可以把这个消息打包为一个 C 结构,然后把 C 结构指针编码在发送的消息中。
skynet 的内部控制指令全部可以移到一个系统服务中,用 Lua 编写。
跨机支持不是必要的。如果需要在多个进程/机器上运行多份协同工作,可以通过编写一个跨机通讯的服务来完成。虽然会增加一个间接层使跨进程通讯代价更大,但是可以简化许多代码。
广播也不是基础设施,直接用循环发送复制的消息即可。为了必要过大的消息在广播过程中反复拷贝,可以把需要广播的消息先打包为 C 对象,然后仅广播这个 C 对象的指针即可。
Posted by 云风 at 01:15 PM |
June 18, 2013
MongoDB lua driver
唔,我知道有人已经做了 MongoDB 的 lua driver ,比如 。但我不想仅仅是对 C++ API 的封装,而想从协议层做起,这样日后可以方便改为异步模型,也好整合到
这里还有一份 的实现,是从协议层做的封装。但有几个问题,一是依赖 lua-socket 库,二是纯 lua 实现不如 C 库性能好,三是特性没有支持完整。
我。做的过程中发现 c driver 代码质量不高,且特性支持不完整,最终我考虑自己从开始重新做一份。
Posted by 云风 at 12:05 PM |
June 14, 2013
写了一个 lua bson 库
的官方网站上链接了一个,但是实现的不完整。
我用 C 实现了一个
设计的结构化数据序列化协议,所以有很多设定是为 mongodb 服务的,如果单用于序列化结构化数据,那么那些不一定要实现。但我写这个的最终目的是做一个 lua 的 mongo driver ,所以就实现的比较完整了。
bson 结构中,有一些固定长度的字段,修改它们不必重新编码。这在 mongDB 的通讯协议中非常有用,所以我也加了对应的接口。还有许多特性可能会有用,比如把两个 bson document 连接成一个之类的,等我在写 mongoDB driver 的时候,视情况实现。
Posted by 云风 at 12:51 PM |
June 09, 2013
用栈方式管理 Lua 中的 C 对象
最近思考了给 Lua 写 C 扩展的另一个问题。
我曾经总结过几种 Lua C 库中
。最近想到另一个方案,虽然实现后并没有用到项目里,但值得记录一下。
Lua 没有 RAII ,一切对象的回收是依赖 GC 的。封装 C/C++ 对象则一般用 userdata 。userdata 比较重,作为临时对象使用总觉得有点别扭。比如封装 matrix 对象,如果我们为每个 matrix 对象都生成一个 userdata ,那么一些临时的 matrix 对象就会一直推迟到 GC 发生的时候才回收。而在 C/C++ 这样的语言中,临时对象通常是在离开调用层次时自动释放的。
对于某些 C 和 Lua 混合的业务也有这样的问题。某些较长的业务流程,一部分环节由于性能原因使用 C 来实现,另一部分更适合直接用 Lua 。我们必须用 userdata 来交换中间状态。比如处理一个 C 层次上产生的数据包或 C 结构数据,交由 Lua 处理后,C 对象就没有必要再存在了。但处理过程中,Lua 代码则需要反复引用和处理它。
多数情况下,我们不用太考虑这两者间的差别。但这并不妨碍我去考虑有没有可能在 Lua 中模拟一套栈对象的管理机制。它可能是 GC 系统之外的一种对象生命期管理的选择。
Posted by 云风 at 03:51 PM |
June 07, 2013
MongoDB 的 Lua Driver
最近听从同事建议想尝试一下 MongoDB 。
前年,图灵的同学送过我一本《》 ,当时我花了两个晚上看完。我所有的认知就是这本书了。我们最近的合作项目
也是用的 MongoDB ,最近封测阶段,关于数据库部分也出过许多问题。蜗牛同学在帮助成都的同学做调优,做了不少工作。总是能在办公室里听到关于 MongoDB 的话题。
做一个 MongoDB 的 Driver 。
Skynet 默认是用 lua 做开发语言的。那么为什么不直接用
因为 skynet 需要一个异步库,不希望一个 service 在做数据库操作的时候被阻塞住。那么,我们就不可能直接把 luamongo 作为库的形式提供给 lua 使用。
一个简单的方法是 skynet 目前对 redis 做的封装那样(当然,skynet 中的 redis 封装也是非阻塞的),提供一个独立的 service 去访问数据库,然后其它服务器向它发送异步请求。如果我直接使用 luamongo 就会出现一个问题:
我需要先把请求从 lua table 序列化,发送给和 mongoDB 交互的位置,反序列化后再把 lua table 打包成 bson 。获得 MongoDB 的反馈后,又需要逆向这个流程。这是非常低效的事情。如果我们可以直接让请求方生成 bson 对象,这样就可以直接把 bson 对象的指针发过到 交互模块就够了( skynet 是单进程模型,可以在服务内直接交换 C 指针)。这就需要我定制一套 lua moogodb 的 driver 了。
Posted by 云风 at 06:55 PM |
May 16, 2013
介绍几个和 Lua 有关的东西
最近有份工作是需要把 Lua 中的数据结构以某种特定的格式输出为文本的,所以就用到了
这是个代码生成工作的利器。
可能是用的人不多,所以还略显不完整。用的时候发现一些个小问题,原来以为是 bug,读了源代码后发现是个 feature 。但是觉得这个 feature 不太合理,就上 github 上留言。作者倒挺爽快,马上表示赞同并去掉了。
。这个东西原本是 luajit 的一部分,可好多人确是冲着 ffi 库去用 luajit 的。
luajit 目前尚有不少的局限性,比如内存只能用 32 位寻址,不支持 lua 5.2 的 api 等。另外,从稳定性上来说,也不如原版的 lua 更让人放心。
据我所之,我们合作的 的服务器端就为了 ffi 使用了 luajit ,却担心稳定性问题,把 jit 功能关闭了。
这类项目,未必是稀罕 luajit 的性能,更多的是贪图用 ffi 写 binding 的便捷才启用 luajit 的。
Posted by 云风 at 01:06 PM |
May 06, 2013
招聘 Lua 开发人员一名
5 月 16 日 注:由于已经收到足够多的简历,所以招聘提前终止,谢谢大家的热情。
招聘网络游戏服务端开发人员一名(截至到 2013 年 6 月 1 日)。
基本要求:有至少原创 1000 行以上 Lua 语言编程经验,一万行 C/C++ 语言编程经验。有网络服务开发经验:可以独立解决问题(包括但不限于设计合理的通讯协议,评估其效率及安全性)。
有游戏行业从业经验两年以上可以加分。
有兴趣且满足基本要求的同学,可以 email 和我联系获得更详细的信息。
Posted by 云风 at 04:55 PM |
April 17, 2013
Lua 5.2.2 中的一处 Bug
前几天, Lua 5.2.2 发布了, 主要是修复了 4 个 Lua 5.2.1 中已知的 bug . 其中包括前段时间一个同学和我在 email 交流中讨论的一个问题.
我把 Lua 5.2.2 更新到公司项目的主干上,同时需要对我的那本 《》做一些更新,需要把这次的代码更改同步到书里去。这个工作很繁琐,但有它的价值。比如我发现了 Lua 5.2.2 比 5.2.1 的更改远不只官方宣布了 4 处 bugfix ,还有一些小调整,让 Lua 的源码更规整一些。
阿楠同学因为这段时间一直在维护
这个 C# 版的 Lua 项目,我就随便和他通告了一下这次的一些代码变更,方便他同步到 UniLua 项目中去。
讨论之中,他提到 luaD_precall 函数的实现有些诡异之处,没有看明白。我顺着他指出的位置又仔细阅读了一下,果然发现这里存在一个隐藏很深的 Bug 。
Posted by 云风 at 04:59 PM |
February 01, 2013
C# 版的 Lua
我们游戏客户端使用了 Unity3D , 我们不打算给它写 C 插件, 所有的开发都在 mono 中进行的。
由于某些需求,我们需要在客户端解析一些 Lua 脚本(这些脚本同时供我们的服务器使用)。所以,就有了阿楠同学开发的
这个世界上已经有了很多的 .net 版的 Lua 实现,但是都不完整。它们大多是基于 Lua 5.1 甚至更老的版本的。还有一些只能解析 Lua 的字节码(这样很容易实现),而不能让 Lua 源代码直接工作起来。这使得在 Lua 中很常见的 meta 编程变得不可用了。
Posted by 云风 at 02:04 PM |
January 30, 2013
温故而知新
我上次通读 Lua 的源代码时,Lua 还在 5.1 。当然 Lua 5.0 我也读过,4.0 和 3.2 则读的不多。
最近有一点空闲,想续写我那本 Lua 源码欣赏。按我心里的计划,还有大约 6 章。虚拟机、字节码持久化、C API 、解释器、GC、库函数。
新添了一章关于虚拟机的,所以重新读了一遍相关源码。发现 Lua 5.2 比上一版修改了不少,几乎每个位置都有修订。
自己读代码和写出来给人看又是不同,真的逐行推敲的话,之前的理解也是经不起琢磨的。为什么要写这一行;为什么这一行在这个位置,而不是在后面;为什么要这么实现,而不是那样实现……
一边写,一边发现对别处的引用会引发新的疑问,继而需要对之前已完成的章节做一些修补。
上一次发布 pdf 时,采用的是日后纸质书的版式。留白太多对于电子阅读其实是很浪费的,读代码尤其不好。所以这次重新排了一下。
这次主要是增加了关于 VM 的新章节。
有兴趣的同学可以下载:《》。但我不建议现在开始阅读,尤其是对不仅仅想随便翻翻的同学。因为我经常修改它,今天看到的版本,可能写完后已经改了不少了。
btw, 在我写完后,发现最近有另一个同学也在写类似的文章。这里给出,有兴趣的同学可以看看。
Posted by 云风 at 01:25 PM |
January 10, 2013
为 Lua 绑定 C/C++ 对象
如何绑定 C/C++ 对象到 Lua 里?通常是创建一个 userdata ,存放 C/C++ 对象指针,然后给 userdata 添加元表,用 index 元方法映射 C/C++ 中的对象方法。
也有另一个手段,直接用 lightuserdata 保存 C/C++ 对象指针放到 Lua 中,在 Lua 中创建一个 table 附加元表来来包装这个指针,效果是类似的。区别在于对象生命期的管理方式有所不同。就这个问题,几年前我 。
绑定 C/C++ 对象到 Lua 里的设计难点往往在这个正确的生命期管理上。因为 C/C++ 没有 GC 系统,依赖手工管理资源;而 Lua 则是利用 GC 做自动回收。这两者的差异容易导致在 Lua 中的对象对应的 C/C++ 对象已经销毁而 Lua 层不自知,或 Lua 层中已无对象之引用,而 C/C++ 层中却未能及时回收资源而造成内存泄露。
理清这个问题,首先你要确定,你打算以 Lua 为主干来维护对象的生命期,还是以 C/C++ 层为主干 Lua 部分只是做一些对这些对象的行为控制。
我个人主张围绕 Lua 来开发,C/C++ 只是写一些性能相关的库供 Lua 调用,即框架层在 Lua 中。这样,C/C++ 层只提供对象的创建和销毁函数,不要用 C 指针做对象的相互引用。Lua 中对象被回收时,销毁对应的 C 对象即可。
但是,也有相当多的项目做不到这点。Lua 是在后期引入的,之前 C/C++ 框架层中已做好了相当之复杂的对象管理。或者构架师不希望把脚本层过多的侵入引擎的设计。
那么,下面给出另一个方案。
我们将包装进 Lua 的 C 对象称为 script object ,那么只需要提供三个函数即可。
Posted by 云风 at 11:57 AM |
December 14, 2012
因为内存限制问题, 我们暂时放弃了 luajit 。这两天,我想另辟蹊径找到别的方法去加速 lua 程序的运行。
所以我这两天做了这么一个玩具,试一下是否可行。
的东西,它允许你在 Lua 代码中直接写 C 代码。由于是用 tcc 运行时编译运行的,所以你可以获得和 C 一样的效率。(同样,C 语言引入的问题也同样要考虑)
之所以我称之为玩具,是因为它现在还不支持复杂的数据结构。你只能把单层的,以 string 为 key 的 lua table 映射到 C 代码中(表现为一个 user type ,其实是一个 C struct )。目前还不能用数组做数据交互。
它可以利用一个内建类型 object 来持有传递 lua 的对象,但不能操作它。
有兴趣做进一步完善的同学,可以。
Posted by 云风 at 03:31 PM |
December 12, 2012
一个 Lua 内存泄露检查工具
昨天我们发现每日构建的服务器突然在一个晚上内存暴增了 8 G ,显然是发生了内存泄露。
之前,我们在 skynet 里留下了许多调试协议,使我们很快的确定了发生泄露的服务:在一张地图的 lua State 中。可以确定是地图的 lua 实现中,有些 lua 对象在不断的生成。生成速度不快,但确实没有人解开引用,导致内存持续增长。
曾经有很多人做过 Lua 的内存分析工具,但是我懒的去搜了,花了半天时间自己写了一个。()
原理是这样的:
Posted by 云风 at 02:16 PM |
November 09, 2012
Lua 字节码与字符串的共享
我们的系统的应用场合比较特殊,在同一个进程内存在数千个 lua_State 。
Lua 的虚拟机占用的内存已经足够小了,但还是抗不住数量多啊。所以我希望有版本节约一些内存。
最想做的一件事情是把不同 lua_State 中相同的函数字节码合并起来共用一块内存。要做到这一点并不复杂。而且可以提高一些内存访问的效率。(因为大部分 lua 程序在并行执行相同的逻辑)
首先我们需要准备一个用来共享数据块的模块,它必须是线程安全的。因为既然分到了不同的 lua_State 就是想利用并发的优势。针对这个特定需求定制这样一个模块可以做到 lock-free 。
Posted by 云风 at 04:54 PM |
November 01, 2012
开发笔记(28) : 重构优化
正如 记载的,我们第 2 里程碑按计划在 9 月 30 日完成,但因为赶进度,有许多 bug 。性能方面也有很大问题,大家都认为需要重构许多模块。所以,在最后几天修补 bug 时,许多补丁是临时对付的(因为整个模块都需要重写了)。为此,我们留下了一个月专门重构代码、修改 bug 、并对最后的结果再做一次评测。
这项工作,终于如期完成了。
半个多月前在白板上留下的工作计划还没擦掉。我列出了 12 点需要改进或重写的地方,考虑到内容较多,又去掉了 3 项。在大家的通力合作下,完成的很顺利。
,我们的老系统处理 80 人同一战场混战就让服务器支撑不住了。当时我们的服务器 CPU 达到了 790% 。虽然我们的服务器硬件比较老,配置的是两块 Intel Xeon E5310 @ 1.60GHz ,更新硬件可以有所改善。但这个结果绝对是不能满意的。从那个时候起,我从重写最底层框架开始一步步起着手优化。
昨天的测试结果基本让人满意,在同一台机器上,200 个机器人的混战 CPU 占用率平均仅 130% 左右,而机器人 client 边数据包延迟只有 1 秒,完全可以实用。这离我们的设计目标 ( 500 人同战场流畅战斗)还有一些距离,但考虑到今年新配置两块 Intel Xeon E5-2620 @ 2.00GHz 的话,按其性能指标,应当再有至少一倍的性能提升。
ps. 参考 ,我们计划采购的 [Dual CPU] Intel Xeon E5-2620 @ 2.00GHz Benchmark 16707 分,而目前使用的 [Dual CPU] Intel Xeon E5310 @ 1.60GHz 仅 4160 分。即使仅考虑单线程分数,也在两倍以上。
Posted by 云风 at 11:19 AM |
October 25, 2012
让 LuaJIT 2.0 支持 Lua 5.2 中的 _ENV 特性
我们的项目是用 Lua 5.2 标准来写的, 最近想迁移到 LuaJIT 2.0 中。其中碰到的最大障碍是,LuaJIT 2.0 不支持 Lua 5.2 中的 _ENV 特性。而且,看起来将来也不会支持了。
在邮件列表中,LuaJIT 的作者 。
可是我真的需要它,所以只好自己阅读 luajit 的源代码,给它打了个 patch 支持这个特性。
patch (基于 luajit 2.0 的 beta 11) 如下:
Posted by 云风 at 03:07 PM |
September 12, 2012
Lua 5.2 的细节改变
最近想试一下, Lua JIT 2.0 能给我们的系统带来多大的提升。但可惜的是,我们一开始就在用 Lua 5.2 来构建系统,而 Lua JIT 2.0 只支持 Lua 5.1 的 API ,在可以看到的时间里,恐怕也不太会去支持 5.2 了。
所以,我只能想办法反向支持 Lua 5.1 。
语法层面最重大的改变是 Lua 5.2 取消了环境表这个概念,转而提供 _ENV 这个语法糖。
许多小细节是 C API 上的变化。这使得按 Lua 5.2 标准写的 C 库,无法在 Lua 5.1 环境下编译。我打算用 Lua 5.1 的 API 来模拟出来。
Posted by 云风 at 09:02 PM |
July 29, 2012
开发笔记(24) : Lua State 间的数据共享
最近工作展开后, 我们一共有 10 名程序员在目前的项目上工作。我暂时没有和其他人有依赖关系的工作,最近一周在改进以前做的一些东西,在不修改接口的前提下,争取提供更高的性能,以及完成一些之前没完成的功能,为以后的扩展做准备。
最近值得一提的东西是:关于我们的共享储存的数据结构。
最早在设计的时候,是按多进程共享的需求来设计的。希望不同的进程可以利用共享内存来共享一组结构化数据。所以 。这个东西实现的难点在于:一、共享内存不一定在不同进程间有相同的地址,所以不能在结构中用指针保持引用关系;二、不希望有太复杂的锁来保证并发读写的安全性。
后来,我们采用了 Erlang 做底层的框架。在同一台机器上,只有一个系统进程。所以,这个东西可以不必实现的这么复杂。我抽了三天实现,重新实现了一个。这次不考虑跨进程的问题,只在同一进程的不同线程中,让独立的 Lua State 可以访问同一份结构化数据。至于结构化数据支持到怎样的数据类型,我认为和 Lua 原有的 table 类型大致一致就可以了。
最后,就完成了这么一个东西。我认为到目前这个阶段,这个模块还是比较独立的,适合开源分享。以后的工作可能会和我们具体项目的模块整合在一起,还需要做一些修改,就不太适合公开了。有兴趣的同学可以在我的 github 上看到代码。 。
Posted by 云风 at 04:12 PM |
July 19, 2012
开发笔记(23) : 原子字典
问题是早就提出了的。在
中,就写到一个需求:一个玩家数据的写入者,可以批量修改他的属性。但是,同时可能有其他线程在读这个玩家的数据(通过共享内存)。这可能造成,读方得到了不完整的数据。
我们可以不在乎读方得到某个时间的旧数据,但不可以读到一份不完整的版本。就是说,对玩家数据的修改,需要成组的修改,每组修改必须是原子的。
起先,我想用读写锁来解决这个问题。方案想好了,一直没有实现。只是把读写锁的基本功能实现了。
这几天这个问题被重提出来。因为,前段我们都采用了鸵鸟政策,当问题不存在(事实上我们也没有发现实际中出现可观测到的问题)。
反正探讨了好几个解决方案,一开始都是围绕怎么加锁,锁的粒度有多大来展开的。甚至,我们把其中的一种方案都实现出来了,并写了压力测试程序测试。不过,这些方案都不太令人满意。大家担心锁的开销,以及逻辑代码编写者所需求关心的问题太多,导致有死锁的可能性。
昨天差一点决定用一个地图锁来解决这个问题,就是用牺牲同一个地图进程上,玩家间并行的可能性为代价的。这个方案也不无不可。但昨晚躺在床上一直睡不安稳。因为这样做,就失去了一开始我期望用并行方案来设计游戏服务器的初衷。如果这样,还不如全部退化到单地图单进程来编写程序。那么一定有方法是可以避开锁以及避免让写逻辑的程序员去关心数据共享的读写冲突问题的。
Posted by 云风 at 09:58 PM |
July 11, 2012
Lua 5.2.1 的一处改变
Lua 5.2.1 正式发布有段时间了。虽然相对于 5.2.0 只是一个小版本的提升,但也是有些东西可以拿出来讲讲的。
比如,在这次小版本更新中,字符串类型被分为了长字符串和短字符串两类。长字符串(大于 40 字节的字符串),不再做内部化处理了。
一开始我以为这是为了性能的一处小改进,可以在字符串处理比较多的场合,少做一些 hash 计算和 hash 表插入。后来查了一下邮件列表发现,其实是为了安全性,防止别人做
攻击。一起改变的是字符串的 hash 过程使用了一个随机种子。默认设定和时间有关。值得注意的是,这处改变可能会使得嵌入 lua 的程序每次运行的内存状态不一致,有可能给调试带来一定的麻烦。
Posted by 云风 at 09:14 PM |
July 09, 2012
在 C 中设置 Lua 回调函数引起的一处 bug
我们的提供了一个 C 接口, 在 RPC 调用时, 回调一个事先注册的函数.
C 中标准的回调函数的接口设计, 标准方法是设置一个 C 函数指针加一个 void * 类型的数据指针.
由于我们的游戏逻辑使用 Lua 来实现, 所以这里只需要实现一个 C 函数去调 Lua 机里的函数, 而对应的 void * 自然就是 lua_State * 。
今天,同事在实现服务的热更新功能。发现多次热更新 lua 写的服务会导致一处 core dump ,一直没有找到原因。通过阅读代码,我仔细思考后,确定了 bug 所在。
Posted by 云风 at 08:23 PM |
June 21, 2012
Lua 5.2 如何实现 C 调用中的 Continuation
Lua 5.2 最重大的改进,莫过于 "yieldable pcall and metamethods" 。这需要克服一个难题:如何在 C 函数调用中,正确的 yield 回 resume 调用的位置。
resume 的发起总是通过一次 lua_resume 的调用,在 Lua 5.1 以前,yield 的调用必定结束于一次 lua_yield 调用,而调用它的 C 函数必须立刻返回。中间不能有任何 C 函数执行到中途的状态。这样,Lua VM 才能正常工作。
(C)lua_resume -& Lua functions -& coroutine.yield
-& (C)lua_yield -& (C) return
在这个流程中,无论 Lua functions 有多少层,都被 lua state 中的 lua stack 管理。所以当最后 C return 返回到最初 resume 点 ,都不存在什么问题,可以让下一次 resume 正确继续。也就是说,在 yield 时,lua stack 上可以有没有执行完的 lua 函数,但不可以有没有执行完的 C 函数。
如果我们写了这么一个 C 扩展,在 C function 里回调了传入的一个 Lua 函数。情况就变得不一样了。
(C)lua_resume -& Lua function -& C function
-& (C) lua_call
-& Lua function
-& coroutine.yield -& (C)lua_yield
C 通过 lua_call 调用的 Lua 函数中再调用 coroutine.yield 会导致在 yield 之后,再次 resume 时,不再可能从 lua_call 的下一行继续运行。lua 在遇到这种情况时,会抛出一个异常 "attempt to yield across metamethod/C-call boundary" 。
在 5.2 之前,有人试图解决这个问题,去掉 coroutine 的这些限制。比如
这个项目。它用操作系统的协程来解决这个问题 (例如,在 Windows 上使用
)。即给每个 lua coroutine 真的附在一个 C 协程上,独立一个 C 堆栈。
这样的方案开销较大,且依赖平台特性。到了 Lua 5.2 中,则换了一个更彻底的方案解决这个问题。
Posted by 云风 at 12:07 PM |
June 05, 2012
让 Lua 支持中文变量名
在做策划表格解析的时候,我们希望可以在表格里直接填写一些脚本代码。我们的脚本语言使用的 Lua ,所以,直接填写 Lua 代码最为简单。但是,策划同学强烈需要在脚本中直接使用中文。而 Lua 原生并不支持使用中文作为变量名。一开始我们使用了一些变通的方案:比如建立一个字典,把中文词通过程序替换成相应的拼音。倒也能工作。
昨天在午饭途中的电梯里,我想到了另一个方案,用了一个下午实现出来验证可用。
修改 Lua 的语法解析代码,让其支持汉字并非难事。但我不太想通过给 Lua 打补丁,修改 Lua 语言的方式来做这件事情。即,我不想因为这个项目为 Lua 创造一门方言。但是,我们却可以把策划表格中填写的代码当成一种 DSL ,正如之前我实现的 那样。把这部分用 Lua 的方言来实现,把修改的影响减少到最小,而不蔓延到整个系统的实现语言中去,或许是个更好的方法。
因为 Lua 是否支持中文变量名,只是一个语法解析层面的问题。到了虚拟机解析 bytecode 层面就不存在了。即,我们修改 Lua 的实现,让它支持中文变量名,它解析源代码生成的 bytecode ,是完全可以直接在未修改过的 Lua 环境中运行的,甚至连调试信息都完全兼容。
Posted by 云风 at 11:38 AM |
April 26, 2012
最近几天优化了一下
这是一个大改动,所以写 blog 记录一下。
首先,我为 rmessage 定制了一个 heap alloc ,在使用 rmessage 解包的时候不再调用系统的 malloc 。而是从一个连续内存 heap 上取用内存。这样在删除 rmessage 对象时也会更快。因为只需要把 heap 回收即可。
当然这样会导致 rmessage 解包时用到的内存增加。对于内存紧张,性能关键部分,我还是推荐 pattern 模式。虽然比较难用,但可以保证时间和空间性能。
另外,我增加了
的 Event-based parsing 模式,见新增接口 pbc_decode 。
不过我认为这个 api 不适合直接在 C 里调用,但是用来做动态语言的 binding 不错。现在 lua binding 中的 decode 就改用这个实现了。这样每次解包就把所有项都解出来,而不用附着一个 userdata 。回避了手动调用 close_decoder 的问题。
btw, 根据一个同学使用的反馈,他们大多不主动调用 close_decoder ,而依赖 gc 回收 decode 过程中产生的 C 对象。但是这些 C 对象申请的内存不会通知 lua ,所以 lua 的 gc 触发条件不会及时触发。这使得 pbc 的 lua binding 可能占用大量内存。我这次的修改主要针对这个问题。
Posted by 云风 at 06:47 PM |
April 19, 2012
让多个 Lua state 共享一份静态数据
如果你在同一个进程里有多个 lua state , 它们需要共享大量的只读数据, 那么可能就不希望在每个 state 启动的时候都加载和解析一遍这些数据.
所以我们需要一个共享只读数据的方法。
前段时间,我实现了一个
,这个可以保证共享内存的安全读写。不过,如果数据是只读的,那么就不需要这么复杂了。
我们只需要把数据加载到一个 lua state 中,其它的同一进程内的 state 通过 C 接口去读数据就可以了。
今天,我做了,放在了 github 上。
Posted by 云风 at 05:34 PM |
April 11, 2012
Lua int64 的支持
虽然今天发了 twitter ,以及向 lua mailling list 里投递了消息,不过想想还是写一篇 blog 记录一下。
Lua 只支持一种 number ,默认是 double 类型。虽然你可以通过修改 luaconf.h 里的定义,把 lua number 改成 int64 。但是为了 int64 类型而放弃浮点数,恐怕不是大多数人想要的。
int64 通常用在 uuid 上,也就是说不需要对其数学运算,只需要可以比较就好了。我以前最喜欢的做法是用 8 bytes 长的 string 来表示一个 int64 。这样,即可以做唯一的 key 用,又不用做复杂的扩展。
中,对 fixed64 类型,我就是这样处理的。
Posted by 云风 at 07:01 PM |
February 17, 2012
跟踪调试 Lua 程序
我们用 lua 做主要的项目开发语言,一直有同学希望可以在 IDE 里单步跟踪调试 lua 代码。我总觉得这个坏习惯是被 Windows 带坏的。当然,很多年前,我也尝试过编写 。后来这玩意半途而废了。因为我觉得没啥实用价值,需要这样去调试 lua 程序的程序员反正也写不好 lua 程序。宁可不要这种工具让 lua 程序员的代码质量能提高一点。
后来过了两年,还在网易时,又有同学要求有一个方便点的调试器。,gdb like 的界面,用 C/S 方式调试,并用 GTK 配了一个 GUI 的 client 。主要就是远程设置断点,观察变量等。有兴趣的同学可以。
这套东西不多提了。今天又有人老话重提。我觉得吧,与其做一个交互式的调试器,不如做一个 trace log 简单实用。毕竟在生产环境,不是有那么多机会让你中断下服务单步调试的。
Posted by 云风 at 08:41 PM |
January 19, 2012
一个链接 lua 引起的 bug , 事不过三
今天花了将近 3 个小时帮同事看一个崩在 lua VM 中的 bug 结果打乱了进度,没有在年前把预想的东西做完。其实说起来这不是个大问题,以前也碰到过。我检讨自己没有在看到出错时的调用栈时去看一眼 lua 相关的代码。如果是那样,因为以前遇到过同样的问题,所以就可以条件反射出问题原因,而不用荒废宝贵了数小时时间了。
唉,这下整合的进度没接上,过年不能自己一个人接着做下面的活了。
下面记录一下这个 bug ,提醒自己第三次遇到时不用再花时间找问题:
Posted by 云风 at 06:42 PM |
December 30, 2011
lua 5.2 的 _ENV
lua 5.2 正式发布了,对于 lua 语言本身的修改,重中之重就是对 environment 这个概念的修改。
可以说, 5.1 以前的 environment 已经没有了。environment 对于制造一个安全的沙盒(或是实现 DSL)是一个很重要的语言特性,我以前很喜欢使用,但也很容易用错。这次的修改我认为是一个谨慎的决定,并使得 lua 语言更为精简和严谨了。
我这样理解 5.2 中的 environment 。本质上,lua 取消了原有意义上的 environment 。所以我们可以看到 C Function 不再有环境了。function 、在 lua 中称为 closure ,仅仅只是函数体和 upvalue 的联合体。这简化了 lua 语言本身。全局变量实际上只是一个语法糖,编译时再前面加上了 _ENV. 的前缀。这样,从 load 开始,第一个 chunk 就被加上了 _ENV 这个 upvalue ,然后依次传递下去。
这个设计基本可以取代以前使用 getfenv/setfenv 改变函数环境的方法。但是又不完全等价。总体来说,增加了一些限制,但不太容易写出 bug 的代码了。
比如说,现在想给返回一个独立环境的函数,可以这样写:
Posted by 云风 at 12:34 AM |
December 14, 2011
pbc 库的 lua binding
前几天写的
初衷就是想可以方便的 binding 到动态语言中去用的。所以今天花了整整一天自己写了个简单的 lua binding 库,就是很自然的工作了。
写完了之后,我很好奇性能怎样,就写了一个非常简单的测试程序测了一下。当然这个测试不说明很多问题,因为测试用的数据实在是太简单了,等明天有空再弄个复杂点的来跑一下吧。我很奇怪,为什么 google 官方的 C++ 版性能这么差。
我的 lua 测试代码大约是这样的:
local protobuf = require "protobuf"
addr = io.open("../../build/addressbook.pb","rb")
buffer = addr:read "*a"
addr:close()
protobuf.register(buffer)
for i=1,1000000 do
local person = {
name = "Alice",
local buffer = protobuf.encode("tutorial.Person", person)
local t = protobuf.decode("tutorial.Person", buffer)
100 万次的编码和解码在我目前的机器上,耗时 3.8s 。
Posted by 云风 at 10:44 PM |
November 12, 2011
Ameba , 一个简单的 lua 多线程实现
几个月以前,在我在 blog 上曾谈及 Lua 5.2 的改进。 。
周末休息,我把这桩事挖出来娱乐一下,花了一整个晚上做了实现。把 lua 的每个线程锁定在独立的 lua state 中,强迫线程之间通过消息管道的方式通讯。经过测试,Lua 5.2 每个独立的 state 占用的内存很小。通过自定义 alloc 函数可以测算出,一个干净的 32bit state ,不含任何库函数时,占用内存量在 2K 以下(1726 bytes)。如果加载基本库,也仅仅占用不到 4K (3265 bytes)。若把所有 lua 官方标准库加载进来,才会上升到 10K 以上(12456 bytes)。
对于 luajit 2 ,这个基础开销会大一些,最小开销也在 10K 左右 (8058 bytes) 。加上 ffi 达到 30k (31605 bytes)。不过 ffi 可以使 lua 代码直接使用 C 的数据结构,在实际运用中还可以减少内存的使用。
废话不多说, ,有兴趣的同学可自取。
这个娱乐项目命名为 Ameba ,暗示每个代码单位都足够的小,功能简单。它们必须通过很少的 send/recv 和外界通讯。目前,通讯的数据类型仅限于 number boolean 和 string 。
Posted by 云风 at 07:41 PM |
August 09, 2011
Lua 下实现抢占式多线程
。漫长的流程到今天已经快两年过去,终于等到了 beta 版。我十分期待它可以在 2011 年内正式发布。在这几经折腾的两年里,许多新特性企图挤进 5.2 版,又最终被否决。
当我们审视,似乎看不到太多耳目一新的东西。但如果仔细阅读一下源代码,就会发现,大部分地方都重新实现过了,以配合这些表面上看起来不大的修改。如果你对 Lua 有足够理解,会发现,这次最激动人心的改进是 "yieldable pcall and metamethods" 。官方也把之列为 Main changes 第一条。语言上的重大新特性 goto 却被列在末尾。
当然,这只是我粗浅的理解而已。没有经过实践使用 5.2 一段时间,下这样的论断有点太草率。不过我还是想谈谈,这点改进可以给我们的开发带来什么。
coroutine 的 yield 现在几乎可以在任何地方使用了。我用了几乎,是因为它依然有一些限制。这些限制不大容易说的很清楚,为了理解其限制,我花了一整天实现阅读 lua 5.2 beta 版的源代码。这个话题下次有机会我再另写一篇 blog 总结一下。今天只谈应用。
Posted by 云风 at 06:50 PM |
June 24, 2011
使用 luajit 的 ffi 绑定 zeromq
最近 Lua 社区非常活跃。6 月 22 日发布了 Lua 5.2.0 (beta-rc2) 。今天(6 月 24 日) 发布了 LuaJIT-2.0.0-beta8 。
虽然 luajit 和 lua 5.2 还有点小矛盾,luajit 没有完全支持 lua 5.2 的迹象。不过,这些对 Lua 社区都是好消息啦。可能对于 lua 用户会有点小纠结,到底是追随官方的 5.2 版呢,还是去用性能更好的 luajit2 。我比较在意性能,暂时先投靠 luajit 了。反正和 5.2 区别也不大。更重要的是,luajit2 提供的 ffi 库相当之好用,极大的减少了我们写 C 库的 lua binding 的负担。从某种角度可以看到另一个问题,为基础设施模块设计出良好的 C 接口(而不是 C++ 的)是多么的重要。
zeromq 是用 C++ 实现的,但它提供的是简洁纯粹的 C 接口。这让它相当利于 binding 到其它语言中使用。之前,已经有了成熟的
可供使用。它分别实现了 ffi 和不带 ffi 的版本。不过也正因为此,封装层包裹的很淡疼。如果只支持 ffi 版本的话,其实这个工作可以做的非常简洁。
出于实践 luajit ffi 库的目的,也为了让这部分代码看起来清爽一点。我花了半个下午自己封装了一下 zeromq 。所用时间比在 windows 下配置安装那个现成的 lua-zmq 所用时间看起来更少(不需要装 msys ,cmake 等等淡疼的玩意)。谁再下面留言说不要重复造轮子了,我也不打算跟它急了。吵架的时间都比写代码时间长。我们从来不会把写一遍 hello world 看成重新制造轮子不是么?使用 ffi 去 binding C 库实在是太容易了,不比写 hello world 更复杂。
Posted by 云风 at 07:02 PM |
April 02, 2011
把 lua 的 gc 移到独立线程
前几天分析了 lua gc 的实现细节。这里先汇总一下:
btw, 阅读 lua 的代码是段很有趣的经历。但如果是重头读 lua 的源码,建议从简单的部分读起。gc 恰巧是最难的一段。 的作者 Mike 在这方面很有发言权,他在回答 Which OSS codebases out there are so well designed that you would consider them 'must reads'? 这个问题时,列过一张推荐。我的观点一致,Lua 是少有的设计优秀,C 程序员必读的代码。
从小处说,如果想进一步改进,那是必须仔细研读的(但这绝对不是主要原因)。Lua 的 GC 实现的已经相当不错了,想找出实现中的问题,改进算法,可能很难。如果有多核处理器,那么把 GC 放到一个独立线程里去做倒是可以考虑的。
如果没有前面的研读,恐怕只能用一把大锁来安全处理多线程的 GC 了。lua 的代码为多线程安全预留了 lua_lock 和 lua_unlock 两个 api 。默认是用宏定义出来关闭的,必要的时候可以改写它们。所有的对外 api 都加入了 lock 的调用。
但是,用它来实现多线程的 gc 是完全没有意义的。GC 部分永远不能并行处理。这个东西只是为了多线程访问同一个 lua state 提供了安全保障而已。
下面我们看看能做点什么。
Posted by 云风 at 04:50 PM |
March 31, 2011
Lua GC 的源码剖析 (5)
今天来说说 write barrier 。
在 GC 的扫描过程中,由于分步执行,难免会出现少描了一半时,那些已经被置黑的对象又被修改,需要重新标记的情况。这就需要在改写对象时,建立 write barrier 。在扫描过程中触发 write barrier 的操作影响的对象被正确染色,或是把需要再染色的对象记录下来,留到 mark 的最后阶段 atomic 完成。
和 barrier 相关的 API 有四个,定义在 lgc.h 86 行:
Posted by 云风 at 04:04 PM |
Lua GC 的源码剖析 (4)
今天来看一下 mark 过程是怎样实现的。
所有的 GC 流程,都从 singlestep 函数开始。singlestep 就是一个最简单的状态机。GC 状态简单的从一个状态切换到下一个状态,循环不止。状态标识放在 global state 的 gcstate 域中。这一点前面谈过。
开始的两个状态和 mark 过程有关。
初始的 GCSpause 状态下,执行 markroot 函数。我们来看一下 markroot 的代码。见 lgc.c 的 501 行。
Posted by 云风 at 01:02 AM |
March 29, 2011
Lua GC 的源码剖析 (3)
有了前几天的基础,我们可以从顶向下来读 lua gc 部分的代码了。
我们知道,lua 对外的 API 中,一切个 gc 打交道的都通过 lua_gc 。C 语言构建系统时,一般不讲设计模式。但模式还是存在的。若要按《设计模式》中的分类,这应该归于 Facade 模式。代码在 lapi.c 的 895 行:
Posted by 云风 at 09:46 PM |
March 28, 2011
Lua GC 的源码剖析 (2)
早期的 Lua GC 采用的是 stop the world 的实现。一旦发生 gc 就需要等待整个 gc 流程走完。如果你用 lua 处理较少量数据,或是数据增删不频繁,这样做不是问题。但当处理的数据量变大时,对于实时性要求较高的应用,比如网络游戏服务器,这个代价则是不可忽略的。lua 本身是个很精简的系统,但不代表处理的数据量也一定很小。
从 Lua 5.1 开始,GC 的实现改为分步的。虽然依旧是 stop the world ,但是,每个步骤都可以分阶段执行。这样,每次停顿的时间较小。随之,这部分的代码也相对复杂了。分步执行最关键的问题是需要解决在 GC 的步骤之间,如果数据关联的状态发生了变化,如果保证 GC 的正确性。GC 的分步执行相对于一次执行完,总的时间开销的差别并不是零代价的。只是在实现上,要尽量让额外增加的代价较小。
先来看 GC 流程的划分。
Posted by 云风 at 02:45 PM |
March 27, 2011
Lua GC 的源码剖析 (1)
最近发现在大数据量的 lua 环境中,GC 占据了很多的 CPU 。差不多是整个 CPU 时间的 20% 左右。希望着手改进。这样,必须先对 lua 的 gc 算法极其实现有一个详尽的理解。我之前读过 lua 的源代码,由于 lua 源码版本变迁,这个工作还需要再做一次。这次我重新阅读了 lua 5.1.4 的源代码。从今天起,做一个笔记,详细分析一下 lua 的 gc 是如何实现的。阅读代码整整花掉了我一天时间。但写出来恐怕比阅读时间更长。我会分几天写在 blog 上。
Lua 采用一个简单的标记清除算法的 GC 系统。
在 Lua 中,一共只有 9 种数据类型,分别为 nil 、boolean 、lightuserdata 、number 、string 、 table 、 function 、 userdata 和 thread 。其中,只有 string table function thread 四种在 vm 中以引用方式共享,是需要被 GC 管理回收的对象。其它类型都以值形式存在。
但在 Lua 的实现中,还有两种类型的对象需要被 GC 管理。分别是 proto (可以看作未绑定 upvalue 的函数), upvalue (多个 upvalue 会引用同一个值)。
Lua 是以 union + type 的形式保存值。具体定义可见 lobject.h 的 56 - 75 行:
Posted by 云风 at 06:14 PM |
January 28, 2011
如何给指定地址空间拍一个快照
需求来自于,我希望可以对 lua 虚拟机中的内容做持久化,却又不希望 stop the world 。这需要利用 os 的功能,对内存做一个快照。简单的 fork 就可以达到快照的要求,但是 fork 会快照整个进程的地址空间,这不是我想要的。
这两天和几位同学讨论了各种方案,比如 memcpy ,比如 fork+exec 传递 shm_open 的 fd , fork 后 munmap 不用的区域等等。最后我认为如下方案相对更满意一些。我并没有实现出来, 写 blog 只是做个记录。
Posted by 云风 at 03:00 PM |
December 14, 2010
lua cothread
前段时间在玩
,非常喜欢 goroutine 的编程模型。采用 chan 进行 thread 间的通讯写起来很舒适。今天花了一个下午,为 lua 写了一个简单的库,模拟这种编程方式。暂且把这个东西叫作 lua cothread 。它基于 lua 的 coroutine ,只是写了个简单的调度器。
这个库有如下几个 api :
Posted by 云风 at 09:04 PM |
August 18, 2010
继续完善 protobuf 库
又仔细推敲了两天,把 lua 版的 protobuf 库完善了一下。主要是做了两个工作:
protobuf 本身的格式,google 是自描述的。定义为 google.protobuf.descriptor 。我先自己实现的 parser 图方便,用了自己的中间交换格式。为了日后更通用,稍微修改了一下,可以生成于官方相同的结构。解析的性能稍有下降,不过应该兼容性更好。一开始实现的 api 虽然性能非常好。(经简单测试,是 python 库的 30~40 倍,和 C++ 库性能相当)但若消息格式复杂,实现起来稍有麻烦。所以我做了点小封装。即为每个消息生成一对函数,可以用来打包和解包完整的消息,映射到 lua 的 table 结构上。lua 生成代码供自己调用的技巧在 lua 社区广泛使用。比如 kepler 项目。这使得 lua 可以用很短的代码行数完成很复杂的工作,不失性能。我这个封装层只有 100 行代码左右,一大半代码是为了解决消息展开时有递归定义的情况,否则更简短。(message 中有一些 field 的类型是自己,这是一种不多见的用法,但 protobuf 似乎并没有拒绝这种用法)
Posted by 云风 at 12:09 AM |
August 11, 2010
Proto Buffers in Lua
的意义在于,定义了一个不错的 PDL 。protobuffers 的实现反而不那么重要了。
这几天我一直在倒腾 lua 下的 proto buffers 的支持。一直在思考,怎样的接口才是最适合 lua 使用的。
大多数语言下的 proto buffers 实现,都是将编码的数据块展开成本地语言的数据结构。对于 C/C++ ,这是最高效的形式。但对于动态语言,那就未必了。虽然 google 为 python 做的 proto buffers 的官方实现也是如此,但我依然想考虑一下,是否有更高效的方式来做这件事。
Posted by 云风 at 12:06 AM |
June 09, 2010
采访 Lua 发明人的一篇文章
《Masterminds of Programming: Conversations with the Creators of Major Programming Languages》是本相当不错的书。博文翻译出版了这本书,中文名叫做《》。
书是好书,可惜翻译这本书需要对各种语言的深入研究,看起来译者有点力不从心。出版社打算重新做这本书。受编辑所托,我校对了其中第七章:有关 Lua 的一段。原文读下来拍案叫好。可惜译文许多地方看起来有些词不达意。许多在口语化交流中提到的术语被忽略了做了错误的翻译。有些部分应该是对 lua 理解不够而没能表达清楚。
仔细校对了两段后,我干脆放弃原译本,自己动手翻译了一份(保留了不到 1/4 原来的译文)。虽然个人能力有限,但也算是每句话自己都看明白了再译的。虽说有些地方没有直译,但也算没有夹带私货。
这里贴出一段,希望大家阅读愉快。
Posted by 云风 at 10:36 PM |
May 27, 2010
共享 lua state 中的数据
今天和倩女幽魂的同事讨论一个问题:他们的游戏 client 中,有大量策划填写的表格直接导入 lua state 中的大量数据。大约有 100M 以上。这样,如果玩家在一台机器上启动多个 client ,就会占用大量的内存。
而这些数据,一旦加载进 lua ,就不会再修改,且每个 client 中数据都是一致的,这是一种浪费。
问题是:如何利用进程间的数据共享,在多开 client 时节省这些空间。(同时也可以加快开第二个 client 的启动速度)
Posted by 云风 at 03:41 PM |
January 12, 2010
Lua 5.2.0 (work1)
,这个消息有几天了。lua 社区这两天非常热闹,各大牛都现身了。
做 LuaJIT 的牛人 Mike Pall 对 bit 库没有采用他做好的现成方案那可是。
不过,欢呼雀跃的人还是比较多的。每次 lua 升级个小版本,改动都非常大。对成熟项目,不给你伤点筋骨,那就不是 lua 三巨头的风格了。当然,对于时不时重写代码的我,欣赏这种风格 ;) 我喜欢更健康的 lua 语言。
嗯,无论如何,lua 的源代码是非常值得阅读的。
Posted by 云风 at 09:54 PM |
November 01, 2009
luajit 这次终于扬眉吐气了
几个月前, Mike Pall 就在 lua 的 mailling list 里叫嚣他的
2.0 用的新算法将会大幅度提升性能。还记得 Soloist 同学当初就是眉飞色舞的跟我说这个事。所以 luajit 2.0 还真是万众期待啊。至少 lua 社区的人都等着呢。
昨晚,。那个性能测试结果真是很吓人啊。
ps. 我昨天刚把项目里的几个命令行工具用 lua 改写,并把
加到了 makefile 框架里。嗯,可以考虑做一个带 jit 的 srlua 。
Posted by 云风 at 06:13 PM |
October 15, 2009
C/C++ 与 Lua 的混合编程
这个是我将在上进行的议题的演示稿。最近太忙了,国庆期间在家写的草稿。
有兴趣的同学可以看看,没兴趣的请无视。
另外我会作为嘉宾参加 SD 大会的一个所谓算法论坛。还不知道谈些啥呢。
Posted by 云风 at 09:02 PM |
May 26, 2009
lua 中判断字符串前缀
一个 lua 的小技巧
在写 lua debugger 的时候,我需要判断一个字符串的前缀是不是 "@" 。
有三个方案:
比较直观的是 string.sub(str,1,1) == "@"感觉效率比较高的是 string.byte(str) == 64或者是 string.find(str,"@") == 1
我推荐第三种。(注:在此特定运用环境下。因为用于判定 source 的文件名,大多数情况都是 @ 开头。如果结果为非,则性能较低)
第一方案 string.sub 会在生成子串的时候做一次字符串 hash ,感觉效率会略微低一些。
第二方案效率应该是最好,但是需要记住 @ 的 ascii 码 64 。如果前缀是多个字符也不适用。
Posted by 云风 at 10:40 PM |
May 25, 2009
lua 调试器制作注意
前两年写过一个 lua 的调试器,
不过调试器设计的关键不在于界面,在于调试协议。前两年的那个是设计的不完整的。
最近同事强烈要求引擎提供一个强力的调试工具,虽然我个人不太依赖调试去写代码。甚至认为,经过反复调试才正确工作的代码不是好代码。不过周末还是花了点时间重新制作了一个 lua 调试器。
中间发现一些问题,非常让人吐血。列在这里,做个记录。
Posted by 云风 at 09:27 PM |
May 10, 2009
树结构的管理
要写过多少代码才能得到哪怕一点真谛?
多少年过来,我在潜意识的去追求复杂的东西。比如我自幼好玩游戏,从小到大,一直觉得玩过的游戏过于简单(无论是电子游戏还是桌面游戏),始终追寻更复杂规则的游戏,供我沉浸进去。或许是因为,有了更高的理解和控制复杂度的能力,就可以更为轻松的驾御复杂性。
这很好的解释了 2000 年到 2004 年我对 C++ 的痴迷。还有对设计模式的迷恋。
Eric S. Raymond 说:尽量不要去想一种语言或操作系统最多能做多少事情,而是尽量去想这种语言或操作系统最少能做的事情——不是带着假想行动,而是从零开始。禅称为“初心”(beginner's mind)或者叫“虚心”(empty mind) 。
代码写多了,问题见过了,甚至是同一问题解决多了。模式这种东西自在心底,不必拿出来。时时的从零去想,总能重新明白一些道理。
为什么说语言重要也不重要,算法和数据结构重要也不重要。对要解决的问题的领域的理解很重要(即明白真正要做什么)。理解了,我们才可以用面向对象,用模式去套问题;可理解了,我们又不真的需要这些繁杂的抽象。
闲话放一边,今天想谈谈树结构的管理。
Posted by 云风 at 09:13 PM |
May 06, 2009
回顾 Forth
第一件事就是没有钩子。不要留一个接口,想着未来的什么时候当问题变化时插入一些代码,因为问题会以你不能预见的方式变化。反正这个成本肯定是浪费了。不要预测,只解决你眼前的问题。by Charles Moore (Forth 之父)
今天也是机缘巧合,莫名其妙的翻出老资料温习 Forth 了。我想是个心结吧。19 年前,我痴迷于 Forth ,只看到了皮毛;13 年前,我进入大学的第一年,在校图书馆借出的第一本书,就是《Forth 语言》,读书笔记写了 20 多页。
只到今天,我才有机会,有能力,去仔细探究 Forth 的深层思想。当然,由于时间有限,几个小时的阅读,也只算是初窥门径。原本是想研究下 ,对同事正在设计的 3d 粒子系统,提供一些建议的。
碰巧又读到 Charles Moore 在 99 年的访谈稿
,颇多感慨。题头那段话,我在一周前刚好苦口婆心的对一同事说过,只差几个字而已。
Posted by 云风 at 08:20 PM |
May 03, 2009
树型打印一个 table
php 中有个 print_r 函数,可以递归打印一张表。很多 php 程序员喜欢用这个去调试程序。
我想,所有写过一定代码量的 lua 程序员都会写一个类似的东西放着备用吧。这两天调试 lua 程序的时候,发现以前做的简陋的 print_r 不够好用。对于复杂的 table 打印出来一大篇很不直观。结果就放下手头的工作,花了整整一个小时,写了下面几十行代码。把 table 输出成树结构。
alpha = 1 ,
foo = "ooxx",
bar = "haha",
test = a.a
a.c = a.a.hello
print_r(a)
可以输出成:
+a+hello+alpha [1]
| +world+root {.}
+bar [haha]
+foo [ooxx]
+c {.a.hello}
+b+test {.a}
Posted by 云风 at 02:38 PM |
May 01, 2009
在 lua 中实现函数的重载
警告:记录以下内容纯粹自娱,请勿轻易用于项目。我个人也不赞同随意使用语法糖去改造语言。
我们知道 C++ 里有函数重载的特性,程序员可以为一个看起来同名的函数做多份实现,让编译器通过调用时的参数类型去指定链接器链接最为匹配的一份实现。对于死忠的 C++ 程序员,这绝对是最必不可少的利器。如果没有它,那些 template 绝对玩不出现在这么多花来,当然也就没那么多机会拿着“充满智慧” 花哨的 template 代码来 YY 自己的智商了。
哦,写 lua 的所谓脚本程序员不要沮丧,其实 lua 中可玩的花样也很多。一样可以写出让同行瞠目结舌的代码来。比如这个函数重载的问题,虽然 lua 不可能做所谓编译期运算(或许勉强算一个),也没有什么静态链接过程。
但 lua 是个有趣的语言,下面看我怎么模拟出一个类似的东西来。
Posted by 云风 at 05:17 PM |
April 06, 2009
为 lua 插件提供一个安全的环境
wow 开放了一套用户自定义的插件系统,很多人都认为,这套系统是 wow 成功的因素之一。反观国内乃至韩国的网游,至今没有一款游戏能提供相当自由度的用户自定义插件系统。
最开始,暴雪是想让用户可以由用户甚至第三方自定义操作界面。后来,这套基于 XML 和 lua 的插件系统不仅仅用来做界面了。
从我在网游行业从业这么多年的经验,游戏界面相关的开发也是颇费人力的。甚至于,Client 开发到了维护期,几乎都是在 UI 上堆砌人工。一套自由的插件系统,对于开发商和用户是双赢的。
但是,插件系统也是双面剑。最典型的负面问题就是安全。越是自由,越是给机器人制作开放了自由之门。这里暂且不提这个方面的问题。首先关注一下另一个:尽可能的保护系统中不想被插件系统访问的数据,避免利用插件编写木马。
Posted by 云风 at 03:47 PM |
March 28, 2009
安全的迭代一个集合
把同质的东西放入一个容器,然后用迭代器迭代这个容器,把里面的内容逐个取出来处理。这是一个非常常见的需求。但是,这个过程往往也会滋生 bug 。因为,若将容器看成一个对象,那么对其迭代的这个操作很难实现原子性。
非原子性导致了,在迭代过程中,十分有可能对容器本身进行修改。或增加若干元素,或删除若干元素。这些都容易造成迭代过程不正常。
所以,最终我们需要根据需求设计以及实现合理的容器。比如管理消息的消息队列,严格的满足尾进头出,没有删除中间数据的需求,就不会导致 bug 。
那么,如果容器是一个集合怎么办?即,允许向其中增加新的元素,也可以移除某些元素。这种数据结构非常有用。比如向某对象注册若干回调函数,一旦满足条件则依次调用。即设计模式中的 Observer 观察者模式。回调函数就极有可能增加新的观察者或某些老的观察者退出。
Posted by 云风 at 01:54 AM |
March 12, 2009
为 lua 封装 C 对象的生存期管理问题
把 C 里的对象封装到 lua 中,方便 lua 程序调用,是很常见的一项工作。
里面最大的问题是生命期管理问题。
通常有两种方案:
第一:编写 C 库的时候,完全针对 lua 设计,所有对象都有 lua_newuserdata 分配内存。对象和对象之间的联系可以使用 userdata 的 环境表,把对象间的引用放在里面,使得 lua 的 gc 过程可以正常进行。
第二:给 C 对象简单加一个壳。lua 的 userdata 中仅仅保存 C 对象指针。然后给 userdata 设置 gc 元方法,在被回收时,正确调用 C 对象的销毁函数。
以上两种方案都依赖 lua 的 full userdata ,这里,我想提供第三种方案,仅使用 lightuserdata 完成这项工作。
这第三方案未必比前两种都好。虽然从字面上理解 light userdata 比 full userdata 更廉价,但诚如 pil 中所言,full userdata 也非过于重量。
最终的方案选择还是要结合实际的设计,仔细考量。
Posted by 云风 at 04:03 PM |
March 07, 2009
降低 lua gc 的开销
周末有同事问我一个问题,说他们猜测在他们系统里 lua 的垃圾回收过程导致了系统开销过大。而其中有些开销是无谓的。
比如在他们的系统中,有大量的结构化数据是只读的,不会和其它数据产生联系。大多为策划设定的游戏逻辑参数。而偏偏这部分数据结构复杂,在 lua 的 gc 过程中会产生大量的遍历。但我们明明知道,这些数据一定不会被回收掉,且不会影响 gc 的结果。那么有什么方法可以优化呢?
Posted by 云风 at 10:33 PM |
October 31, 2008
给 Lua 增加参数类型描述
Lua 的函数定义是没有参数类型信息的。这些信息在跨语言的模块化设计中非常有价值。因为跨语言的方法调用通常需要做列集(Marshaling) 的操作,缺乏类型信息很难完成这个工作。同样的需求在做 RPC 调用的时候也很重要。
感谢 Lua 简洁的 metatable 的设计,我们可以用一个简单的方法自然的描述出 Lua 函数的调用参数类型,又无损性能。
一开始,我们先回顾一下在 。通常我们可以把方法列表(对应于 C++ 中的虚表)放在一个 table 中,然后把这个 table 作为一个 metatable 的 __index 方法,并把 metatable 附加到一个 table 上,就生成了简单的对象。我喜欢这样做:
Posted by 云风 at 10:38 PM |
August 15, 2008
让 lua 编译时计算
lua 里其实也颇多奇技淫巧,使用时应三思。
如果你读过
的代码,就会发现,多次编译这种技巧用的很多,甚至迭代几次使用。即,第一次加载代码时,用一段 lua 程序生成真正需要的源代码,然后再将其编译出来。
由于 lua 的编译速度相当快,而且这种迭代编译的过程仅仅在程序加载的时候进程一次,故而可以带来性能的提高:一些在系统初始化时可以决定的参数(比如从配置文件中读出来的数据)直接编译为常量置入程序中。
,简化这种迭代编译的过程,算作周末自娱吧。
例如这样一个例子,我们在程序中需要用一个常量,这个常量可能是通过加载配置文件得到。假设允许编译期计算,我们可以这样:
function foo()
for i=1,| config "max" | do
execute(i)
这个例子里,循环的终值是通过调用 config "max" 得到的,如果每次运行这个程序时都去查询 config 必然影响效率。我们需要在程序加载时,第一次得到 config "max" 的结果即可。
这里,我使用 | 夹在中间 | 表示这段代码应该在加载时运行。
有点像 php 写网页用的模版?呵呵,可以接着往下看。
Posted by 云风 at 10:42 PM |
August 13, 2008
Lua 不是 C++
嗯,首先,此贴不是牢骚帖。
话题从最近私人的一点工作开始。应 dingdang 的建议,我最近在帮
做一些程序上的工作。接手做这件事情,是因为这个内部被我们称作 dt2 的游戏 engine 关系重大。公司有至少四个项目在使用(另外三个暂处于研发期,尚未公布)。
dt2 用了大量的 lua 代码构建系统,但从系统设计上,沿袭了老的大唐的许多代码。原来的大唐是用 C++ 构建的,为了利用上这些代码(虽然我觉得这种复用非常无意义,但是其中原因复杂,就不展开谈了),dt2 engine 的开发人员做了一套非常复杂的中间层,把 lua 和 C++ 几乎无缝的联系在了一起。
Posted by 云风 at 08:13 PM |
May 09, 2008
The Implementation of Lua 5.0 中译
读者向海飞给我 email 了一份他翻译的《The Implementation of Lua 5.0》这篇 paper。原文可以在
这篇由 lua 作者们写的 paper 对理解 lua 非常有帮助。有兴趣的朋友在
译文最后附有译者的 email 大家可以直接向他反馈。
Posted by 云风 at 07:16 PM |
March 22, 2008
感觉好多了
其实我并没有用 lua 亲手写过什么大规模的项目。超过 5 千行代码的项目几乎都是 C 或是 C++ 写的。这几天算是做了点复杂的玩意了。几经修改和删减,最后接近完工的这个东西统计下来不多不少 3000 行(误差在十位数)。其中用 C 编写了基础模块 900 多行(仅仅是 socket api 的封装和 byte 流的编码解码),剩下的都是用 lua 设计并实现的。
好吧,我承认 2000 多行代码也只是一个小东西。不过用 lua 实现的一个 wiki 系统, 还不到 2000 行呢。lua 有一种特质,用的久了就容易体会到。它和 python ruby 这些更为流行的动态语言是不同的。曾经,我把选择 lua 的理由,肤浅的停留在了更轻便更高效上,虽然这些也很重要,但抓住语言的特质才是更关键的。
Posted by 云风 at 06:59 PM |
March 15, 2008
基于 lua 的热更新系统设计要点
很久没写 blog 了,主要是忙。项目排的很紧,一个小项目上线,发现不少问题,所以把多余精力都投进去了。最后人手不够,亲自上场写代码。在不动大体的情况下,最大能力的修改了一些设计,并把能重写的代码重新写过了。
这一周,写了三天半程序(其中通宵了一次)。平均每天写了千多行程序。基本上把原来项目里用的几万行东西替换下来了。重构总是能比老的设计更好,不是么? :) 不过这事不能老干,个人精力再充沛。故而还是找到称心的合作人比较好,也不能啥都自己做啊。程序员的效率差别可不只十倍二十倍的差别这么少。btw, 前段时间通过这里找到的新同事不错,呵呵。如果有缘,还想再找一个能干的。我相信,聪明人在一起做事可以获得更多快乐。
闲话扯到这里,总结下这两天做项目的一点经验。那就是:当我们需要一个 7*24 小时工作的系统时,实现热更新需要注意的要点。当然,这次我用的 lua 做实现,相信别的动态语言也差不多。只是我对 lua 最为熟悉,可以快速开发。
Posted by 云风 at 12:28 AM |
October 18, 2007
在 Lua 中管理 C 对象
今天同事在设计引擎的脚本接口时遇到一个问题:需要把 C 对象指针放到 Lua 中,允许 Lua 保存这个指针,并传递给其它模块。
这是给 Lua 写 C 扩展时常见的问题,撇开如何如何将对象的方法导入 Lua 这个更复杂的问题不谈,我主要想说说 C 对象的生命期管理的问题。
一开始的设计是把对象的销毁方法也导入 Lua ,由脚本程序员手工管理。这是很明显的 C 程序员的思路:谁构造谁释放。但在这里是不合适的,不符合带 gc 机制语言的习惯。
我们当然希望脚本更为健壮,不需要考虑对象释放的问题。所以晚上我想了一下,修改了一下这部分的实现。
Posted by 云风 at 12:52 AM |
September 06, 2007
玩了一下 ajax
起因是这样的:
几个同事在棋牌群里聊天,说找不到搭档打桥牌。网上也没啥好地方去,大家都比较讨厌下客户端和注册。我说,不如我做一个免客户端免注册的桥牌网站吧。然后就开始了。
直觉告诉我,ajax 技术可以实现这些。但是我没做过 web 方面的开发,仅有的一些知识只在几年前写过一个 。一开始觉得 ajax 这些时髦玩意学一下午,然后一通宵就可以把想要的东西做出来。哪知道,结果不务正业干了半周了,中间还熬了两晚上,到今天都没做完。明天要出差,只好放一放了。
Posted by 云风 at 08:31 PM |
June 17, 2007
如何在 Lua 注册表中选择一个合适的 Key
Lua 提供了一个注册表(REGISTRY)让我们的 C 扩展可以安全的把一些运行时数据放进去,而不被 lua 代码碰到。为了让各个 C 扩展库之间可以相安无事的工作,并且对注册表的操作又有较高效率。Lua 大神 Roberto 在神作
里给出了一个简洁的方案: 。
静态变量在当前进程中一定拥有惟一的地址,且 lightuserdata 作 key 非常高效。这无疑是一个好方法。
但是,当模块的源码规模变大了以后,我们将代码分散到不同的源文件中。或者几个子模块需要相互协作时。这个方法就有了一定的缺陷。那就是,必须将这个静态变量暴露出来供大家蹂躏;或是写一个内部函数来取得它(其实没有本质区别)。
如果你也碰到这类问题,不妨看看下面的解决方案。
Posted by 云风 at 02:37 PM |
June 12, 2007
魔兽世界的影响力
晚上一个小朋友在 gtalk 上问我编程语言专注哪一门好。当然这不是一个简单能回答的问题。尤其对刚上大学的小朋友不太好解释清楚。
不过睡觉前我还是八卦了一下当今世界编程语言的流行程度排名。查了下 。首先映入眼帘的加黑的头条:Lua only 0.003% away from top 20 position 。
不得不感叹魔兽世界的影响力啊。(同事语: wow 让阿猫阿狗都开始写 lua 程序了)回想 05 年的时候,Lua 可是排在 70 多位的。
我们在 01 年底为大话西游2 选择一门嵌入式脚本语言的时候,考察了 lua python java javascript ruby 等许多开源动态语言(java 是个例外,而且 java 不开源,但还是可以找到一些 JVM 的开源代码)。最后定下 lua ,其中一个原因就是它不太为人所知。反逆向工程可能可以方便一点,真没想到今天会是这个局面。(当然,那个时候 python 和 ruby 在国内用的人也相当的少)
是金子总会闪光的 :D
Posted by 云风 at 12:43 AM |
May 04, 2007
正确的向 WinProc 传递 lua_State 指针
在 Windows 下写一些关于窗口的程序时,如果在软件中嵌入 lua ,那么就很有可能遇到一个棘手的问题:如果你需要用 lua 来直接响应一些 Windows 消息,那么如何向 WinProc 传递 lua_State ,也就是那个充斥于 lua 代码中的 L 。
在 Lua 的第 4 版及以前,这个问题并不突出。因为大多数情况下,我们并不需要嵌入多余一个的 Lua 虚拟机。而 L 这个指针,从 Lua 虚拟机被创建出来以后,就不会改变。那么我们只需要把 L 保存在一个全局变量中就可以了。若是你的程序是多线程的,并且每个线程都开有独立的虚拟机,把这个全局变量放到 TLS 中就可以完美的解决问题。当然一些全局变量的排斥者,会想到把 L 放到 Window 对象的 USERDATA 中,这也未尝不是一个体面的方法。
但是,从 Lua 5 开始,因为 coroutine 的引入,即使只打开一个虚拟机,我们也会面对不同 L 的问题。这个问题早在去年就困扰过我,我和同事一起也讨论并研究过这个问题,当时得到了一些解决方法。今天,我重构代码,又想起这个话题,觉得有必要把当初的思考、结论和今天的想法纪录下来。
Posted by 云风 at 12:34 AM |
April 18, 2007
以自定义方式加载 lua 模块
今天我们的一个小项目开始做内部测试发布前的资源打包。这个项目基本上是用
做开发的。整个开发过程中,我们的代码是直接把 Lua 源代码放在项目的发布目录下的。发布版因为安全或是整洁等种种原因,我们必须给所有的脚本代码打包。
这种事情以前在 里也干过,当时用的 lua 4.0 而且也没多少经验,我们是直接去修改的 lua 的代码,适应我们的打包格式。这次,不想这么干了。希望能够完全不动 lua 官方发布的源代码,来最终完成这项工作。
简单分析了一下,发现实现起来非常简单:
Posted by 云风 at 01:04 AM |
February 12, 2007
lua 近期的一个 bug
在 lua 的 maillist 上最近报告了 。
看起来问题比较严重,因为稍具规模的 lua 程序都可能因此而出现问题。最近两周,我和我的同事都比较关注这个问题,并对 lua 的源代码做了相关的分析。
Roberto 作为 lua 委员会三巨头之一,在 mail 中已经表示追踪到 bug 的起因,但暂时还找不到合适的解决方案。直觉告诉我,这不会是一个简单的问题。如果容易修正的话,patch 早就有了,而不会只是发一个 bug report 而已。所以我们也并未尝试自己去修补这个 bug ,可以做的可能只有等待。
Posted by 云风 at 07:43 PM |
December 18, 2006
Lua 中 userdata 的反向映射
lua 中,我们可以用 userdata 保存一个 C 结构。当我们为 lua 写扩展时,C 函数中可以利用 lua_touserdata 将 userdata 转换为一个 C 结构指针。
但是,有时候我们却需要把一个指针转换回 lua 中的 userdata 对象。用到它的最常见的地方是封装 GUI ,通常 GUI 的底层是用 C 编码的。当 engine 把鼠标位置或是别的消息拦截到以后,消息会被传递到一个 C 对象中。这个时候,我们需要从 C 对象中得到对应的 lua 对象,并触发事件。
Posted by 云风 at 10:49 PM |
December 11, 2006
为 lua 配一个合适的内存分配器
以前版本的 lua 缺省是调用的 crt 的内存分配函数来管理内存的。但是修改也很方便,内部留下了宏专门用来替换。现在的 5.1 版更为方便,可以直接把外部的内存分配器塞到虚拟机里去。
有过 C/C++ 项目经验的人都知道,一个合适的内存分配器可以极大的提高整个项目的运行效率。所以 sgi 的 stl 实现中,还特别利用 free list 技术实现了一个小内存管理器以提高效率。事实证明,对于大多数程序而言,效果是很明显的。VC 自带的 stl 版本没有专门为用户提供一个优秀的内存分配器,便成了许多人诟病的对象。
其实以我自己的观点,VC 的 stl (我用的 VC6 ,没有考察更新版本的情况)还是非常优秀的,一点都不比 sgi 的版本差。至于 allocator 这种东西,成熟的项目应该根据自己的情况来实现。即使提供给我一个足够优秀的也不能保证在我的项目中表现最佳,那么还不如不提供。基础而通用的东西,减无可减的设计才符合我的审美观。sgi 版 stl 里的 allocator 就是可以被减掉的。
好了,不扯远了。今天想谈的是,如何为 lua 定制一个合适的内存分配器。
Posted by 云风 at 09:23 PM |
December 04, 2006
LoadLibrary 的搜索次序
今天写程序的时候发现一个问题,我为 lua 写了一个叫作 console 的 C 扩展库,可老是加载失败。郁闷了好半天后终于找到问题,那就是 lua 解释器实际找到的是 windows/system32 下的一个同名 dll 文件。原来系统也有一个 console.dll 了。
记得从前没有这个问题的,上网查了下 msdn 终于发现其缘故了。原来 windows xp sp2 以后,动态链接库的缺省搜索次序被修改了。
Posted by 云风 at 07:13 PM |
November 30, 2006
Lua Debugger
最近想做一个 visual 版本的 Lua 远程调试器。奋战了两天弄出这样一个玩意出来。如果有精力做完,就可以在 Windows 下远程调试任何地方的 Lua 代码了 :D
如果近期没精力,就开源让别人继续做好了。
Posted by 云风 at 04:21 PM |
November 24, 2006
lua 代码的断点调试
Lua 5.1 带了一个 debug 库,把所有的 C API 中的 debug 相关 api 都导出了。作为独立的语言使用的话,这些足够搭建一套方便的调试库。
说到最常用的断点调试法,我们能想到的最直接的方法就是利用 lua debug 库中的 hook ,然后记录一张断点位置表,设置行模式的 hook ,每次进入 hook 都检查是否是断点处,若是就停下来等待交互调试。
这个方法很有效,但是很消耗 cpu 。因为每进入一个新的代码行,都需要回调一个函数。当这个函数本身又是用 lua 写的时候,效率更低。
本文提供另一种思路,换一个方法设置断点,让没有断点时不影响运行效率。
Posted by 云风 at 08:58 PM |
November 17, 2006
Lua 中写 C 扩展库时用到的一些技巧
今天方舟组的同事问到我一些 lua 的问题,主要是关于 C 扩展库的。我觉得有些技巧性的东西挺值得跟大家分享一下,那么就写篇 blog 吧。
通常,C 扩展库中 C 代码会有一些数据要放在 lua 状态机中。Lua 提供的方案是放在它的
中。如文档所言,因为 Lua 的注册表是全局共享的,选择 key 的时候就要千万小心了。整数 key 已经被 reference 系统用掉了,一般我们会采用字符串作 key 。
从 C 中压入字符串的效率不是最高,这是因为外部字符串进入状态机时需要重新 hash 并检查唯一性所致。关于避免直接压入字符串的问题,。( btw, 以前那个方法也不是最好的解决方案,不过还是可以作为一个参考的 :)
很容易想到,最方便且能保证唯一性的 key 是一个 light userdata 。这一点,在参考手册以及 Programming in Lua 中都有提到。
Posted by 云风 at 09:44 PM |
November 13, 2006
Lua 5.1 中文手册
前段时间安排同事工作时间翻译了
。当时的目的是想让翻译的人可以借翻译的机会深入了解这门语言。另外,其他人在查手册的时候也可以轻松一点。
事与愿违,这个中译本陆续经过了几个月的翻译和校对后,可读性依然欠缺。往往我需要去手册里查点东西的时候,都发现还不如直接看英文原版。中文译文读的晦涩倒是次要的,主要是一到关键点上就译的含糊不清,甚至有错误。
这倒符合我的观点,翻译技术资料,首先要求的是对原文的理解,然后是中文的组织水平,最后才是英文水平。
周末,我突然发神经,自己动手译了一下(其实起先只是想看看翻译这个篇幅的文档大约需要多少工时)。花掉两个半天敲了大约一万两千汉字,手都快抽筋了:)把最重要的一部分关于 Lua 语言的译完。
我想我的英文水平是很糟糕的,中文能力也不怎么样,不过满足翻译技术文档的第一要点:对 Lua 本身是很熟悉的。所以这个译本当是能看吧。
很多技术术语我没有按标准译法来译,尽量用一些更通俗但是更繁杂的译法。大部分译词,如果我认为大家普遍能接受,就统一用中文译词,只在第一次出现时括号注明英文原文。例如:事件 (event) 、元方法 (metamethod)。
如果觉得译词不太能被大众接受,大部分地方保留英文,而在第一次出现的地方括号注明我认为合适的中文译法。例如 metatable (元表)、closure(闭包)。
手册的第三部分(C API)业已完成 :D
Posted by 云风 at 05:16 PM |
September 21, 2006
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 也不会报错。知道仔细查看源码才发现其中的秘密。
Posted by 云风 at 12:56 PM |
July 27, 2006
用 lua 调用 Windows 的 API
昨天同事谈起能否给一个从 lua 中调用 Windows API 的简单方案。一开始觉得,如果是一个通用方案,那么至少需要先给出一个类似 windows.h 的原型声明,然后从 lua 来解析这些原型。大约写了几十行程序就实现了。后来又想了一下,似乎可以用一个更简单的方式,绕过原型,更简洁(但不保证安全)的方法来做到。
其间的问题就只有一个,每个 api 的参数都不一样,如何自动生成 C 中匹配的函数指针。似乎 C++ 的 template 是一个正统的解决方案。不过思考过几分钟以后,就被我否决了。实际用到的解决方案比较诡异:
先用 alloca 分配出正确的参数空间,再立刻填充这些参数,接下来以无参数的形式调用 api 。这样做,对于 __stdcall 的函数是没有问题的。好在 api 大多也是这样。
我写了这样一段程序来验证我的想法:
Posted by 云风 at 02:58 PM |
June 12, 2006
在 Lua 中实现面向对象
在 pil 中,lua 的作者推荐了,比较简洁,但是我依然觉得有些繁琐。
这里给出一种更漂亮一点的解决方案:为了贴代码和修改方便,我把它贴在了
在这个方案中,只定义了一个函数 class(super) ,用这个函数,我们就可以方便的在 lua 中定义类:
base_type=class()
-- 定义一个基类 base_type
function base_type:ctor(x)
-- 定义 base_type 的构造函数
print("base_type ctor")
function base_type:print_x()
-- 定义一个成员函数 base_type:print_x
print(self.x)
function base_type:hello()
-- 定义另一个成员函数 base_type:hello
print("hello base_type")
以上是基本的 class 定义的语法,完全兼容 lua 的编程习惯。我增加了一个叫做 ctor 的词,作为构造函数的名字。
Posted by 云风 at 11:59 PM |
March 14, 2006
使用 closure 替代 table
前几天谈到 ,我整理在
上了。今天又加了一个,关于 point 结构的封装的。
function point (x,y)
return function () return x,y end
可以用 point(1,2) 构造一个 point 。它比 {1,2} 轻量。
Posted by 云风 at 04:36 PM |
February 18, 2006
lua 5.1 final release
这一天等了很久,终于看到了这则消息:
Lua 5.1 (final) is now available at
Thank you very much for your patience during this long release process.
Special thanks to everyone that sent suggestions. They have helped make
Lua still better.
Enjoy! We can now focus on 5.2 :-)
Posted by 云风 at 09:17 PM |
February 16, 2006
lua 5.1 的 module
lua 从 5.1 开始终于官方提供统一的 module 实现标准了,这是个值得庆幸的事。今天读了下相关的源码和文档,把这套机制搞清楚了,还是很巧妙的。从简洁这个角度看,要比 python 强 :)
有一点容易被忽略掉(我的同事在用的时候就忽略掉了),module 指令运行完后,整个环境被压栈,所以前面全局的东西再看不见了。比如定义了一个 test 模块,使用
module("test")
后,下面不再看的见前面的全局环境。如果在这个模块里想调用 print 输出调试信息怎么办呢?一个简单的方法是
local print=print
module("test")
这样 print 是一个 local 变量,下面也是可见的。或者可以用
local _G=_G
module("test")
那么 _G.print 也是可以用的。
当然还有一种巧妙的方式,lua 5.1 提供了一个 package.seeall 可以作为 module 的option 传入
module("test",package.seeall)
这样就 OK 了。至于它们是如何工作的,还是自己读源码会理解的清楚一些。
在读源码时可以发现很多 lua 的技巧,还有一些 undocumented 的东西,比如 newproxy :) 它是一个 unsupported 且 undocumented 的东西,但是它希望实现的却是个巧妙的玩意。
Posted by 云风 at 07:24 PM |
February 11, 2006
lua 终于支持了16进制数
今天 lua 5.1 rc4 发布了。看了一下,比较 rc3 只改了两个地方,一个是 luaconf.h 里的 lua_popen 的宏。还有一个是增加了 hex number 的支持。
前两天在 mailist 里讨论了这个问题,其实早就有呼声加上 16 进制数了。其实我自己也写过 patch 加上,lua maillist 里 Roberto 提了个方案,只需要修改 luaconf.h 里的 #define lua_str2number(s,p) 这个宏就可以了。我测试了一下,很巧妙,还顺手附和了两句。
不过这个方法对 16 进制数前面加了负号是有问题的(虽然我认为一般不会这么用),结果还是对词法分析代码打了 patch,改动不大,而且同样也很巧妙。Roberto 这次很大方,这么快就加到官方版本并发布了。
这次因为这么小的改变就发布新的 rc ,看来 lua 5.1 的正式 release 很近了。期待,这样 lua 就拥有了官方的模块化解决方案。
Posted by 云风 at 08:47 PM |
January 01, 2006
向 lua 虚拟机传递信息
当程序逻辑交给脚本跑了以后,C/C++ 层就只需要把必要的输入信息传入虚拟机就够了。当然,我们也需要一个高效的传递方法。
以向 lua 虚拟机传递鼠标坐标信息为例,我们容易想到的方法是,定义一个 C 函数 get_mouse_pos 。当 lua 脚本中需要取得鼠标坐标的时候,就可以调用这个函数。
但这并不是一个好方法,因为每次获取鼠标坐标,都需要在虚拟机和 native code 间做一次切换。我们应该寻求更高效的方案。
Posted by 云风 at 05:47 AM |
December 25, 2005
虚拟机之比较,lua 5 的实现
前段把自己的虚拟机和编译器完成后,曾经和 lua5 做过一个比较。比较的结果很沮丧,我的虚拟机只能达到 lua 5 一半多点的速度。所以很不服气的又读了一段 lua5 的源码。而之前我是一段一段的看 lua source code 的,甚至 lua 4 和 lua5 的是在不同时期去读的,当然我也知道其间巨大的不同。
Posted by 云风 at 06:02 PM |
December 11, 2005
12K 的虚拟机
今天把脚本虚拟机整合到正在开发的引擎中去了,按新引擎的跨平台2进制格式 build 出来,只有 12.6K :D 比 lua 小多了 ^^ 庆祝一下。如果不是现在机器都是 32位了,在 16 位或者 8 位机上,这代码体积还能更小。唉,早几年计算机的地址空间只有 64K 的时候多痛苦啊。
突然想,我们这套引擎给手机用一定很不错 尤其是 gc 部分,比 lua python 什么的更适合小内存环境,可惜我现在对嵌入式开发没啥兴趣。
Posted by 云风 at 08:37 PM |
December 10, 2005
基于并行处理的垃圾回收方法
最近在做的一个虚拟机是基于垃圾回收(garbage collection)的,采用的是标记整理算法。这种算法的好处在于不需要 太多额外的内存,而且可以将内存中的 garbage 完全压缩掉。至于长期占用的内存空间,会被压到内存块的底部,整理时无须移动。
对于需要长期稳定运行的服务器程序,在 32bit 操作系统下,受限于有限的地址空间, gc 技术是根本解决内存碎片问题的最佳通用方案。
我计划在服务器程序中,全部程序逻辑

我要回帖

更多关于 protobuf 的文章

 

随机推荐