« 近期攀岩小记 | 返回首页 | Lua 字节码与字符串的共享 »

开发笔记(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 分。即使仅考虑单线程分数,也在两倍以上。


这个月的改进最重要的在四大点上,目前还没有准确评测到底是那处改进带来的收益最大。

其一,我们改进了 AOI 模块 ,由于接口和用法改变,需要重新做整合工作。

其二,组播部分做了优化。(见 skynet Skynet 设计综述 的组播一节)。

其三,把公式计算模块 抽象出来,用 C 语言重写。

其四,之前,我们在 agent 上计算和玩家有关的技能伤害,通过共享状态的方式同步给 map 服务。目前把计算全部挪到了 map 里计算。关于这点改动,是这次耗时最长的部分,也利弊共存,下面详细这一点。

当初发现的一个热点是对玩家属性的共享读写。倒不是因为数据共享需要加锁的代价。而是,lua 在读取 userdata 中的数据需要代用外部的 C Function ,其开销远大于 Lua 对内建的 table 的读写。(大约有 4 倍的差距)而公式计算则需要大量读写这些属性域。

我改写了公式计算部分,这个改动也使得不再想把战斗相关属性放在一个复杂的共享结构中了。

这样,我们需要把玩家的属性计算从 agent 中挪到 map 中去。这是违背最初的设计的。因为,我希望整个系统中对一组数据的写操作仅存在一处,而其它地方都只是对它的读操作。这样比较干净,也不必用复杂的锁。在理解数据流时也相对清晰。

说服我做这个改动的理由是,战斗系统全部存在于 map 中,map 负责了玩家和玩家、玩家和 npc 、npc 和 npc 间的战斗计算。这些过程是一致的,不因为参战方是玩家还是 npc 有所区别。但我们无法将 npc 也如玩家那样用一个 agent 代理独立出去,所以,就一个服务处理一类操作这个角度而言,把玩家的战斗计算放到 map 中处理是合理的。而在数据上,我们应当把玩家的战斗相关属性于玩家身上的背包以及他自身的非战斗属性(比如名字),看成是两类东西。这样就能很好的解释为什么可以让玩家的战斗属性由一个服务去写,而背包这样的数据却由另一个服务改写。

但这样的修改并不是没有弊端的。最大的问题就是,之前 agent 和 map 分离,agent 可以承担一部分战斗计算工作。这是可以和 map 上的计算工作并行的。不同 agent 间,如果完全没有相关性,他们的战斗计算也是完全并行的。现在在同一 map 中,就失去了并行的好处。

所以,之前固然性能不佳,但可以撑满所有可以用的 CPU 资源,并可以预期增加 CPU 数量还有提升空间。目前,我们 CPU 占用率仅仅 130% ,用 top 查看,8 个核,每个都平均在 20% 左右,但超过 240 人后,client 的包延迟就明显增加了。这应当是 map 处理能力不够造成的。目前的设计中,单个 map 以轮询的方式处理每处战斗,战斗数量增多后,延迟必然增加。

mike 同学认为许多代码还有优化的余地,还有一定的提升空间。我也相信,即使不再优化代码,更新硬件后也还可以轻松把性能翻倍。目前,CPU 的整体资源还有相当大的余量,大约同台机器同时开 6 个以上这样的场景也能轻松胜任。那么,就目前的硬件配置,单机 1200 活跃在线玩家应该不成问题的。我相信在明年上线时的硬件水平下,单台机器应当可以支持到 5000 同时在线用户。


最后谈谈 LuaJIT 的问题。

我一直觉得 Luajit 是我们可以预支的一张性能提升的支票,但总有些小问题迟迟没有兑现。

之前我认为最大的问题是内存限制。Luajit 在 64 位平台下,只能使用 1G 内存。即使在同一进程内开辟多个 lua state 也不例外。查看代码可以知道,luajit 为了结合更高效的 gc ,使用了自己的内存管理器。这需要用一个 32 位的伪指针工作。为了适应 64 位平台,它强制让分配器分配出来的内存的高位都必须是 0 。所以,多个 lua state 也需要满足这个条件,而不可能突破 4G 的限制。

我们现在单个 agent 的内存消耗已经接近 10M (我认为有优化空间,可以下降到 2M 左右),那么 1000 个 agent 的内存总量是很难低于 1G 的。

昨天我想了个办法来突破这个限制。那就是在 lua state 创建时,预先分配好 32M 内存(假定我们单个 state 这些内存足够使用了)。保证这块内存的高 32 位是一致的。那么就可以改动一下 luajit 的源代码,增加少许成本,让 32 位伪指针正常工作。

ps. 这个想法 在 2009 年时有人提到过.

再构思这部分该怎么修改的过程中,我突然发现另一个问题。

luajit 居然使用了 NaN Trick ,这导致 lightuserdata 仅有 48 位有效值(可见 luajit 的源代码 lj_obj.h)。这和我们 在 lua 中使用 64bit 整数 的方式是有所冲突的。而后者对我们相当重要。我们不可能用 luajit 中推荐的 boxed cdata 的方式使用 64bit 整数。这是因为,我们在很多代码中,使用了 64bit id 做 table 的 key ,而那种方式表示 64 位整数,相同的数值可能呈现成不同的 key 。

从代码中去掉 NaN Trick 也是挺复杂的了。相对于 Lua 5.2 ,NaN Trick 是可配置的,且默认并不开启。

Comments

想问下客户端的效率能同屏支持200人吗?+1 500人的战场想想都激动,不清楚客户端有没保证。 致敬

嗯,之前看到这篇文章一时技痒发了下自己的想法, 现在想想我不该乱干涉别人的设计,无论如何,祝大家好运~

页游ARPG开发周期短,云风为何不考虑用自己的技术在页游上多做些目前行业内不曾有的突破

修正协议为48bit或62bit~

我觉得在这种状况下正确的做法应该是修正协议,而不是hack lua~

对通用的极致追求, 往往反而会对开发者造成巨大的限制. 既然lua不支持int64, 你要不选择放弃lua, 要不选择放弃int64, 然后我前面想说的是lua选择只支持double自然有他的道理所在,你又何必执着于int64呢?如果是通过trick的方式暂时的支持了,就埋入了之后lua版本更新时带来的潜在风险,得不偿失. 以上纯属个人看法.

所谓协议, 就是在不考虑别的模块怎么用它的前提下所要满足的所有技术要求.

如果协议中 id 指明是 64bit 就不能认为 63bit 是够用的.

也不能假设协议的应用范围和使用方法.

嗯,如果是我做这个问题,我会暂时使用在100年内理论上够用的48位数据,然后在找到完美扩展到62位之前先凑活着, 知道找到完美方案再作patch

哦,刚又想到还有个质疑,就是double是否真的只有48位有效性,浮点数做比较的时候,和整数行为不一致的实际上只有-0, +0, -NAN, +NAN, 所以直觉上只需要损失2位,但如何安全的使用剩下的62位并使它符合人类直觉可能需要一些精巧的设计,个人暂时还用不上这方面,我就不深入了,只提这么个思路.

最后,如果你实在是不放心,你仍可以在C里面使用64位ID,然后降低lua的在你整个系统里的权限,使之不会接触到需要超过48位ID的大环境.总之就是, 其实程序里没有什么太了不起的问题,别把问题想复杂了~

况且能够容纳48位ID的内存还不知道是几百年几千年以后的事~

其实48位做id是足够了的,2^48 = 281474976710656, 假设一个人能活100年, 100(年) * 365(天) * 24(小时) * 60(分钟) * 60(秒) = 3153600000秒, 281474976710656 / 3153600000 = 89255.130, 也就是说对于单人而言,每秒钟要消费89255个id, 才会使得48位不够用,所以如果需要妥协,我会考虑直接用double而不必执着于int64(虽然我不用lua而不需要做这个妥协~)

在同一台机器上,200 个机器人的混战 CPU 占用率平均仅 130% 左右。


想问下客户端的效率能同屏支持200人吗?

云风,麻烦问下你们对机器的benchmark分数是用什么计算的?

期待突破luajit的64位下1G内存的限制

@张鹏

暂时不用了。我在原来的招聘贴中已经做了说明。

云风哥 冒昧的问下你现在还要web开发人员么

Post a comment

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