战斗性能优化个人经验
前言
最近在做项目的性能优化。这里把做性能优化的一些个人总结。
由于本人为gameplay程序员,主要重点也就是战斗脚本侧部分,不涉及到各种源码,底层优化。
优化目标
在这里简单介绍一下项目,作为一个帧同步开房间战斗老游戏,底层代码都比较原始,没有用什么多线程、客户端提前预测等优化方式。可以说是最经典的帧同步游戏,同步指令到每个客户端上分别执行。
在这次优化中,主要是希望能够从战斗脚本侧找到优化点。因为战斗脚本如果单帧时间过长的话,不可避免地导致了主线程阻塞和卡顿,这会显著影响玩家体验。因此本次的优化目标就是优化战斗脚本中的卡顿点。
请注意,这个时候还并没有确定是否真的存在战斗脚本卡顿点。
数据支持
获取数据
项目上线很久了,在线上跑时自然有大量的日志记录和工具,但是由于年代久远、人员迭代、缺乏文档,各种各样的缘故导致并没有办法直接得到有意义的数据。
在对于战斗内日志的梳理之后,总结出来战斗日志的几类数据,最后结合工具,找到了有意义的数据,每帧逻辑执行时间。
这是最直观的脚本数据,在此基础上,可以进行进一步分析。另外、垃圾数据真的很多,完全被淹没了。
数据的意义
在知道这份数据后,第二步是,理解该数据的意义。逻辑执行跑久了自然不行,但是怎么样算久呢?
在使用项目已有的工具人工翻看了多局战斗的各项数据波形图后,得到了确认的答案。
当单帧脚本逻辑执行超过40ms时,会明显挤压后续的步骤、导致战斗处理延迟、渲染延迟、FPS急剧下降。在超过100ms后,更是一段剧烈的波折。因此得出了性能优化的目标。解决脚本中的性能问题。
在这一步时,才首次明确了痛点、缘由和目标。
数据分析
知道了数据的意义之后,需要深入分析数据。光靠每一帧的逻辑执行时间不能说明问题。这边正常项目应该都有对应工具,只是该项目比较老又缺乏,只能先临时做一份。
这边先写了个python脚本和对应GUI,将项目的大量战斗数据保存到本地的MongoDB后再进行分析。
一局战斗中,逻辑执行超过40ms的比率和逻辑执行超过100ms的比率分别作为指标。对于各个觉得相关因素进行排查。
从对局英雄、对战地图、投放AI数目、投放AI种类等多个维度尝试分析了之后,得出来了结论。
投放AI的数目,对局英雄都是会显著影响卡顿率的因素。
战斗分析
这一步就是具体分析到逻辑卡顿点了。在分析完后,选出了多局明显的战斗录像,采用tracy分析。
由于战斗录像是帧同步回放,由于采用PC分析,因此在tracy使用时,略微放宽了条件,标注逻辑执行超过30ms的帧用来协助定位逻辑问题。随后人工排查性能糟糕的帧,总结其中的脚本逻辑问题。
解决方案和问题
在Tracy分析了多局战斗后,总结出来项目的主要几个卡顿点。
这里从解决方案入手,来梳理问题。这样子更有助于容易理解问题本质。
C++化
C++化永远是脚本语言的最后防线,没有什么比把lua和python重写成C++能获得更高的性能提升了。当然代价也是存在的,不好迭代、复杂、后续热更不支持等导致不能当银弹使用。
问题
本次排查中,发现AI在寻路阶段会出现明显卡顿,单AI寻路某些时候可达30ms以上,对于这种常年不变的底层代码,最后顺理成章地采取了C++化解决方案。
确定AI问题初期后以为AI的性能问题源于行为树的复杂和写法随意缘故,但实际总结后发现,实际卡点还是在寻路上。
分帧
一件事情在一帧内执行多次导致的性能问题,总可以考虑分到多次执行的。
问题
本次排查中,发现部分角色会创建多个道具,由于道具创建本身涉及到大量的物理层操作,顺利成章地耗时过大。最后和策划讨论后,将部分技能的实现改为分帧处理。
分帧创建在正常游戏开发中应该由创建层本身处理的。但是由于项目较老,同时早期代码不规范。往往写法上会直接获取创建的道具,自动分帧的话会导致不能获取到道具从而出现BUG。在后续开发时需要保持注意,对于创建的道具的使用逻辑应该考虑作为创建后的回调。
同时,分帧对于具体细节还是需要和策划商讨的,不应该为了性能优化而在不知情地情况下发生了逻辑变化。
脏数据
脏数据实际上是对于错误逻辑的解法。往往处于这样一个情景,一帧内某些数据多次变化,每次变化都产生了完整调用,但是只有最后一次调用的结果才有效,这会导致没有意义的性能消耗。脏数据就是通过标记,统一一并处理。
问题
排查中确实发现,某些UI对于BUFF情况的依赖、AI对全局的评估效用系统均会同一帧多次调用。再确认后进行迭代,减少性能消耗。
预加载
避免资源的加载时间采用预加载,已经是非常经典的解决方案了。
问题
部分配表,某些技能图标变动均会导致预加载。做了一个统一的注册加载机制以在战斗加载过程中,各自注册预加载相关资源。
这里发现某个很常用的资源配表,如果在各种功能中遇到都会加载,只有少数直奔战斗的才会导致战斗预加载。这个预加载问题在正常流程中很难发现,但是看录像时直接进战斗能够还原这个情景。
合理逻辑
总有些写的很奇怪的逻辑导致一些没必要性能消耗,但实际上影响不大,只是仍需要培养一个好的代码习惯。
迭代期望
对于一个老项目来说,确认了我们性能卡点目前和战斗脚本逻辑其实联系不大,并不需要频繁查看相关问题。暂时性能优化推进也就到这一步了。
如果需要这方面迭代的话,应该追求的是自动化,将线上数据拉取分析整体自动化,并且看能否接入tracy直接分析性能缺陷。