« 千呼万唤始出来,结果是这么白痴的设定 | 返回首页 | 采访 Lua 发明人的一篇文章 »

共享 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 启动后,

  1. 初始化 lua state ,不初始化任何用到的库。
  2. 通知内存管理器,切换一个 heap ,并加载所有策划表格。以及用到的 C 库。(因为 client 相同,这些库中函数指针地址也相同)
  3. 做一次完整的 gc 。再次通知自定义内存管理器切换一个 heap 。
  4. 此时应该有 3 个 heap 。一个保存了 lua state 的结构,一个保存了策划表的数据,一个是空的,用来存放以后 lua state 中的所有数据。把第一个 heap 复制一份共享,(并提供原始的地址信息)。第二个 heap 直接共享,如有可能,把这个 heap 的页设置成只读。
  5. 以后的内存管理全部在新的第三个 heap 中进行。并在 free 操作中对企图在老 heap 上的操作做 assert 。

第二份以后的 client 启动时,如果发现有共享的 heap ,把第一份 heap 取到,并复制到指定的地址空间。以只读方式映射第二个 heap 。并在指定位置创建第三个 heap (和主 client 的三个 heap 的地址保持一致)

btw. 如果实现上允许,可以让内存管理器讲第一和第三个 heap 合并成一个。中间只是一个切换的过程。那么,总共只需要两个 heap 即可。

在 client 进程退出时,应当按如下次序:

  1. 取消内存管理器中的 assert
  2. 所有针对第二份 heap (存放策划表格的那个)上数据的 free 操作全部忽略掉。
  3. 关闭 lua state

或者,直接选择不关闭 lua state


这样做之所以可行,基于以下几点:

  1. 我们可以利用自己的内存管理器,让每个 client 在初始化后,内存布局完全相同。所有的策划表格存放在一致的内存地址上。(相同地址的 heap page ),lua state 的指针也完全相同,结构体内的指针也正确的指向相同的位置(在独立的 heap page 中)。
  2. lua 的 C 库中函数指针在所有 client 中的地址一定相同,所以是一致的,可以共享。
  3. lua state 的结构在初始化完毕后是一致的,虽然以后会被修改,但我们是以复制的方式存在于每个 client ,所以相互不会受影响。

Comments

你好最近一直在研究lua与C++的一些知识,有什么好书推荐一下吧,我今天找了一点都没弄好究竟怎么将C++的类对象传到lua中进行使用

@dwing

:-) 我觉得你的方法是正解,但这要求在写所有逻辑的时候,都要考虑好多实例的问题。

这个需要重开始写的时候就注意,已经完成的客户端要改,成本就大了。

这个题目很有趣

创建一个代理类,
在代理类实例中包含一个到共享lua_state 中的表的引用,

在多开lua_state 中,操作代理类实例就可以了。

代理实例只要保存引用,几乎没有内存浪费
代理对象用C写,性能几乎没有损失

看起来应该可行

直接调API,读取数据后做共享内存不就行了。为什么要在LUA上折腾?
如果LUA需要用操作数据,给开接口不就可以了。

@r91085

链接已经修好。

請問雲風前輩在您 wiki 中的 "•Pentium 及 MMX 代码的优化 "這份文件現在是否無法瀏覽了?

谢谢楼主分享!

如果游戏很关注多开,为什么不直接考虑单进程呢? 只要把客户端的部分单件拆成实例,渲染引擎支持多场景实例及多窗口渲染就解决了.

我一般很少关注这个blog里面关于lua的讨论,不过今天突然有个想法说下:

花那么多精力在客户端的lua的性能上面得益有多少?只有不是太差需要改进的话,作为游戏客户端性能瓶颈不会在这个地方有着明显影响吧?即时是出现了比较离谱的现象,也恐怕是脚本书写的不好是主要原因吧?

浪费资源桌面软件实在太多,少一个不少啊。

如果是lua应用到Server端,在海量部署的情况下,挤出一点性能就能节约一批成本,但是如果是Server端,恐怕有些场景用解释语言来跑业务也算是比较奢侈的方案了。

对啊,游戏客户端的美术资源也应该在多开的情况下使用同一份,这样第二个客户端启动速度就更快了。

还有请问切换堆是什么意思,怎么办到呢?

话说我们也有这个问题

多开client之间,可能需要
共享所有只读资源。云风兄有没有考虑过lua之外的更通用的方案?

@tonyzzp

我给不了任何抽象的建议。如果有,我曾经都写过。

另外,写邮件比留言更好。当然留言有可能有更多人回复。

还是动态加载策划数据吧,打补丁的方法补丁只会越来越多最后成了九袋弟子了。

很乐于看你的文章
可惜我的留言你还是没有回复
好遗憾
麻烦有时间帮我看看吧
指导下.谢了.

100M的策划数据表格可真是海量啊(擦汗)昨天才发现Lua 5.2 (work3)release了。另外请教一下lua_state的生存期问题,如果在Server中让其依附于某个具体的玩家Actor,那么随着玩家的下线,是不是需要lua_close呢?

那些策划数据应该放在专门的数据文件中

Post a comment

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