实战PerfDog优化小游戏性能

16 篇文章 5 订阅
13 篇文章 0 订阅

背景:
我们的引擎是Egret,使用的是原生的EUI,转微信小游戏;
工程第一版出来后使用PerfDog测试一波数据。结果发现很多问题,本文主要分两部分

第一部分主要介绍通过PerfDog发现问题,
第二部分主要介绍通过PerfDog的数据定位并解决问题。

PerfDog具体操作方法不再赘述,这里可以看文档PerfDog使用说明

第一部分————数据分析

本次的案例多见于游戏第一版时的情况,比较常见,所以拿出来做个分析。
这里强调一点。分析问题需要整体数据联动分析,单独看某单一信息是没是意义的

第一次测试数据

FPS感人(我们限帧60)
在TA-1041 诺基亚手机测试上发现FPS波动比较大,而且多为出现在战斗场景以及UI切换的场景,如下图。继续看内存数据
在这里插入图片描述
内存问题
在这里插入图片描述
这个内存有经验的同学应该一眼就可以看出来存在内存泄露,内存逐渐上升到最后就会被System kill掉,需要注意的是不仅PSS(Memory默认为PSS)要注意,VSS(VirtualMemory)同样也要注意

内存耗用
VSS - Virtual Set Size 虚拟耗用内存(包含共享库占用的内存)
RSS - Resident Set Size 实际使用物理内存(包含共享库占用的内存)
PSS - Proportional Set Size 实际使用的物理内存(比例分配共享库占用的内存)
USS - Unique Set Size 进程独自占用的物理内存(不包含共享库占用的内存)
一般来说内存占用大小有如下规律:VSS >= RSS >= PSS >= USS

这里再稍微介绍下安卓的LMK(Low memory killer),详细信息就不多赘述了。

1.Android系统 会定时执行一次检查,内存达到某个值后,就会杀死相应的进程,释放掉内存。
2.每个程序都会有一个oom_adj值,这个值越小,程序越重要,被杀的可能性越低
3.Low memory killer 主要是通过进程的oom_adj 来判定进程的重要程度。oom_adj的大小和进程的类型以及进程被调度的次序有关
4.阈值表可以通过/sys/module/lowmemorykiller/parameters/adj和/sys/module/lowmemorykiller/parameters/minfree进行配置

接来下查看CPU数据
Cpu勉强还过得去
【图一】
注意看图一的app CPUusage
在这里插入图片描述


有的同学可能发现 App的CPUusage比total cpuusage低很多(图一),是因为我选择测试的是微信app,小游戏 是作为子进程而存在的,所以后来选择PerfDog的子进程进行测试,得到的数据会更加的精准
深色表示正在运行的顶层进程
在这里插入图片描述
再次测试就是正常的数据啦(图二),于是我们切换到子线程开始进行第二次测试,这里提前放一张第二次的测试数据。
【图二】
在这里插入图片描述

第二次测试数据

FPS数据:
在这里插入图片描述
CPU数据:
在这里插入图片描述
内存数据:
在这里插入图片描述
此时我们在测试过程中发现内存不断上升,没有呈现一个正常的内存趋势,所以调整了一下测试策略

测试数据组成:
我们在测试过程中做了一些特殊操作:

1.战斗挂机          【为了判断是否是战斗过程中触发的内存泄露】
2.反复打开关闭UI    【为了判断UI创建与销毁是否存在内存泄露】
3.静止在某一UI页面  【为了与其他场景作区分】
4.息屏挂机          【为了判断是否是由图像资源引起的内存泄露还是代码资源引起的泄露】

在这里插入图片描述
GPU压力山大
在这里插入图片描述
我们通过FPS数据发现在游戏过程Jank十分严重,FPS波动过于剧烈,尤其是集中在UI开启或者关闭的时候,游戏来说,渲染画面,相对来说GPU可能出现瓶颈,逐对GPU进行查看,这个时候我们进行数据排查发现GPU的使用率也变得异常高,基本上已经爆表,很明显渲染的压力很大,而我们游戏UI打开时实际上战斗也会被渲染,这和我们游戏的设计有关,所以渲染的压力很大。

