|
文/顾煜 知乎专栏 https://zhuanlan.zhihu.com/gu-yu
前文回顾:The world at your fingertips — 天涯明月刀幕后21(妥协)
多进程加载
32位系统中,内存的使用上限只有2G,打上系统补丁,可以用到3G。对于一个MMO,这点内存不太够。早期开发并不重视,内存该用就用,飞奔向前的项目,很快迎面撞上了内存墙。
第一波遇上问题的是编辑器。在改用大地形体系后,内存马上吃紧。有几张内容比较充实的地图,已经没有足够的内存可以跑。于是大家开始注意内存使用,用上了各种streaming手段,确保美术能正常工作。
再往后,在游戏中的内存也不够了。玩家的电脑普遍都有4-8G的内存,但是我们的游戏只能用2G。于是streaming系统进一步被加强改善,确保随时只使用最精简的数据集合。
为了改善内存情况,一方面要节约,我们需要减少无谓的浪费,另一方面也需要开源,寻找一些方法使用更多的内存。
某次看技术文章的时候,突发奇想,windows系统可以跨进程做内存共享,那如果我把大量内存放在其他进程,然后再映射回我的游戏,每次在一个滑动窗口中访问,不就可以使用更多的内存了。
想归想,实际要用起来还是不太方便,对逻辑代码影响比较大,很多时候内存不能直接访问,要封装一层,带来了不小的改动成本和运营时刻开销了。
想法暂时按下,但心却狂野奔放,寻找着宣泄的出路。
终于有一次做版本的时候,内存又爆了,我们分析内存占用,留意到为了减少卡顿,程序员分配了一个大约100m的预先缓存buffer,在加载线程中不停做预读取。暂时先禁用了他的预读取,确保程序能正常使用,同时我想了一下,动态streaming和预读取,正好很符合我那个预读取的场景。于是我规划了一下,找何老师做了预加载进程,把所有的加载逻辑放到了新的进程中,和主线程做一些进程间的通信,接受主线程的加载建议,做按需加载,也会自主做一些提前预加载,放进分配的内存,然后通过进程间的内存共享机制,把加载的数据,共享给主进程使用。主游戏进程,永远只要维护一个很小的内存窗口,大量的内存数据,都在另一个进程中。
虽然工作量不小,但这样做有很多好处。
首先,所有的访问io的操作,都是streaming系统接管,这意味着所有的改动,并不会影响所有上层的逻辑代码,因为它们本来就是通过streaming系统来异步访问的,所有改动对上层造成的影响,到streaming系统为止,不会往上传递。这样整个系统的复杂度得到了很好的降低。
其次,跨进程访问造成的复杂度以及对性能的影响,和streaming系统面对的低速IO相比,都不算个事,可以认为不会比原来系统更慢。
再次,这个系统是scalable的,根据玩家内存不同,可以更激进的分配缓存内存,对于高端电脑,独立加载进程可以用更多的内存,更多缓存常用数据,由于这些缓存数据是跨进程的,带来了新的好处,如果玩家喜欢双开游戏,也会从中获益,可以共享这部分内存。双开游戏如果要去同一个地图的同一个地方,则缓存的数据会被重用,第二个游戏窗口的加载速度就非常快。
最后,由于跨了进程,所以我们可以用到大于2G的内存,降低了主游戏进程的内存压力。
附带的一个好处,是这个模块是完全可以在不同游戏中复用的,因为所有的输入输出都是标准化的,它本身也不知道上层的逻辑,只负责离线IO,所以重用性比较高。
为了充分加快加载速度,我们也做了很多别的优化,比如chrome浏览器开发团队介绍过的操作系统预读法,又比如根据玩家位置,如果靠近关卡传送点,也会提前加载一些其他关卡的数据,反正一切读写硬盘的io操作都是异步的,根据我们的各种预判,可以尽可能多的做一些预先加载工作。而独立这个加载系统在外部,也可以很好的降低主游戏的开发复杂度。
优化
由于游戏scope越来越大,后期我们对产品效率开始做了深入的优化。
以往做主机,传统项目的优化,一般是2-3个资深程序员,做上2-3个月就差不多了,天刀这个项目,优化基本持续了整个项目的始终。从很早期开始,我们就一直有人在做优化的工作,一直持续到项目上线。
PC的优化是一个不太好做的事情,机器配置千变万化,PC优化也是一个幸福的事情,可用的工具集非常强大。
优化中我们的主要原则有几个。
先是能更好的收集性能问题。我们建立了持续测试的框架。常见的性能问题点,都覆盖了非常多的性能收集点,经常跑一下,随时可以留心到性能异常。也有dashboard,可以定期根据地图,遍历各个位置,跑性能收集,画热度图。同时也在项目中普及了查找性能问题的流程,策划美术在运行游戏的时候,碰到性能问题,可以随时截取相关数据,然后放声尖叫,召唤程序员过来查找问题。
再是工具层面做好充分的研究。每一种工具,都有擅长解决的问题,和不擅长解决的问题,要对症下药。有些工具可以快速给你性能全貌,有些工具擅长解决卡顿问题,有些工具实时监控引擎性能,有些工具只能离线分析。在不同的场合,使用不同的工具处理,会有不同的惊喜。
三是定期有大型长期的优化目标。针对专项领域,我们一般都会安排有人花整段时间去研究,且这个优化的人,每个阶段是轮换的。这有很多好处,从个人成长角度看,每个人去玩一下优化,有助于变成一个更好的程序员,知道了性能开销,就不会乱写代码。当然我们去优化的人一般比较senior,这个不是最大的考虑因素。最大的因素是考虑到每一个开发人员都有盲区。我一直profile一个东西,可能久而久之,就有盲点了,对很多问题视而不见,思路会受限。换一个人,一眼就有可能发现里面的问题。
四是在性能优化方面,重点关注卡顿问题,而不是平均帧数问题。很多时候游戏平均帧数其实不低,一直在50帧以上,但只要某一帧卡顿一下,有100ms以上的延迟,就很容易能感觉到帧数问题。我们在中后期,专注看卡顿问题,尝试解决所有100ms以上的卡顿问题,发现一个解决一个。这里的问题并不容易解决,大多数同类卡顿是因为不小心用了同步的硬盘io,但也有很多是一次创建太多材质纹理,一次创建过多对象,或者是一次初始化过多物件。修改这些问题,往往要深入引擎内部,进行细致调整,或者修改一些系统的实现方式,才能缓解卡顿。为了做好这些优化,我们把所有加载都异步化,对所有可能造成性能瓶颈的初始化、创建部分也做了控制,这些系统有严格的时间预算控制,超过了,本帧就跳过,留给下一帧继续处理。
经过数个版本的优化,效果应该说是相当理想的。由于渲染的东西够多,我们的平均帧数并不能如当时同类游戏一样高,但玩家普遍反映很流畅。在游戏运行过程中帧数变化比较平均,所以整体感受是比较流畅。
当然也不是所有的性能问题都会影响感受,比如好多游戏放大招的时候,由于太多全屏特性,会有剧烈的卡顿,然而玩家不怒反喜,有种莫名的快感,那是积攒多时的郁闷,那是心灵释放的解脱。我们把这种快感称之为技能越强卡顿越大快感越爽综合症,越大的招数,自然要卡顿,让玩家能感受到其中的快感。对于这类问题,我们降低优先级,但最后还是全处理了一下。一个稳定的帧数,对整体游戏品质,是有巨大影响的。
|
|