Egret 性能优化
引言
之前完成了项目的逻辑内容开发之后,开始着手解决性能方面的问题,比较严重的就是发热和耗电。而且出现玩的时间越长越卡的问题,想必是有内存泄露了。接下来就是优化的主要思路:
-
首先,降低常驻场景的 drawcall ,即游戏主场景及主界面 UI ;
-
其次,排查内存泄露;
-
最后,假如资源回收机制。
减低 Drawcall
设置 index.html 中 egret 的启动参数:data-show-fps="true"
即可在屏幕左上角实时看到当前的绘制批次和 drawcall 信息。
1. 优化图集
我们项目使用 egret + fairygui 来开发的,有相关优化经验的应该知道,通过打图集的方式可以达到同一图集图片可以通过同一个批次绘制出来,然而 Egret 并不能像 Unity 那样通过判断 mesh 是否相同来实现卡节点合批,例如:两个图片在同一个图集中,但在两个图片中间插入了一个文本组件,这样合批就被打断。
2. 文本合批
其次,基本文本使用的字体相同,但是在 egret 中,每个文本都需要单独占用一个绘制批次。要实现文字合批,只能通过自定义字体,使用图片字体的方式代替原生的字体,但这样也存在局限性,即只能针对内容变化较小的情况,例如一些标题和数值的展示情况下。
3. 动静分离
动态内容与静态内容分离,这也是我们项目优化中效果最为明显的一个部分,因为我们主场景是一个由多个格子组成的棋盘,而格子会有多个样式,可分为三层。
-
假如把三层放在同一节点下,则每个格子需要3次 drawcall ,那么当格子越多, drawcall 是成倍上升的,而棋盘格子至少也有 20 个,则原本的节点规划方案需要 20 x 3 = 60 次 drawcall ;
-
而修改后,将每个格子的三层,分别是底图层、图标层和行走高亮层。
内存泄露
主要借助 Google Chrome 浏览器调试面板中的 Heap Profiling
工具来进行排查,实现方式是通过记录当前的堆内存(heap)快照,并生成对象的描述文件,给出当时 JS 运行所用到的所有对象、对象占用的内存大小和引用层级关系等。
在调试面板的 Memory 页签便是 Heap Profiling
工具,下面是具体的使用操作:
-
如何打快照:
选中左侧菜单中的
Profiles
选项,在Select profiling type
中选择第一项Take heap snapshot
,点击Take snapshot
点击即可开始打快照。需要注意的是,假如在控制台中打印对象的话,也会把对象引用住,所以打快照前最好也清空一下控制台中的打印日志。此外,每次打快照之前都需要点击一下
Collect garbage
按钮,保证能被回收的资源都被回收了,这样后面对比快照查询泄露才准确。 -
对比快照:
当打出首个快照之后,我们会进行一些游戏内的操作,例如:打开一个界面或切换一下场景,然后再关闭界面或切回原来的场景。一番操作之后,点击
Collect garbage
按钮然后打出第二个快照。现在在HEAP SNAPSHOT
下便有两个快照:接下来是对比快照的步骤:
-
选中快照列表中需要进行对比的一个快照;
-
右侧操作类型选择
Comparison
(对比); -
再选择要进行对比的另一个快照。
-
-
定位泄露原因
在对比结果展示表中有几项数据:
-
#New
: -
#Deleted
: -
#Delat
: -
#Alloc. Size
: -
#Freed Size
: -
#Size Delta
:
对比技巧:
-
可以直接在对比数据上方的
Class filter
中输入打开过的 UI 的类名; -
假如存在,则表示 UI 关闭后仍被引用住没有被回收,展开数据可以看到引用链;
引起泄露的原因通常是对象被全局的变量引用住导致 gc 时没办法自动释放掉,排查引用链时其实可以定位到具体引用住此UI 的对象和引用的代码所在的位置,下面是分析引用链的操作:
-
先选中对象并展开对象的引用链,在
Retainers/Object
中第一个对象通常就是引用此 UI 的对象:不难看出此 UI 是被 gEventCenter 这个对象引用住了。
-
接下来排查引用代码的位置,可以将鼠标直接悬停在引用对象上,然后展开对象的
fn
属性:直接点击
[[FunctionLocation]]
后面的脚本信息,即可直接打开引用对象的代码所在的位置。
-
-
小结:
在 JavaScript 中提及的内存泄露,其实并非正真意义上的泄露,通常只是对象被引用住无法无法被回收而导致内存累积。这里需要注意的就是全局引用住的对象,除非是要重复使用,否则会导致对象无法被内存自动回收,从而导致占用内存一直累加。
资源回收
参考了论坛中的一篇分享,大致思路是通过资源的 hashcode
值弱引用(不至于导致资源无法自动 gc )需要统计回收的资源,使用资源路径作为索引的 key ,并且给每个资源设置一个回收时间,定时检查资源回收时间,达到回收时间限制的直接使用 RES.destroyRes(key)
释放资源。
当然,主动调用资源回收能降低内存占用,但回收太过频繁反而会导致发烫更严重。
其他
除了以上几点,开发 H5 小游戏尽量避免使用 spine 动画,虽然可以压缩资源空间,但是性能比较差,之前在接入 QQ 玩一玩就发现 spine 动画播放时会导致严重掉帧,后来换成了 dragon bones 才改善了一点(DragonBones Pro 可以直接将 spine 动画导成龙骨动画,而且支持二进制数据格式)。尽量使用帧动画这样实时计算量小的动画形式。