再来看看内存:
内存数据:
在这里插入图片描述
我们通过PerfDog的数据发现内存是呈现一直上升的状态,尤其是VSS(VirtualMemory),如脱缰的野马一发不可收拾,这样下去最终的结果就是被System Kill掉。
其实现在已经可以确定是发生了内存泄露,在72分钟的时间里内存从726M到了956M,而且还在不断上升;

现在综合两次测试数据得出结论

结论:

1.FPS波动过于剧烈,很不稳定,尤其是在uI创建与关闭时候;
2.存在内存泄露
3.其实还有一些其他小问题,不过优先解决这两个

第二部分————问题定位

内存泄露问题分析

有了PerfDog以上的数据,接下来我们就要开始定位排查问题啦,

项目局部架构:
在这里插入图片描述
1.我们的项目的基础架构是所有的基础功能都调用的同一份基础class(祖传代码),例如通信类等等;
2.我们发现内存在一直上升,无论是角色在什么环境下,甚至是在息屏的时候内存也在上升,那么我们其实可以大概率定位是项目内部的基础class内部出了问题;

接下来开始细细排查;

内存泄露排查

首先要先了解一些JS的内存管理机制

回收机制
JS中内存的分配和回收都是VM自动完成的,不需要像C/C++为每一个new/malloc操作去写配对的delete/free代码,JS引擎中对变量的存储主要是在栈内存,堆内存。内存泄漏的实质是一些对象出现意外而没有被回收,而是常驻内存。
GC原理
JavaScript虚拟机有一个特点,就是对象创建的开销远远大于对象计算的开销,并且对象创建会导致垃圾回收,而垃圾回收会导致游戏不定期卡顿。
在堆中查看无用的对象,把这些对象占用的内存空间进行回收。浏览器上的GC(Gabage Collection垃圾回收)实现,大多是采用可达性算法,关于可达性的对象,便是能与GC Roots构成连通图的对象。当一个对象到GC Roots没有任何引用链时,则会成为垃圾回收器的目标,系统会在合适的时候回收它所占的内存。

我这里使用的谷歌浏览器的Head Profiling,或者你也可以使用白鹭引擎的profiler:
使用很简单:

1.打开Google浏览器,打开要监控的网页,win下按F12弹出开发者工具
2.切换到Memory,选择堆类型,选中Take Heap SnapShot开始进行快照
3.右边的视图列出了heap里的对象列表,点击对象可以看到对象的引用层级关系
4.进入游戏后拍下快照,打开某个界面,关闭界面,拍下快照
5.将新的快照转换到Comparsion对比视图,进行内存对比分析
需要额外注意的是:
每次拍快照前,都会先自动执行一次GC,保证视图里的对象都是root可及的。GC的触发是依赖浏览器的,所以不能通过时时观察内存峰值而判断是否有内存泄漏。

我们可以每隔一段时间来拍一次快照(由于公司项目原因,我就不展示真实项目了,此处仅作为教学):

我们可以打开谷歌浏览器的内存分析工具后有三个选项,我们可以根据自己的调试方式交替使用;

1.Heap snapshot - 用以打印堆快照,堆快照文件显示页面的 javascript 对象和相关 DOM 节点之间的内存分配
2.Allocation instrumentation on timeline - 在时间轴上记录内存信息,随着时间变化记录内存信息。
3.Allocation sampling - 内存信息采样,使用采样的方法记录内存分配。此配置文件类型具有最小的性能开销,可用于长时间运行的操作。它提供了由 javascript 执行堆栈细分的良好近似值分配。

在这里插入图片描述
这里举例使用堆快照分析,
在这里插入图片描述
右侧查看详细信息
在这里插入图片描述
可见rect对象一直在增高,那么我们可以查看一下导致rect对象未被释放的原因:
在这里插入图片描述
是由于Rect对象中存在一个属性rect一直被引用导致内存无法释放,那么我们到代码对应的位置去找,就可以较快的定位原因;最终我们发现是因为在自定义的一个全局事件监听器中实例化了一个对象,但是这个对象的一些属性会持续被这个事件监听器所引用而不会被回收

