客户端性能测试
目标
一般来说,客户端性能测试主要是为了评估客户端运行是否顺畅,是否容易出现崩溃闪退等问题。曾今也专门配合客户端引擎开发进行测试,一方面验证引擎的优化效果;另一方面通过测试数据来规范美术资源的标准,例如场景角色的面数、数量,特效的大小规格等等。
在实际的产品开发过程中,性能测试的数据主要用作参考,并不是优化的主要目标,除非是为了做一些技术研究。出于商业产品考虑,需要在性能与产品体验之间做权衡,而不是一味地追求性能数据上的好看,只需要确保性能数据在一个可接受的范围内即可。
注意:目前并没有哪个性能测试工具,可以在记录性能数据的同时记录下具体的客户端行为,大多只能统计客户端代码接口的调用和响应时间。所以进行客户端性能测试时,需要在测试过程中持续地观察客户端运行的情况,记录过程中发现的问题,然后再通过时间轴与数据进行对比,帮助分析和定位问题。
衡量指标
前面介绍通用性能测试思路的时候,提到了很多性能测试的数据指标,例如CPU、内存、I/O读写、网络流量等。对于客户度性能来说,有两个虽然比较模糊,但更直观,更具有代表性的指标,一个是代表客户端流畅度的『帧率FPS』,另一个是衡量业务响应速度的『等待时长』。
帧率FPS
FPS:Frame per Second
一般来说,游戏客户端运行的帧率,最好能保持30以上,低于24就会感到画面不连贯,低于15就有明显的卡顿。影响帧率的因素有很多,它是综合影响的结果,可能是计算量太大(如CPU/GPU占用高),也可能是读写量大(如加载很大块的资源),甚至还可能是网络问题(收发包太多)。所以如果发现客户端整体FPS较低,需要进一步定位问题时,就需要设计专门的测试场景,排除其他因素的干扰。例如测试场景大小对性能的影响时,场景里不应该有角色;而测试角色数量对性能的影响时,不应该加载场景地图。而收集FPS的性能数据,iOS设备可以通过Mac系统Xcode自带的Instruments,具体方法可参考iOS客户端性能测试,而Android因为硬件和系统版本差异的问题,很少有稳定统一的获取方法。但都可以让前端程序帮忙,在客户端运行时,自行计算FPS并显示或打印出来。
最常出现FPS帧率较低的情形有以下几个:
- 播放剧情动画。
- 加载地图,尤其是像主城、野外这种大型场景地图。
- 第一次打开界面,界面上有很多贴图或特效。
- 同屏大量角色移动,并释放技能。
- AOE范围攻击技能打到多个敌人,飘血冒伤害数字。
- 3D场景地图,平视时显示大量的远景物件。
等待时长
所谓的等待时长,与我们常说的“响应时间”有所不同。响应时间指的的是从客户端端发出请求操作,到服务端进行处理并将结果发给客户端进行展示的等待时长,这里其实关注的还是服务端的性能。游戏客户端除了收发服务端数据,自身也有大量的计算,例如加载资源,计算绘图,初始化数据等等,这些业务逻辑相关的才是真正影响客户端性能的主要内容。
通常比较关注的两个等待时长是:客户端启动时长,还有加载地图时的loading时长。这两个时长越短越好,一般启动时长不超过3秒,而加载时长应在10秒以内,如果时长很难通过优化缩短,就要给予玩家动态提示,降低等待的焦虑感。测试时长的方法,最简单的就是自己观察和记录时间,比较精确的还是找前端同学帮忙,将处理时长写到log里。在Android系统上,app的启动时长可以通过adb命令获取下:
$ adb shell am start -W com.speed.test/com.speed.test.HomeActivity
Starting: Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] cmp=com.speed.test/.HomeActivity }
Status: ok
Activity: com.speed.test/.HomeActivity
ThisTime: 496
TotalTime: 496
WaitTime: 503
Complete
但如果想要大规模的统计玩家的启动时长,就还是得客户端自己计算,然后上传给统计服务器。以下是一个模拟我以前做过的项目统计不同设备上,地图加载时长的绘图,通过这个绘图可以用来分析哪些场景地图是否过大,需要优化。
其他指标
在实际的客户端性能测试工作中,要收集的数据肯定不止FPS和等待时长,收集最多的还是前面提到过的CPU占用率、GPU占用率、内存消耗、显存消耗、流量消耗、电量消耗等,FPS和等待时长反而因为各种工具限制,往往很难收集到,需要客户端程序同学帮忙计算和打印出来。收集客户端性能数据的工具有很多现成可用的:如Android系统上有腾讯的WeTest,网易的Emmagee,iOS系统推荐Instruments,还有一些开源代码,可以根据需要自己开发定制。如果使用Unity3D开发的话,开发工具自带有一个Profiler工具,可用于查看客户端性能消耗的情况,对开发自测和定位性能问题十分有帮助。而对于测试同学来说,最好还是在真实设备上,用正式的版本进行测试,采样到的性能数据才更为真实准确。
除了上述提到的诸如CPU、内存这些系统指标外,客户端还可以继续收集更多的性能数据,来辅助进行深入测试。例如对客户端性能影响最大的有地图的大小和角色数量这两个因素,而地图和角色在画面绘制的时候都需要做相同的图层处理,所以为了方便数值统一,可以让客户端同学帮忙计算出当前显示画面中地图和角色的总面数和顶点数,然后通过面数和顶点数与CPU、内存等性能数据进行关联测试,可以用来约束美术资源的制作规范,如地图和角色最好不超过多少面数,可视范围内不超过多少个角色等。
CPU/GPU
客户端性能测试中,CPU数据主要看是否长期占用较高,比如99%的情况下不应高于某个值,如果出现较大的峰值,且频繁出现,就需要定位下什么情况下出现的。CPU占用率与耗电量也是直接关联的,占用率高,耗电量就快,发热也就越多。
内存/显存
内存其实是比CPU更容易判断客户端性能的一项指标。一般来说,整体内存占用越小越好,而且运行过程中内存不能突然间有较大的增长,否则内存占用太大或增长太快,都会引发客户端闪退。尤其是在iOS系统上,如果App运行消耗的内存超出了系统一开始分配的,系统会直接将进程杀死。还需要注意的是,客户端长时间运行(超过30分钟)时,内存一般有涨有跌,如果一直增长,几乎没有降低过,就要怀疑是否存在内存泄漏,需要找开发进一步排查。如果内存泄漏较为严重时,客户端运行也会变得越来越卡,FPS明显降低。比如我曾经测试过的《剑荡八荒》,在做跑环任务时,每次完成任务,前一次加载的数据和资源都没有清理,一直做下去就会越来越卡。
流量
流量是手游比较特别的一个数据指标,因为手游不可能一直在wifi下玩,在使用GPRS移动网络,流量如果消耗过大的话,也会影响玩家继续玩下去的心情,毕竟流量就是金钱。流量的消耗目前没有明确的标准,因为不同的游戏类型,流量消耗相差很大。越是前后端交互频繁,需要实时同步数据的,流量消耗就越大,例如MMORPG,每10分钟可能消耗几MB;而一些回合制或单机类的,基本会同步奖励和胜负结果,每10分钟流量消耗可能只有几十KB。
之所以不去测试耗电量和发热这些数据,因为耗电量其实就主要跟CPU消耗有关,另外屏幕亮度也会较大影响耗电量的数据,所以意义不大。而发热除了与耗电量有关,还与手机硬件的散热设计有关,这跟App没有太直接的关系。
常用的性能测试方案
刚开始进行客户端性能测试的时候,不会一上来就设计很具有针对性的测试用例,除非开发直接告诉了你具体要测试的模块。所以首先是先从模拟玩家体验过程进行初步的探索的,然后再根据测试过程中发现的问题,看是否有需要排除其他因素,进行更深入的测试。以下介绍两种最简单快速的客户端性能测试方法,对于新人来说十分容易上手,同时便于熟悉测试工具的使用。
情景一:新手前30分钟
一款游戏,玩家看到的第一眼会决定愿不愿意尝试,玩15分钟会决定要不要继续下去,而玩30分钟基本决定了以后还会不会打开这个游戏。
基于上述的理由,采集游戏前30分钟的性能数据,评估性能体验的流畅性就显得比较重要。日常工作中,渠道代理的游戏,在准备上线前,也会至少进行一次这样的性能测试,如果有明显的性能问题会要求优化后再上线。因为游戏在进行推广时,会通过广告宣传,购买流量等手段争取尽可能多的玩家体验这个游戏,如果前期体验流畅度很差的话,很难留住玩家。导量的转化率太低的话,平均每个玩家的获取成本就显得很高了,游戏的利润也就随之降低。
新手前30分钟的测试步骤十分简单,不需要专门的设计,只需要从客户端启动开始,按照任务引导一直跑下去即可,过程中注意记录遇到的问题。当然这个时间也并不限于30分钟,可以根据实际游戏历程进行调整。容易出现问题的地方有:
- 客户端第一次安装启动需要的时间太长;
- 触发剧情或过场动画时,容易卡顿;
- 进入主城或野外大地图,场景加载的时间较长;
- 角色打怪战斗的时候可能出现的卡顿;
- 如果客户端做了边玩边下载的大小分包,可能会影响游戏流畅度;
情景二:多人同屏
现在很多游戏都喜欢强调多人玩法,比如势力战、国战等。一方面人多显得游戏火爆,可以吸引更多的人,另一方面多人玩法容易刺激玩家之间的竞争攀比,拉动消费,提高游戏收益。
可以说,只要是强调多人交互的游戏,都需要进行一下多人同屏的测试。除了验证多人同屏时,客户端的性能是否流畅,同时也是验证多人同屏时,客户端的屏蔽或优先显示的规则。在内网环境下,没有大量玩家角色同时在线交互,所以就需要程序支持,实现机器人工具,模拟出大量角色在线。要实现的机器人的行为大致包括:登录创角,寻路移动,释放技能等。如果游戏角色还可以通过装备或皮肤改变外观,那么最好也能让机器人角色拥有不同的外观,增加角色之间外观的差异性,这样更接近真实用户。
首先,要先确认多人同屏显示的规则,如显示的人数上限是多少,达到上限后是否是屏蔽角色模型及技能特效,但仍显示角色的名字等。其次,多人同屏的测试需要设计对照组数据,例如同屏人数上限设定为50人,那么就需要分别测试同屏有0个/25个/50个/100个机器人时,不同的客户端性能数据。最后,每组测试数据持续至少需要记录15分钟,且分为三段情景,具体的步骤参照如下:
- 选择一个屏幕范围内两三个NPC的地方,登录指定数量的机器人角色,尽量确保其在一屏内活动和释放技能。——布置多人同屏的环境。
- 确认机器人状态符合预期的情景设计后,自身客户端角色走出机器人可见范围外,然后重启客户端。——重启的目的是为了清除之前观察机器人时的影响。
- 重新启动客户端同时开始收集客户端性能数据,保持角色不动,先收集5分钟数据。——先收集静态数据是为了与之后同屏显示机器人数据进行对比。
走向机器人角色所在的区域,尽量移动到能将所有机器人角色显示在同一个屏幕内的位置,持续收集至少5分钟的性能数据。同时可以观察记录以下几点内容: 4.1. 通过截图,数下同屏显示的角色数量是否正确,是否优先显示距离更近的角色。 4.2. 观察不同的技能特效显示是否完全,是否有哪些技能特效播放时会带来明显的卡顿。 4.3. 观察同屏显示人数达到上限后,自身角色和NPC是否会优先显示。 4.4. 自身角色移动或释放技能时,是否有明显的延迟或卡顿。
最后自身角色走出机器人区域,客户端不再显示任何机器人角色,持续收集5分钟数据。——这个数据可以与刚开始未进入机器人区域之前的数据进行对比,看是否有未释放的数据或资源引起的内存泄漏。
- 如果客户端同屏人数显示上限可以修改,那么还可以尝试下“周围多有个机器人角色,但同屏显示人数设为0”与“同屏没有机器人角色”的性能数据对比,看下被屏蔽角色对提高客户端性能有多少帮助。
目前最常用的手游引擎是Unity,一般支持同屏的数量大概是50个,再多也可以显示的出来,但可能就很容易卡,较难优化。但也有一些技术手段可以改善这个问题,但可能会降低一些表现效果和游戏体验,比如角色同模,特效分级等。
- 角色同模:就是所有角色都用同一个模型,然后复制多份,看起来都差不多,可能只有名字不同。
- 特效分级:将特效根据大小及重要程度划分为高中低档次,客户端性能如果足够高,可以所有特效都进行播放,反之性能很低,就只显示最低档的特效,优先保证客户端的流畅性。
注意事项
进行客户端性能测试时,有一些细节操作会对测试结果产生影响,需要稍加注意:
- 测试前沟通测试目标和方法,确认待测的资源和版本环境。
- 每次测试前,关闭其他无关的程序,然后先试运行一下工具,收集一些数据,确认工具的运行和采集的数据都正确可用。
- 测试过程中,发现有问题的地方记录下出现的现象和时间,方便与性能数据进行对照,注意进行截图。
- 多次测试时,每次测试前都要记得重启客户端,排除前一次的影响。
- 收集完数据后,简单看下数据是否有明显的问题,不要急于破坏环境进行下一个测试。
- 在不同的设备上运行客户端,收集到的性能数据会有较大的差异,所以尽量保持在同一台设备上进行客户端性能测试。
- 测试小型游戏(如休闲、棋牌)的性能数据时,不能忽视性能数据收集工具本身对性能数据的影响。
- 最终整理和分析测试结果时,不要妄下结论,优先描述问题现象,展示数据图表。有疑问点或建议,可提出后与开发人员进行核对确认,跟进结果。
性能结果分析
客户端性能测试完成后,除了发现的问题,主要的收获就是一堆性能数据了,那么这些性能数据应该如何去理解和分析呢?首先,你要将其整理成类似下面的图表,一般用excel就可以,也可以写一个工具自动绘图。
先看下整体数据的峰值和中间值。可以与同类型产品进行对比,一般来说相差不超过30%都是可以接受的,高出50%以上就需要查明原因,进行优化。例如内存占用太高,容易引发闪退;CPU占用太高会导致设备发热,增加耗电,一般客户端CPU占用都在40%以下。本图中计算的是均值,而不是中间值,性能测试最好看中间值,或者Top99值(就是99%的数据都小于某个值),因为这个图的问题比较有代表性,有机会会再改为中间值。
看数据曲线的波动情况。如果曲线剧烈抖动,说明运行不太稳定。例如内存曲线抖动幅度较大(超过50M),就可能需要优化内存管理,比如做一些缓存,预加载,或使用内存池等。如果是CPU抖动过大(相差15%),游戏中可能就会表现出卡顿。本图中的内存曲线有很明显的波动,但后期排查发现是工具本身的问题,并不是客户端真实的数据。
观察内存曲线的走势。像本图中,内存曲线一路增长,几乎没有降低过,疑似存在内存泄漏。之后与开发人员核对,也证实在做任务的时候,已完成的任务数据和加载的资源没有释放,内存开销才会越来越大。CPU一般很少出现持续的增长,本图中的CPU曲线最后出现下跌,是因为结束性能数据收集的时候,将App进程切换到后台,程序处于休眠状态,CPU消耗就直接降低了。
上述三点只是最简单通用的分析方法。结合自身的测试方案,对比测试目标和结果会有更加有针对性的结论。而且可以横向对比其他产品或自身的历史数据,来判断性能变化。重点是要将测试数据和初步的分析点同步给开发同学进行核对,对方可以帮助确认测试结果的合理性,同时给出更多数据有效性的建议。
一些客户端性能优化方案
以往的工作过程中,记录一些客户端性能优化的方案点,不太成体系,只能作为参考,程序基本都是基于Unity3D开发的。
策划能做的事
- NPC和怪尽量分散摆放,避免玩家集中聚集在同一个地方。
- 视野范围在合理的情况下尽可能地小,尤其是可见范围内的怪、NPC、玩家数量不要显示太多。
- 怪物或NPC的尸体存在时间尽可能地短。
- 少用一些粒子光特效。
- 避免开怪的瞬间数据计算或资源加载,比如boss一上来就放大招。
- 有限视野的场景,如室内,会比空旷的室外,客户端性能要好,尤其是平视角的3D游戏。
- 一个技能不要绑定多个特效,如需要,一定要分好主次关系,不要一下都播放出来,最好阶梯式显示。
- 建议不要在同一个场景里大量使用含有淡入淡出的NPC。
美术能做的事
- 控制贴图的大小,尽量不要超过1024X1024。
- 尽量使用2的n次幂的贴图,否则FfxDriver会有2分贴图。
- 尽量使用压缩格式减少贴图大小。
- 尽量重用贴图,例如贴图合并,或同一个贴图让程序更换材质来达到重复使用。
- 去除多余的alpha通道。
- 控制单个模型的面数,控制同一个画面中多面数的模型数量。
- 不同设备使用不同的模型面数。
- 控制模型的骨骼数。
- 一个网格不要超过3个material。
- 动画要压缩。
- 音频尽量采用压缩mp3和wav格式。
- 新资源尽早做性能测试。
- 制作场景时,使用的资源种类越少越好,避免不合理的消耗。
- 场景地图减少高低地形,尤其是有上下坡的多层结构。
- 多做室内这种封闭的环境,避免空旷无垠的场景。
- 制作粒子特效时,特效面积越大的,粒子数要减少;粒子数多的,粒子特效面积要减少。
程序能做的事
- 合并顶点数。
- 更换计算效率更高的数学库。
- 将骨骼计算从GPU移到CPU上。
- 对常用资源做预加载或尽量缓存,避免频繁的反复读取,例如缓存最近进入过的3个场景。
- 采用对象池,而不是大量频繁地创建和销毁相同的对象,减少Instantiate和Destory,例如打怪时怪的死亡和重生。
- 采用视野剪裁,关闭玩家视野外不必要的特效和渲染。
- 支持在不同档次的设备上分别加载不同层次的特效包。
- 减少每帧的drawcall,具体做法需要结合业务逻辑进行筛选。
- 做纹理压缩,不同设备采用不同的纹理贴图,分层显示。
- 使用Resource.Load方法,在需要的时候再读取资源。
- 资源使用完成后,尽快用Resource.UnloadAssert和UnloadUnusedAsset卸载掉。
- 灵活运用AssetBundle的Load和Unload方法动态加载资源,避免主要场景内的初始化内存占用过高;但此方法的实现起来较难。
- 采用www加载了AssetBundle后,要用www.Dispose及时释放。
- 在关卡内谨慎使用DontDestroyOnLoad, 被标注的资源会常驻内存。
- 尽量避免代码中的任何字符串连接,因为这会给GC带来太多垃圾。
- 用简单的for循环代替foreach循环。
- 尽量不使用LINQ命令,因为它们一般会分配中间缓存器,而这很容易生成垃圾内存。
- 将引用本地缓存到元件中会减少每次在一个游戏对象中使用 “GetComponent” 获取一个元件引用的需求。
- 减少角色控制器移动命令的调用。移动角色控制器会同步发生,每次调用都会耗损较大的性能。
- 最小化碰撞检测请求(例如ray casts和sphere checks),尽量从每次检查中获得更多信息.
- AI逻辑通常会生成大量物理查询,建议让AI更新循环设置低于图像更新循环,以减少CPU负荷。
- 要尽量减少Unity回调函数,哪怕是空函数也不要留着(例如空的Update、FixedUpdate函数)。
- 尽量少使用FindObjectsOfType函数,这个函数非常慢,尽量少用且一定不要在Update里调用。
- 千万一定要控制mono堆内存的大小。
上述部分优化方案参考自:https://juejin.im/entry/5774af485bbb50005925a3b1