iOS 微信小游戏内存与性能优化指南
前言
由于微信小游戏架构是基于 C++ 渲染层模拟 Canvas 渲染功能的实现方案,导致在 iOS 端的性能表现一直差强人意。 同样的程序版本,与有 JIT 加速的 Android 比起来,相差甚远。
微信小游戏提供的高性能模式,通过转用微信内部的 Webkit 运行游戏的方式,使得在 iOS 上的小游戏,也能拥有 JIT 能力,大幅度提升运行性能。
从微信小游戏官方文档的水族馆测试中,我们可以看在,同样的场景,在 iPhone11 Pro Max 上,高性能模式下达到了 49 FPS,普通模式下却只有 13 FPS。
本文将从以下四个方面进行讲解:
- 高性能模式
- 运行时内存结构
- 内存问题诊断
- 内存优化技巧
因为内存问题在 Android 端表现良好,在 iOS 端反馈较多,所以本文仅对 iOS 端内存优化做介绍。
高性能模式
有关高性能模式的介绍以及使用方法请详细阅读微信小游戏开发文档:高性能模式(可长按文末二维码)。
开通方式
- 登录微信公众平台 -> 首页能力地图模块 -> 点击进入"生产提效包" -> 点击开通高性能模式。
- 开通成功后,通过配置 game.json 的 iOSHighPerformance 为 true 则可进入高性能模式。
通过去掉此开关可以正常回退到普通模式,以便两种模式对比。
注意问题
高性能模式下,游戏将拥有更好的渲染性能和表现,但是它对游戏的内存要求更加严格。
在高性能模式下,小游戏运行于浏览器内核环境,所以兼容性、内存消耗、稳定性等方面需要单独进行测试,不能复用普通模式的测试结果。
在 iOS 设备中,iphone 6s/7/8 等 2G RAM 机型的内存限制为 1G,iphone 7P/8P/iPhoneX/XR/XSAMX/11 等 3G RAM 机型的内存限制为 1.4G,一旦应用程序的内存占用超过这个阀值,就会被系统杀掉进程。因此开发者务必保证内存峰值不超过该数值。
如果游戏没做好内存优化,不建议开启高性能模式,否则在 iOS 低端机容易出现内存异常退出的情况,如有内存问题,可参考本文的内存优化技巧,充分优化内存。
运行时内存结构
从上图中可以看到,运行时内存一共由 6 个部分组成。
JavaScript Heap
在高性能模式下,小游戏运行于浏览器内核环境,因此 JavaScript Heap 包含游戏逻辑代码内存。 通常我们可以打包 web-mobile 端,使用 Mac 平台的 Safari 浏览器的调试工具来远程调试手机 safari 的内存情况。 需要注意的是 JavaScript Heap 通常无法看出具体物理内存使用。
WASM 资源
为了提高 JS 模块的执行性能,比如物理引擎的计算,我们会将一些 C++ 代码直接编译成 WASM 代码片段来达到优化性能的需求。 比如 Cocos Creator 部分引用的第三方物理库就是 WASM 版本。
基础库和 Canvas
基础库可以理解为微信小游戏的黑盒环境暴露的 API 封装,可以防止将浏览器内核环境 API 暴露给开发者,实际测试基础库内存占用在 70M 左右。小游戏环境第一个创建的 Canvas 是主 Canvas,也是唯一可以将渲染表面同步到主界面的 Canvas,即呈现我们游戏的渲染表现。Canvas 的内存占用跟 Canvas 的宽高大小成正比。
音频文件
音频文件内存是指加载到内存的音频实例。
GPU 资源
比如顶点数据缓存,索引数据缓存,纹理缓存和渲染表面缓存等等。
内存问题诊断
下面给大家介绍一些常用的 iOS 内存诊断工具,它们可以辅助我们快速定位内存问题,找出解决办法。
常用 iOS 设备内存查看工具
- Xcode 自带的 Instrument 分析工具
- Perfdog 工具
- 微信开发者工具
注意:iOS 端小游戏的进程名称在不同模式下有区别。
- 高性能模式:含有 WebContent
- 普通模式:含有 WeChat
XCode Instruments
XCode Instruments 是 XCode 自带的应用程序运行时分析工具,它同样适用于微信小游戏进程。
使用 Activity Monitor,选择对应的设备 all processes 捕捉,等进程列表刷新后,输入 webkit 进行过滤,即可看到所有进程的 CPU 与内存情况.
Perfdog
Perfdog(性能狗)是一个 iOS/Android/PC/ 主机平台的性能测试和分析工具,具体使用方式请参考:
选择对应的设置-进程名,即可看到相关性能数据。
可以参考【开发阶段内存调优: 把一切都控制在最开始 | 微信开放文档】: https://developers.weixin.qq.com/minigame/dev/guide/performance/perf-action-memory-dev-profile.html。
微信开发者工具
微信开发者工具主要使用它的调试器来跟踪内存数据,操作流程如下:
- 进入调试开发者工具界面
- 将 【Memory】勾选,然后刷新游戏
- 点击下图左上角的小圆圈按钮开始录制
- 结束录制后,就会显示下图界面
我们主要关注两个波形图,一个是 Main 波形图,用于查看逻辑帧调用栈,一个是 JS Heap 的峰值曲线,用于观察内存增长变化。如果我们观察到某个时刻 JS Heap 值增加,然后我们就可以查看逻辑帧调用栈大概确认数据来源。
通过上述步骤可以分析出游戏的 JS 内存分布,然后当我们要确定某块 JS 内存的来源与释放情况,就需要用到下面的内存泄漏检测工具 - 实时内存诊断。
具体操作步骤如下:
- 点击左上角的小圆圈按钮,会进入下面的录制按钮,柱状图的出现表示某个内存块的创建,消失标识内存块被释放。左上角的垃圾桶按钮是主动触发 JS 引擎的 GC 的按钮,点击后可以加快内存回收速度。
- 再次点击左上角的红色小圆圈按钮结束录制,这时候我们可以选中蓝色区域,然后会显示该内存块包含的对象,这些是在内存中未被释放的资源,选中某个对象后,可以在 Retainers 界面看到对象的内存引用关系。到这里你可以根据代码层的逻辑关系来推理内存对象是否应该被释放,从而确认是否内存泄露。
内存优化技巧
常见微信小游戏项目的内存由下面几个部分组成:
- 小游戏基础库
- 引擎脚本内存
- 业务脚本内存
- 音频内存
- 字体内存
- 图片内存
- Canvas 内存
知道了内存组成部分后,我们就可以针对不同的部分做一些优化。
以 iOS 高性能模式为例,常用的内存优化技巧如下:
小游戏通常基础库的内存 ~= 70M,常驻内存,不可优化。
引擎内存占用加载是确定的,由于引擎加载会初始化渲染器,所以通常主 Canvas 内存占用也在这个时候确定,这块内存占用可以通过配置渲染分辨率的倍数来优化。运行时根据引擎模块需要,会动态增加一些缓存内存,开发者可以根据功能需要通过编辑器项目设置里面的功能裁剪来减少引擎内存占用。
脚本内存包含引擎和业务代码、配置表数据, 根据游戏的开发体量,业务代码和配置表数据内存会有几百 M 的大小,只能用户自己做优化。
单个双通道的音频实例可能在 20M 左右,音频播放完后做释放会减少这块内存损耗,也可以精简成单通道音频减少内存。
在国内,一般使用的是中文字体,加载后内存占用至少大于 10M,所以尽量使用系统字,使用应用内部的共享资源。如果开发条件允许的情况下,可以使用 Bitmap 字体和 SDF 字体渲染。
图片内存是常用资源,根据加载需要,可以选择填充纹理后释放,或者缓存于内存中以便下次重新填充纹理。在iOS端上建议使用 astc 压缩纹理格式,同时禁用动态合批,这样可以释放 image 资源内存。压缩纹理本身也比 png 的内存占用小超过50%,但是 astc 的文件大小会比 png 大,所以会增加包体大小。通常为了减少首包大小,尽量将图片资源放到小游戏分包或者远程分包。
TTF 字体文本渲染时会创建 Canvas 对象,Canvas 对象使用完会被回收到缓存池中,文本渲染的字号越多, 缓存池就越大,目前引擎没有提供回收机制,必要时可以修改引擎来释放 Canvas 缓存池。如果游戏运行内存占用比较高,可以使用 Bitmap 字体替代 TTF 字体。
还有其他的 JS 内存对象,比如 JSON 文件的释放,根据引擎提供的能力按需释放。
本文由 Cocos 引擎官方技术支持团队 提供,已同步到引擎官方开源仓库中:微信小游戏内存优化指南(IOS 端),欢迎大家 Star、Fork。