当然为了更快的定位哪个函数,我们也可以使用
在这里插入图片描述
一般结果是这个样子
在这里插入图片描述

Overview的HEAP(堆)曲线图表示JS堆。
Call Stack通常来说,垂直方向并没有太大的意义,仅仅表示函数嵌套比较深而已,但是横向表示调用时间,如果调用时间太长,那么就需要优化优化了。录制结果的调用堆栈,横向表示时会出现带有更多详情的浮窗间,垂直方向表示调用栈,从上往下表示函数调用。滑动鼠标滚轮可以查看某段时间的调用栈信息。把鼠标放到Call Stacks调用栈的某个函数上面可以查看函数详细信息。这个一般是性能优化时关注,对于内存泄漏,主要用于帮助定位进行了什么操作。
Counter(计数器)窗格。在这里你可以看到内存使用情况(与Overview(概述)窗格中的HEAP(堆)曲线图相同),分别显示以下内容:JS heap(JS堆),documents(文档),DOM nodes(DOM节点),listeners(侦听器)和GPU memory(GPU内存)。勾选或取消勾选复选框可以将其从图表中显示或隐藏。

主要关注第三个的JS堆内存、节点数量、监听器数量。鼠标移到曲线上,可以在左下角显示具体数据。这些数据若有一个在持续上涨,没有下降趋势,都有可能是泄漏。
由于篇幅原因,这里不过多介绍这些工具的使用,网上有很多相关教程;

卡顿优化

我们通过PerfDog的数据发现GPU压力很大,游戏来说,渲染画面久一般是drawcall过多,或者每次draw的时间较长。
在这里插入图片描述
而我们的游戏在查看在drawcall后确定是由于游戏运行时drawcall过多,导致每帧的渲染耗时比较长,所以会呈现一种卡顿的现象;
关于查看drawcall等可以通过白鹭自身的FPS面板查看 白鹭debug文档
在优化前首先要了解egret在渲染的一帧里做了什么工作内容
在这里插入图片描述
细分的话又可以分成
在这里插入图片描述

每一帧的工作内容:

	1.执行一次EnterFrame,此时,引擎会执行游戏中的逻辑。并且抛出EnterFrame事件
	2.引擎会执行一个clear。将上一帧的画面全部擦除
	3.Egret内核会遍历游戏场景中的所有DisplayObject,并重新计算所有显示对象的transform
	4.所有的图像全部draw到画布

现在来优化一下:
首先要降低drawcall:

1把小图全都换成图集
2.实现文字合批,通过自定义字体,使用图片字体的方式代替原生的字体
3.动静分离,将需要变化的和不变的分别放在不同的层级下,比如背景层、图标层和动态变化层
4.动画尽量使用dragon bones帧动画而不是spine 动画
5.使用cacheAsBitmap,把矢量图在运行时以位图形式进行计算

降低帧事件的开销:

1.不要的DisplayObject,直接removeChild 而不是设置他的visible属性为false,否则在第三步还会参与计算
2.不在主循环里创建任何对象,游戏中的人物、怪物、技能特效统统做成对象池
3.不在EnterFrame事件中做过多的操作,非要用可以自定义一些事件

我们可以用以下的函数统计创建的gameobject的数量
在这里插入图片描述
它是显示了每一秒钟去拿一个hashCount跟上一个hashCount作对比,这个hashCount是由白鹭引擎内部 API,用于统计引擎对象的创建数量。如果游戏静止放置不动,理论上hashCount diff的结果应该是0,实际上要尽可能控制在120以下,如果超标,只需要在引擎的 HashObject 的构造函数这里添加一个断点,在运行时去检查调用堆栈就排查就可以了。

补充:
微信小程序公开课
原生微信小游戏优化指南

这里与本篇文章无关

喜欢二次元动漫的小伙伴可以微信扫一扫关注一下哟,点赞关注,鼓励一下博主呦
在这里插入图片描述

  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值