首先介绍市场的大背景,现在很多工程师,无论男女,可能都在玩手游,刚刚过来的路上也发现很多人坐在位置上在玩一些小的手游,腾讯好像已经成为最赚钱的游戏公司,所以我分享手游的内容可能对大家还是有一定帮助的。
首先,从国内的大环境来看,手游经过前两三年的高速发展,现在可能是增速会逐渐放缓的一个阶段。我们看现在 top 10 的游戏,包括王者荣耀、穿越火线、阴阳师、倩女幽魂、大话西游这些系列,会发现手游的玩法越来越重度,主要是以竞技类和 MMO(就是 RPG)为主,这也验证了一个问题,游戏行业经过了前三年的高速发展之后,它的 IP 红利效应正在减弱,进入一个精细化运营的阶段。腾讯从 12 年到现在经历了三个阶段,每个阶段对运营的要求和对产品的把控都略有不同。
这是我在新浪微博上做的一个截图,会发现现在很多玩家对手机玩游戏的性能要求越来越高,可能跟三年前不一样,因为三年前游戏扩张没那么快,用户也没那么多,所以大家买手机可能更看重的是摄像头拍照或者是其他的一些功能。现在很多人可能为了一款游戏去换一部新的手机。这是另外一个截图,通过左右两个截图,我们发现玩家对游戏的性能是有要求的,对厂商来说,很多硬件厂商在发布手机的时候,都会绑定某一款比较著名的游戏,通过这个来吸引潜在的买家用户,现在和我们合作的厂商也有很多,包括 OPPO、VIVO 这些国内大厂,他们也是希望能够通过我们这边的平台,持续对游戏进行优化,让新手机发布的时候能够有更好的用户体验。
简单说一下我们腾讯内部手游版本的审核流程。主要还是关注性能,无论是在功能测试还是自动化测试,或者是在灰度发布的过程中,我们都会做一些基础数据的采集,数据存储,主要还是跟游戏本身相关的一些性能数据。在各种测试过程中,我们对数据进行分析,最后得出一个结论,它的性能是否达标,如果不达标,版本会被退回去,重新进行一些性能的优化和修复。
这里就不得不提王者荣耀,就是正在席卷一切的王者荣耀。无论是在地铁上、公交车上,还是在饭店都会看到三五成群的好友,大家在一起组队玩王者荣耀,王者荣耀的确是目前腾讯最火的游戏。在王者荣耀上线前后,我有幸参与了整个版本的发布和测试,包括性能优化的过程。我们可以看到优化前,图上是一个 FPS,其实游戏内部的 FPS 和传统 APP 的 FPS 可能略有不同。从游戏的角度来说,FPS 应该是越稳定越好,优化前,FPS 波动得非常厉害,优化之后,到了发布公测的时候,其 FPS 波形是非常稳定的。我记得在公测之前,王者荣耀做过的性能优化点几乎有上千个,一个大型手游的性能优化,不仅仅是一两个点那么简单,可能包含上百或上千个点,是每一点、每一点地死抠出来的,并不是解决了一个架构问题就能够优化很大一部分的。
六个圆圈主要包括 CPU、内存,drawcall,以及各个指标的占用率,字可能比较小,大家看图的颜色就好。优化前其实还是会有很多红色的部分,红色就是相当于亮红灯,它是不达标的。优化之后现阶段的一个版本,基本上每一项指标都是非常好的,所以王者荣耀这么火其实是有道理的,因为其对产品质量要求非常高,整个团队从上至下,对产品的玩法、性能的优化都有持之以恒的精神,用他们负责人的话来说,即使达到了公司的发布标准,他们也还会继续深挖优化点的。
下面先说第一部分:性能测试场景。优化前肯定要先发现性能的一些瓶颈,才能够做一些针对性的优化。
现在国内游戏的手游引擎分布大概是这样的,我们看到国内 Unity 引擎和 cocos 引擎各占到一半,剩下的可能是一些自研的,以网易为主,还有一些中小型团队也会用自研的引擎来做特性的开发。因为腾讯内部 90% 的游戏都是用 Unity 做的,所以这次分享的细节还是以 Unity 为主。
我们看到六个方块的图,上面其实就是我们平时在玩游戏的时候能够直接感受到的用户体验:卡顿、发热、耗电、延迟、瞬移、闪退。现在我们围绕这六个点,看看需要做什么数据的采集和哪些方向的测试。
当你发现你的 APP 和游戏卡得很厉害时,首先我们需要去观察 FPS、 CPU 和 GPU 这两个硬件的开销,还要看它的 drawcall 是不是比较多。如果你的应用发热很严重,那我们也同样要去观察的是 CPU 和 GPU。耗电也是一样的,要看 CPU 和 GPU。
如果延迟很严重,比如说你玩王者荣耀,或者玩阴阳师的时候,延迟可能是你的网络有一定的问题,也可能是游戏的服务器自身造成的,也有可能是因为网络的一些波动导致了一些数据包的延迟,当然也有可能是客户端的计算逻辑出了一些问题。瞬移其实跟延迟差不多,当你的网络出现问题的时候,客户端会做一些计算补偿,或是做一些容错的处理,会把你强制拉到和服务器同步的坐标状态。有的游戏,比如玩英雄联盟,或者玩王者荣耀,它就不是瞬移,它可能是一个角色平移过去,你会看到一个角色飘过去,这就是同步的状态。还有些游戏可能没有这样平移的过程,直接就把你人瞬移过去了。
闪退主要是看内存,因为我们知道现在国内安卓机非常多,安卓上面的内存管理其实并没有 iOS 那么好,很多厂商搭层的策略不一样,当你的 APP 内存到了一定峰值的时候,可能会强杀掉你的应用。对 CPU 而言,当你的 CPU 峰值、CPU 占用率持续维持在一个很高阶段的时候,iOS 手机也会把你的 APP 强杀掉。综合来说,如果有这些问题,它会导致用户流失,新进用户减少,产品的口碑下降,所以说用户体验是一个全方位的产品质量问题。
客户端性能测试常见的一些方法,首先是大量机器人同屏,这与 APP 压测差不多,APP 和游戏的区别在于,我们用微信、美团或者大众点评时,整个客户端只有我一个人在用,而玩游戏时,在屏幕上会有很多人,可能有十个、二十个甚至上百个人,所以说这对客户端是有一定压力的。
第二个是单人游戏,就是我们平时经常做的功能测试,一个人正常地玩。第三个是多人组队,王者荣耀可能是十个人,穿越火线可能需要十六个甚至更多,多人组队考验的是客户端的数据同步和图像处理的容错机制,还有自动化测试,这方面腾讯用的比较多,外面的中小团队可能用的还不是很多。
腾讯的自动化测试主要是用来做一些冒烟测试,或者做版本构建之后的一些最基础的功能验证。比如说像某个游戏,它有十个副本,或者是一百个副本,就是有一百个场景,那我们如何保证每个场景是可用的?可以通过自动化测试,每天 daily build 出来之后,用自动化测试把所有场景跑一遍,确保这些场景是没问题的。无论是哪种测试,最终是要看数据的,所以我们在测试过程中会抓取很多性能数据,也会在应用中打印出很多日志,用来做一些滞后的分析。
前面说到了自动化测试,在这里提一下腾讯用得比较多的 Unity 自动化测试框架,名为 GAutomator,它已经在 Git 上开源,大家可以去收。它主要是基于 Unity 引擎,并且去年 Unite 2016 大会帮我们推荐了一把,可以说是第一个基于 Unity 引擎的自动化框架。
我们看到上面的三点,其实是我总结下来的关于自动化测试的发展进化过程,最早可能是有一些盲点,我们只是在手机上没有顺序的胡乱去点,第二阶段是基于某引擎的控件,有可能是基于虚幻,有可能是基于安卓控件的一些点。第三个阶段,就是通过写脚本来实现更深度的自动化测试,这就是一个大致的框架。它其实用到了很多特性,首先是用到了 Unity 引擎的特性,第二用到了 UIAutomator 里面的一些功能,可能会在平时的 APP 测试里用的比较多,主要是用来获取一些通用的安卓标准控件,比如发朋友圈、QQ 登陆、微信登录、分享按钮,在游戏内部,还是通过 Unity 引擎来实现,因为现在很多游戏通过 Unity 来做,所以说它引擎内部的控件、按钮、图片其实是在内存中可以拿得到的,拿到之后可以进行模拟的操作,让自动化测试场景深度更深。否则的话,以前如果做盲点测试,它可能最多只是帮你实现了一个登陆,但核心场景进不去,或者核心场景就是站着不动,没法进行操作。所以我们深挖了一下,基于引擎在上面再封装一层,通过 python 脚本来控制你的游戏玩法。
在腾讯内部,Unity 游戏基本上都用了这一套测试框架做版本的冒烟测试和性能测试,当然更重要一点是,它可以帮你实现一些比较深度的、比较宽泛的兼容性测试,一次适配测试可能需要上千台手机,包括各种混在一起的品牌的手机,如果通过自动化测试,你可以让适配测试的场景跑得更深更全。
第二阶段是数据采集、分析、优化,我们能够直接感受到的一些现象,比如说卡顿、发热、闪退,这些现象后的本质可能就是左边这一排红色,主要是内存、流量、drawcall、FPS、CPU、GPU 和三角形的数量,这些东西我们都可以通过一些方法去获取,可以通过安卓的系统文件拿到,也可以通过 OpenGL 去拿,还可以从机器内部拿到。
说一下卡顿的原因,我们玩游戏,无论是玩电脑游戏还是玩手机游戏,经常遇到一卡一卡的情况,用户体验是非常不好的。如果我们看的电影是一卡一卡的,我们的眼睛也会受不了,简单来说,卡顿的原因是什么?无论是 APP 还是手机游戏,它卡顿的最终原因都是因为某一帧耗时过长,导致图像输出延迟。第一个会导致卡顿的原因是资源加载。资源是什么?资源就是游戏中看到的那些图片、动画、特效,我们肉眼能够看到的东西基本都属于资源,资源加载的时候肯定会卡一下,因为它属于本地文件的 IO 操作。
第二个是低效的逻辑函数,主要是开发人员在写代码的时候算法写得不好,或者调用了一些不应该调用的函数。第三个是主线程 IO,和资源加载比较相似,资源加载可能在主线程 IO 里面会做,除了 IO 资源加载之外,我们平时还会发现很多 APP 和游戏在发布的时候会在 logcat 中加一些日志,但是有些日志可能在测试阶段才会用到,它不小心发布到一个现场版本里面去,所以在用户那边会感受到很明显的一些性能开销和不好的用户体验。测试和开发时我们会看到这个 GC 词,GC 就是垃圾回收,而且 Java 和 Unity 在回收方面是比较相似的,每次 GC 的时候大概耗时几百毫秒,这样会让图像输出延迟,必然会卡一下。网络波动,就是我们在玩手机游戏时,突然从 wifi 到 4G 网络的情况,或者是从一个信号很强到没有信号的情况,波动之后,你会看到各种各样奇怪的现象,可能会瞬移,还可能会飘,会直接闪退等。
说一下 Unity 与 GC、内存相关的东西。它的内部是通过 mono 来实现 C#的虚拟机,mono 相当于托管的虚拟机,它有两个标准内存的内存值,首先是 mono 堆内存,第二个是 mono 已用内存。堆内存就是我们预先分配了一大块内存用来给你的游戏用,比如说你分配了 50 兆内存,但是你的 C#对象,你的变量可能只用到 10 兆或者 20 兆,这个 20 兆是 mono 已用内存。看右边的流程图,当我们需要内存分配的时候,我们就去申请,如果在 mono 堆内存里面有足够的空闲,它就直接给你用了,如果没有的话,它会先做一次 GC,肯定会卡一下,如果空闲的内存还是不够,它会去向操作系统做一些内存的申请。所以 mono 堆内存对于 Unity 游戏来说,是一个只增不减的东西,在开发过程中一定要控制堆内存的大小,不然的话只增不减,会导致总体 PSS 越来越大。
看两个图,第一个图上有两条线,一条绿色,一条蓝色。绿色就是预先分配的 mono 堆内存,如果它在持续平稳的状态下,说明这个内存管理是比较好的。我们看到蓝色的线是波浪形状,当它到了一个峰值之后会下来,说明这个游戏的内存管理做得还是不错的,能够把堆内存控制在一个水平线,没有持续上涨。再看这个游戏做得就不是很好,因为它的那条绿色线一直在往上涨,说明它可能会有内存泄露,导致了堆内存不断上涨。
GC 做的可能跟 Java 差不多,其实 GC 是一个普通的函数,它只是耗时比较长,唯一一个区别就是 GC 有时候会暂停相关的线程,那你的整个应用肯定会卡一下,第二步会遍历所有已经用到的内存对象,把那些不再使用的对象标记出来。第三步再把它释放。第四步,再把刚才停掉的线程重新启动。我们看这样一段伪代码,就比如当我们最后调用 GC 回收的时候,A 对象不会被回收,因为 A 被一个静态对象引住了,而 B 是一个局部变量,很快就会被回收掉。所以很多内存泄漏其实是因为对象被很多根节点或者被全局对象引住了,导致它应该被释放的时候没有被释放。现在我们看一个图,上面有很多红点就是 GC。GC 出现的次数非常多,所以导致其 FPS 很不稳定,波动非常大。这个 FPS 的波形图,相对来说比较稳定,因为整个过程中只出现四次 GC,所以我们的结论就是很多卡顿是 GC 造成的。
举一个案例,腾讯一个 MOBA 手游,在比较早期的版本中,我们会发现 5v5 的副本中平均 15 秒左右会产生一个 GC,那个版本性能比较差。然后我们再看产生 GC 原因是什么,首先是内存申请,当你内存申请的时候,并且可用内存很少的时候,它会做 GC,第二个就是会手动调用 GC。
我们在 Unity 游戏里面怎么去获取 GC 的调用情况呢?我们可以通过 mono_profiler_install_gc 这个函数去拿到 GC 的调用次数,因为 mono 是开源的,所以 mono 里面的很多函数我们可以拿来用,这里简单地列一些减少 GC 的优化方法。当时这个版本其实有很多优化方法,就是我们在右边看到的这样一些方法。地图上很多小兵的对象,它们使用对象池,不断去 new 出来,会导致很多内存的碎片,也会导致内存的上涨速度过快。
第二是 Lua 调用 C#的时候不要用 Object 去传递参数,如果在 Lua 这种脚本语言调用 C#时,用 Object 传递参数的话,会有一种反射的机制在里面。C#的反射里面包含了一个装箱和拆箱的操作,装箱就是我们通常说的 boxing,每一次装箱操作会产生大概 20 个字节的内存开销,所以按照普通游戏的标准:每秒 25 帧,每 1 帧有 20 多个字节,那 1 秒就有 400 多个字节的开销,随着游戏的时间不断增长,内存的开销就会上去。
第三优化网络数据收发,有一个小函数,每次 new 了一个对象接收服务器来的数据包,后来改为用 Cache 就好了。
第四是减少一部分 UI 的刷新频率,这个可能听起来比较奇怪,UI 是我们在游戏界面上看到的菜单和选项按钮,因为这些 UI 是固定的,并且变化非常小,几乎没有变化,所以很多一部分 UI 可以减少刷新频率,不用每帧刷新,可以 1 秒刷新一次或是 500 毫秒刷新一次。如果玩的是野外的一些内容可以减少开销,举个例子,玩王者荣耀的时候我们会走到红蓝 buff 那边,其实走过去之后才发现红蓝 buff 的出现,在很远的时候其实是看不到的,在视野外的一些东西我们可以不用把它渲染出来,甚至都不用进行计算,这样可以减少一些客户端对性能方面的一些开销。
需要添加一些析构用的抽象接口,因为开发过程不是一个人的事情,整个大项目开发可能要上百个人甚至更多,每个开发人员用的一些模块都会直接继承过来。这样一来,强调每个人自己产生的垃圾,自己去把它处理掉,所以说需要多一些析构接口。最后很重要的一点是,mono 内存泄漏,内存泄露导致了内存上涨,然后才导致不断申请内存,最终会产生 GC。
Mono 内存泄漏,用一句话来说,就是当一个内存对象应该被回收的时候,但是没有被回收,导致我们通常所说的内存泄漏。因为 C#和 Java 这一层没有绝对的泄露,没有这样的指针概念,但是如果在 C++ 上的话,可能会出现一些指针在里面。
我们看到右边这个小图,有 ABCDEF 六个变量,当我们做一次 GC 回收的时候,我们会发现 E 和 F 其实很快就会被回收掉,左边的 ABCD 都不会被回收,为什么?因为 A 是一个静态变量,被 A 引住的下面三个都会长期驻留在内存里面,其实在这个时候 BCD 应该被回收,因为某些原因或者说因为开发人员的一些手误,它没有把引用关系去掉,所以导致一些多余的内存一直占用在游戏里面。这只是一个简单抽象图,我们在实际测试或在分析过程中,内存对象的引用关系可能多达十几层,我们去不断地抽丝剥茧,把那些内存泄露的问题找出来。
至于内存快照,它与 Java、C++ 层的快照概念是一样的,我们在两个时间点去拿一个内存的快照,然后进行对比。比如说我们进入了核心场景之后拿快照,出来之后再拿一个快照,按理说我们从核心场景回到大厅或者回到主界面的时候,核心场景里面的那些内存应该被释放掉。通过内存快照我们可以得到两点内容,一部分是新增的内存,第二部分是在第二个快照中仍然被保留下来的内存。我们现在拿到了两个快照之间新增的内存,包括内存的大小,这个对象的堆栈以及它的对象类型。这个图是两个快照之间保留的内存,需要强调一点,无论是新增对象还是保留对象,从第三方角度来说,我们是没法判断它是否合理的,所以说这个结果是给相关模块的开发人员自己去看,只有他自己知道这个变量是应该保留还是应该被回收。
再说一下 Unity 的资源,资源其实是我们可见的东西,我们可见的资源,比如说特效、动画、声音还有图片,都是游戏里面的资源,资源是存在 native 层里面的。资源主要是一些场景的加载,资源的释放,甚至还有一些资源可以被锁住。
为什么会锁住呢?我们可以给它加一个属性 DontDestroyOnLoad,就是说当我们把某些资源锁住的时候,其实我们希望它下一次进入这个场景的时候能够更快地看到画面,所以说在多个场景中通用的一些资源我们会把它锁住。这个当然有利有弊,锁的太多可能是没有必要的,锁的太少,可能在场景加载的时候速度会很慢。右边可以看到,Unity 游戏开发过程中比较常见的一些资源细分情况,首先是个贴图、GameObject、Mesh 网格、动画、音频、资源重复率、关卡间保留的资源,还有一些资源拷贝的情况。
资源怎么优化?当时有一个 FPS 游戏,就是射击游戏,有个版本在测试过程中发现低配机的内存峰值很严重,好像到了 350,可能接近 400。低配机的内存太大,很容易直接闪退被系统杀掉。所以我们需要去看看整个游戏中资源的开销情况。我们可以用 Resource.FindObjectsofTypeAll 去拿到它游戏里面所有的资源分配、回收以及资源的生命周期。当然这个函数可能会导致游戏卡顿,这个函数值在测试过程中会用到,现场版本应该是不会用到这个函数的。
最终我们定位有四点原因:
第一,新的版本中它的新贴图和新资源过大,并且没有做一些资源的分层处理,在低端机它有加载那些小的图片或小的动画。
第二,是有一个动画资源没有被释放,就比如刚刚进入游戏的时候,你会看到一小段动画,这段动画播放之后应该被迅速回收或者应该迅速释放掉,但这个动画没有被释放掉,就长期在游戏的生命周期里。
音频资源也是一样的,音频也是没有被释放。音频是什么呢?比如说我们玩游戏的时候会听到“欢迎来到王者荣耀”这样一段音频,当我们进入副本的时候会听到,这段音频播放完之后应该立即被回收,因为它在整个游戏过程中不会再出现第二次,所以我们把这些已经不再使用的音频资源回收。
第四,有 6 个小 BOSS 没有用的对象池,正常情况下我们都会用对象池,因为那个版本可能发布比较急,大地图上面新增了六个小 BOSS,小 BOSS 杀完之后,它会不断刷新出来,所以它每次都是申请内存 new 出来的,导致一些内存上涨过快。
优化的方法有以下几点,小块的资源预先加载,然后需要强化资源、生命周期的概念,就是很多资源,用完删掉就好了。新增的版本贴图不要太大,无论是贴图还是资源都不要太大,比如说贴图不要超过 1024,而且要符合 Unity 官方的标准 2 的 N 次幂。还有就是压缩压缩再压缩,无论是哪个资源,我们发布到现场版本的时候一定要进行压缩,不压缩的资源占用内存还是很大。高中低机型使用不同精度的资源,这也就是分层处理,APP 不会有这个情况,但是在游戏中,这是一个比较常见的开发策略,在高中低三档机型里面都会做一些资源的分层处理。最后是避免无用的资源拷贝。
下面说资源优化,首先是资源重复率,避免重复打包。关卡间的保留资源,会减少一些常驻内存的资源,并且会及时释放一些资源。资源拷贝的话,我们要注意资源拷贝的数量,避免拷贝一些无用的资源。资源的尺寸就是刚才说的需要压缩尺寸,然后分层。比如说这样一个贴图,贴图分成四段,让我们在不同的机型上会加载不同尺寸的图片。
还有一些其他卡顿的优化。举个例子,是一个国战类 RPG,当时发现地图上的 GC 很少,但是卡顿又很严重,所以就觉得很奇怪。然后我们去拿那些 C#层函数的调用堆栈,发现有三个问题,网络 IO 太大,资源加载过大,函数使用不当。当时发现用 foreach 语句(官方是不建议用 foreach 语句的)时,foreach 语句里面的迭代器会产生一些垃圾内存,我们用 for 去代替,进副本速度虽然不能慢,但是一次性加载的内存也不能太大。重复的资源需要独立成包,也就是说很多重复资源,我们独立成一个包,跟其他资源做一些引用就好了。现场版本不要用 FindObjectOfType 这个函数。在数据包方面,我们网络 IO 很大,当时对数据包做了一些合并和优化,最后减少了很多空的回调函数,Unity 官方也是推荐不要用一些空的回调函数。
对于性能优化,我们总结为以下四点:首先是资源的优化,其次是渲染层优化,然后是代码层优化,最后是游戏策略的优化。资源、渲染和代码层的优化其实是比较通用的,但是在策略上的优化可能就不一样,每个游戏的玩法、价格可能都不一样,所以这里仅供参考。
关于腾讯手游的发布标准,腾讯手游经历了 3 到 4 年的发展,我们有这样一套标准。iOS 手机和安卓手机都会有 123 档的机型标准,每一档标准对于性能的指标也不一样,比如说安卓一档机,它的内存指标是不要超过 550 兆,二档机的话是 450,三档机的话是 350,以此类推,在不同的机型上,我们对性能的开销、性能测试指标也是不一样的。
除了腾讯的标准之外,这个图也有 Unity 的标准, 基于 Unity 的引擎,Unity 官方会给出一个引擎的大致内部标准,即 CPU、单帧内存、三角形的面数、VBO 上传量,还有纹理、贴图、网格、动画,其实在 Unity 官网上是能够看到的,只不过它没有中文的表格。右边就是一些举例,动态、静态物品的一些尺寸和标准,尽量不要超过这些标准,因为现在游戏越做越大,不可避免会超一点,但是不要超太多就好。
在这里说一下性能测试的优缺点,优点就是它能够发现大部分场景的性能瓶颈,它可以确保主流机型的用户体验是很流畅的,而且用户投诉会降低,当你的性能优化做得很好的时候,用户留存和产品口碑也会提升。性能测试不足的地方就是我们测试环境的机型肯定是远远不足的。整个国内市场可能有 3000 款安卓机型,在测试环境可能就那么几十款或者上百款。还有我们无法百分之百去模拟用户的场景,也不可能百分之百去覆盖。多人游戏的玩法,可能我们覆盖面也不是很足的,因为每位玩家的操作跟我们自己的操作是完全不一样的。
这里会引出下一个解决方案,就是线上的性能监控和分析 APM。APP 应该也有 APM,关注的可能也是内存、CPU 这些指标。为什么要做手游的 APM 呢?这里会有一个比较经典的冰山图,我们在测试环境看到的问题其实永远是那么一小部分,线上运营的环境永远是深不可测的,是非常险恶的,线上的环境可能有两千多款机型,更多的场景,更多的操作,包括国内非常险恶、非常复杂的一些网络,还包括一些第三方 APP 的兼容问题,这些都是潜在的问题。
我们现在做的手游 APM 的产品功能大概是五点:云端控制、风险预警、数据分析、发现问题,最主要是能够实时监控,当你的游戏新版本发布的时候,你必须要对玩家的性能做一些实时监控,避免性能过差导致一些玩家的流失和投诉。
我们拿到的手游 APM 数据也比较多,现在我们拿到数据纬度大概是这样的。左边这些维度还是比较通用的,主要是内存、CPU、FPS、drawcall。右边 FPS 会更细分,会有些均值、方差、卡顿、抖动、分段的一些计算在里面,因为不同的项目关注点可能不一样,所以我们会尽可能做到大而全。跟 APP 的区别主要是 APP 这边更关注页面的加载,Activity 一些错误的机制,包括一些卡顿,在游戏里面几乎是没有的。
在线上运营环境中,卡顿的原因可能主要是这么几点。第一可能是游戏版本本身的问题,核心硬件问题。第二是服务器的问题,服务器逻辑可能处理不过来,导致一些延迟。第三就是我们比较常见的一些网络波动。
对于手游 APM,我们的选项维度也会比较多。我们是根据版本、时间、机型、场景、画质、平台做一些不同维度筛选、过滤和计算。首先我们能够连续观察到多个版本之间的性能走势变化,假如说它走势是平稳的,那说明情况还好,如果它的走势是一直在往下,就像这个图一样,说明最新的版本应该是有问题的,因为我们单独看一个版本可能看不出问题,但是我们连续看 N 个版本的走势,就会发现问题会越来越严重。然后在某个版本中我们能够把所有的场景做一个排序,我们会发现哪些场景的性能是非常差的,哪些场景是性能比较好的。第三点的话我们看机型,根据机型的排名能够决定下一步去做哪个机型的优化,我们可以找到一些厂商去做一些联合深度的优化。
低帧率,就是我们玩游戏的时候低帧率的分布情况。我们看到长的进度条,其实是每个人玩游戏的时间,我们通过一定算法把这个场景的大致的低帧率出现的一些点标出来,这样来会更加直观。我们知道在有些场景里面大概在什么时段,增速比较低的情况,这样一来,我们能够进一步缩小问题定位的一些范围。
提几个技术难点,第一个是性能无损,因为我们做一些数据采集,对游戏的性能绝对也应该做到无损的状态。APP 相对来说好一点。APP 的界面是静态的,我们的操作也不是很频繁,但游戏玩家的操作频率非常高,所以说我们首先要做到性能无损。第二个是要做到适配兼容,我们拿数据的时候要适配兼容到各种厂商的机型。第三点是个性化配置,因为不同的项目可能需要不同的参考。第四个是动态扩容,有些游戏的用户实际量非常大,所有的游戏加起来可能上亿。第五点是云端控制,可能就是突然发现在某些版本和某些场合下这个功能会出现一些异常情况,我们可以及时把它关掉。最后一点是高并发,这个跟 APP 是一样的。
首先我们说一下性能无损,数据采集线程只有一个,我们在客户端这边的常驻内存是 1 兆到 1.5 兆,当内存写满了之后,通过一个 IO 缓冲技术把它写到本地文件,然后在核心场景里面我们是没有流量的,就是我们退出核心场景之后才去把数据上报。
高并发的话,其实跟传统的软件开发是一样的,持续优化,主从分离,均衡负载,动态扩容,包括一些排队的上报,就是说从客户端上报给服务器的那些数据可以进行一个排队,因为服务器不可能无限地扩充,当服务器负载已经满了,客户端连接失败的时候,把文件会做在一个排队的序列里面,下一次等到服务器空闲的时候,再把它传上去。适配兼容的话这边有三点,主要是 OpenGL 版本的兼容,还有安卓版本的兼容,以及 SDK 的兼容,因为游戏里面会接入很多其他的 SDK 和公共组件。
对于云端控制我们会动态地去做一些功能的开和关,那这边支持的东西就会有有概率、品牌、IP 地址、游戏版本、芯片的架构等。
说一下性能上报的流程。首先从客户端启动的时候,我们会从 CDN 读一个配置,决定我们当前的功能是开还是关,然后会连到一个 TGW,通过 TConnd 这个均衡负载的东西,到了一个文件服务器和一个分析服务器这边,从文件服务器直接传到云,并且从云到 DB 的一个均衡负载的 TSpider 里面。
最后总结,性能测试是一个全链路状态,从自动化测试、灰度放量、数据监控、舆情监控,到版本修复,形成了这样一个闭环。从线上的数据我们能够验证玩家的反馈,也可以及时发现一些版本的问题。
最后做一个网来总结,从产品质量角度来说,我们是希望能够全覆盖的,这个网的上面一部分可能是线上运营阶段做一些监控和分析,下面这部分可能是在研发做一些深度测试。
评论