共享 lua state 中的数据
今天和倩女幽魂的同事讨论一个问题:他们的游戏 client 中,有大量策划填写的表格直接导入 lua state 中的大量数据。大约有 100M 以上。这样,如果玩家在一台机器上启动多个 client ,就会占用大量的内存。
而这些数据,一旦加载进 lua ,就不会再修改,且每个 client 中数据都是一致的,这是一种浪费。
问题是:如何利用进程间的数据共享,在多开 client 时节省这些空间。(同时也可以加快开第二个 client 的启动速度)
如果数据不存在于 lua state 中,而直接用 C 访问。可以用简单的共享内存的方式解决。关于 Windows 下共享内存的方法,我曾经写过一篇 blog 介绍。
如果是非 windows 系统,这个问题解决起来也很容易。
在 lua 加载完所有数据后,做一次 fork ,让所有多开的 client 都是一个子进程即可。
一开始,我们讨论了设置多个 lua state 的方案。让策划的数据表放在一个独立的 state 里。访问这些数据用跨 lua state 的方式进行。
不过这个方案实现起来比较麻烦,而且性能很低。如果想透明的访问数据 state ,需要做大量的 metatable 。甚至并不能节省内存空间。
最终可行的方案是这样的。
使用一个自定义的内存管理器。第一份 client 启动后,
- 初始化 lua state ,不初始化任何用到的库。
- 通知内存管理器,切换一个 heap ,并加载所有策划表格。以及用到的 C 库。(因为 client 相同,这些库中函数指针地址也相同)
- 做一次完整的 gc 。再次通知自定义内存管理器切换一个 heap 。
- 此时应该有 3 个 heap 。一个保存了 lua state 的结构,一个保存了策划表的数据,一个是空的,用来存放以后 lua state 中的所有数据。把第一个 heap 复制一份共享,(并提供原始的地址信息)。第二个 heap 直接共享,如有可能,把这个 heap 的页设置成只读。
- 以后的内存管理全部在新的第三个 heap 中进行。并在 free 操作中对企图在老 heap 上的操作做 assert 。
第二份以后的 client 启动时,如果发现有共享的 heap ,把第一份 heap 取到,并复制到指定的地址空间。以只读方式映射第二个 heap 。并在指定位置创建第三个 heap (和主 client 的三个 heap 的地址保持一致)
btw. 如果实现上允许,可以让内存管理器讲第一和第三个 heap 合并成一个。中间只是一个切换的过程。那么,总共只需要两个 heap 即可。
在 client 进程退出时,应当按如下次序:
- 取消内存管理器中的 assert
- 所有针对第二份 heap (存放策划表格的那个)上数据的 free 操作全部忽略掉。
- 关闭 lua state
或者,直接选择不关闭 lua state
这样做之所以可行,基于以下几点:
- 我们可以利用自己的内存管理器,让每个 client 在初始化后,内存布局完全相同。所有的策划表格存放在一致的内存地址上。(相同地址的 heap page ),lua state 的指针也完全相同,结构体内的指针也正确的指向相同的位置(在独立的 heap page 中)。
- lua 的 C 库中函数指针在所有 client 中的地址一定相同,所以是一致的,可以共享。
- lua state 的结构在初始化完毕后是一致的,虽然以后会被修改,但我们是以复制的方式存在于每个 client ,所以相互不会受影响。
Comments
你好最近一直在研究lua与C++的一些知识,有什么好书推荐一下吧,我今天找了一点都没弄好究竟怎么将C++的类对象传到lua中进行使用
Posted by: Anonymous | (18) July 15, 2010 08:34 PM
@dwing
:-) 我觉得你的方法是正解,但这要求在写所有逻辑的时候,都要考虑好多实例的问题。
这个需要重开始写的时候就注意,已经完成的客户端要改,成本就大了。
Posted by: kasicass | (17) June 19, 2010 08:39 AM
这个题目很有趣
Posted by: mike | (16) June 10, 2010 10:20 AM
创建一个代理类,
在代理类实例中包含一个到共享lua_state 中的表的引用,
在多开lua_state 中,操作代理类实例就可以了。
代理实例只要保存引用,几乎没有内存浪费
代理对象用C写,性能几乎没有损失
看起来应该可行
Posted by: mike | (15) June 10, 2010 10:19 AM
直接调API,读取数据后做共享内存不就行了。为什么要在LUA上折腾?
如果LUA需要用操作数据,给开接口不就可以了。
Posted by: kevin_chou | (14) June 8, 2010 04:09 PM
@r91085
链接已经修好。
Posted by: cloud | (13) June 7, 2010 01:59 PM
請問雲風前輩在您 wiki 中的 "•Pentium 及 MMX 代码的优化 "這份文件現在是否無法瀏覽了?
Posted by: r91085 | (12) June 2, 2010 11:50 AM
谢谢楼主分享!
Posted by: 宠物兔 | (11) May 31, 2010 09:26 AM
如果游戏很关注多开,为什么不直接考虑单进程呢? 只要把客户端的部分单件拆成实例,渲染引擎支持多场景实例及多窗口渲染就解决了.
Posted by: dwing | (10) May 30, 2010 10:24 AM
我一般很少关注这个blog里面关于lua的讨论,不过今天突然有个想法说下:
花那么多精力在客户端的lua的性能上面得益有多少?只有不是太差需要改进的话,作为游戏客户端性能瓶颈不会在这个地方有着明显影响吧?即时是出现了比较离谱的现象,也恐怕是脚本书写的不好是主要原因吧?
浪费资源桌面软件实在太多,少一个不少啊。
如果是lua应用到Server端,在海量部署的情况下,挤出一点性能就能节约一批成本,但是如果是Server端,恐怕有些场景用解释语言来跑业务也算是比较奢侈的方案了。
Posted by: 小x | (9) May 28, 2010 01:32 PM
对啊,游戏客户端的美术资源也应该在多开的情况下使用同一份,这样第二个客户端启动速度就更快了。
还有请问切换堆是什么意思,怎么办到呢?
Posted by: DD | (8) May 28, 2010 10:54 AM
话说我们也有这个问题
Posted by: rainfiel | (7) May 28, 2010 10:27 AM
多开client之间,可能需要
共享所有只读资源。云风兄有没有考虑过lua之外的更通用的方案?
Posted by: dayn9 | (6) May 28, 2010 08:55 AM
@tonyzzp
我给不了任何抽象的建议。如果有,我曾经都写过。
另外,写邮件比留言更好。当然留言有可能有更多人回复。
Posted by: Cloud | (5) May 28, 2010 12:18 AM
还是动态加载策划数据吧,打补丁的方法补丁只会越来越多最后成了九袋弟子了。
Posted by: mosquito | (4) May 27, 2010 10:55 PM
很乐于看你的文章
可惜我的留言你还是没有回复
好遗憾
麻烦有时间帮我看看吧
指导下.谢了.
Posted by: tonyzzp | (3) May 27, 2010 10:51 PM
100M的策划数据表格可真是海量啊(擦汗)昨天才发现Lua 5.2 (work3)release了。另外请教一下lua_state的生存期问题,如果在Server中让其依附于某个具体的玩家Actor,那么随着玩家的下线,是不是需要lua_close呢?
Posted by: xuruoji | (2) May 27, 2010 10:26 PM
那些策划数据应该放在专门的数据文件中
Posted by: Anonymous | (1) May 27, 2010 05:16 PM