Cocos 微信小游戏内存优化指南(iOS端)

文章由 Cocos 技术支持团队提供,已同步到官方开源仓库中:微信小游戏内存优化指南(IOS 端),欢迎大家 StartFork

Enjoy!


1.高性能模式

有关高性能模式的介绍以及使用方法请详细阅读微信小游戏开发文档:高性能模式

  • 高性能模式下,游戏将拥有更好的渲染性能和表现,但是它对游戏的内存要求更加严格。因为内存问题在 iOS 端反馈较多,所以本文仅对 iOS 端内存优化做介绍。

  • 在 iOS 设备中, iphone 6s/7/8 等 2G RAM 机型的内存限制为 1G,iphone 7P/8P/iPhoneX/XR/XSAMX/11 等 3G RAM 机型的内存限制为 1.4G,一旦应用程序的内存占用超过这个阀值,就会被系统杀掉进程。因此开发者务必保证内存峰值不超过该数值。

  • 如果游戏没做好内存优化,不建议开启高性能模式,否则在 iOS 低端机容易出现内存异常退出的情况,如有内存问题,可参考本文的内存优化技巧,充分优化内存。

  • 开通高性能模式的方式为:登录微信公众平台 -> 首页能力地图模块 -> 点击进入"生产提效包" -> 点击开通高性能模式。 开通成功后,通过配置 game.json 的 iOSHighPerformance 为 true 则可进入高性能模式,通过去掉此开关可以正常回退到普通模式,以便两种模式对比。


2.运行时内存结构

  • JavaScript Heap

    在高性能模式下,小游戏运行于浏览器内核环境,因此 JavaScript Heap 包含游戏逻辑代码内存。 通常我们可以打包 web-mobile 端,使用 Mac 平台的 Safari 浏览器的调试工具来远程调试手机 safari 的内存情况。 需要注意的是 JavaScript Heap 通常无法看出具体物理内存使用。

  • WASM 资源

    为了提高 JS 模块的执行性能,比如物理引擎的计算,我们会将一些 C++ 代码直接编译成 WASM 代码片段来达到优化性能的需求。 比如 CocosCreator 部分引用的第三方物理库就是 WASM 版本。

  • 基础库和 Canvas

    基础库可以理解为微信小游戏的黑盒环境暴露的 API 封装,可以防止将浏览器内核环境 API 暴露给开发者,实际测试基础库内存占用在 70M 左右。小游戏环境第一个创建的 Canvas 是主 Canvas,也是唯一可以将渲染表面同步到主界面的 Canvas,即呈现我们游戏的渲染表现。Canvas 的内存占用跟 Canvas 的宽高大小成正比。

  • 音频文件

    音频文件内存是指加载到内存的音频实例

  • GPU 资源

    比如顶点数据缓存,索引数据缓存,纹理缓存和渲染表面缓存等等


3.内存分析

常用真机内存查看工具:

  • Xcode 自带的 Instrument 分析工具
  • Perfdog 工具

iOS端小游戏的进程名称:

  • 高性能模式:含有 WebContent
  • 普通模式:含有 WeChat

3.1 Instruments

  • 使用 Activity Monitor,选择对应的设备 all processes 捕捉,等进程列表刷新后,输入 webkit 进行过滤,即可看到所有进程的 CPU 与内存情况.

3.2 Perfdog

3.3 微信开发者工具

  • 主要使用开发者工具自带的调试器来跟踪内存数据,进入调试开发者工具界面,将 【Memory】勾选,然后刷新游戏,点击下图左上角的小圆圈按钮开始录制。结束录制后,就会显示下图界面。我们主要关注两个波形图,一个是 Main 波形图,用于查看逻辑帧调用栈,一个是 JS Heap 的峰值曲线,用于观察内存增长变化。如果我们观察到某个时刻 JS Heap 值增加,然后我们就可以查看逻辑帧调用栈大概确认数据来源。

  • 通过上述步骤可以分析出游戏的 JS 内存分布,然后当我们要确定某块 js 内存的来源与释放情况,就需要用到下面的内存泄漏检测工具 - 实时内存诊断。

  • 点击左上角的小圆圈按钮,会进入下面的录制按钮,柱状图的出现表示某个内存块的创建,消失标识内存块被释放。左上角的垃圾桶按钮是主动触发 JS引擎的 GC 的按钮,点击后可以加快内存回收速度。

  • 再次点击左上角的红色小圆圈按钮结束录制,这时候我们可以选中蓝色区域,然后会显示该内存块包含的对象,这些是在内存中未被释放的资源,选中某个对象后,可以在 Retainers 界面看到对象的内存引用关系。到这里你可以根据代码层的逻辑关系来推理内存对象是否应该被释放,从而确认是否内存泄露。

4. 内存优化技巧

常见项目的内存计算公式: 小游戏基础库 + 引擎脚本内存 + 业务脚本内存 + 音频内存 + 字体内存 + 图片内存 + Canvas 内存。

以 iOS 高性能模式为例,常用的内存优化技巧如下:

  • 小游戏通常基础库的内存 ~= 70M,常驻内存,不可优化。

  • 引擎内存占用加载是确定的,由于引擎加载会初始化渲染器,所以通常主 Canvas 内存占用也在这个时候确定,这块内存占用可以通过配置渲染分辨率的倍数来优化。运行时根据引擎模块需要,会动态增加一些缓存内存,开发者可以根据功能需要通过编辑器项目设置里面的功能裁剪来减少引擎内存占用。

  • 脚本内存包含引擎和业务代码、配置表数据, 根据游戏的开发体量,业务代码和配置表数据内存会有几百M的大小,只能用户自己做优化。

  • 单个双通道的音频实例可能在 20M 左右,音频播放完后做释放会减少这块内存损耗,也可以精简成单通道音频减少内存。

  • 在国内,一般使用的是中文字体,加载后内存占用至少大于 10M,所以尽量使用系统字,使用应用内部的共享资源。如果开发条件允许的情况下,可以使用 Bitmap 字体和 SDF 字体渲染。

  • 图片内存是常用资源,根据加载需要,可以选择填充纹理后释放,或者缓存于内存中以便下次重新填充纹理。在iOS端上建议使用 astc 压缩纹理格式,同时禁用动态合批,这样可以释放 image 资源内存。压缩纹理本身也比 png 的内存占用小超过50%,但是 astc 的文件大小会比 png 大,所以会增加包体大小。通常为了减少首包大小,尽量将图片资源放到小游戏分包或者远程分包。

  • TTF 字体文本渲染时会创建 Canvas 对象,Canvas 对象使用完会被回收到缓存池中,文本渲染的字号越多, 缓存池就越大,目前引擎没有提供回收机制,必要时可以修改引擎来释放 Canvas 缓存池。如果游戏运行内存占用比较高,可以使用 Bitmap 字体替代 TTF 字体。

  • 还有其他的 JS 内存对象,比如 JSON 文件的释放,根据引擎提供的能力按需释放。

5. 参考文章

微信小游戏的内存调优指南 - 知乎

高性能模式 | 微信开放文档

性能测试实践 | PerfDog助力微信小游戏/小程序性能调优 - 腾讯WeTest - 博客园

微信小游戏的内存调优指南 - 知乎

微信小游戏学习日记1_微信小游戏框架_阿升1990的博客-CSDN博客

minigame-unity-webgl-transform/OptimizationMemory.md at main · wechat-miniprogram/minigame-unity-webgl-transform · GitHub

51赞

赞,论坛需要1万篇这样的文章

4赞

这个渲染分辨率的倍数是什么意思呢

1赞


对应在引擎中的属性是 devicePixelRatio 。

我们会对引擎不同适配层的 screen-adapter.ts 脚本中的 devicePixelRatio 属性做修改,大部分情况下我们会使用尽可能低的 dpr,具体多少由项目组自己决定。

下面是小游戏适配层代码 screen-adapter.ts 的位置

1赞

感谢支持~ :smiling_face_with_three_hearts:

1赞

大赞一个 :kissing_smiling_eyes:

战术性Mark!!!

战略mark

iOS用astc非常合适,就是大小问题,位图转成astc后再zip就可以很小,但微信底层不支持自动unzip

1赞

目前引擎已经支持 Bundle 包的 zip 压缩支持,目前只支持小游戏平台。功能使用了微信小游戏 API 完成了自动 unzip,用户只管在 bundle 的压缩类型选择 zip 就行。到时候资源从远程资源服务器下载到包体内时,会自动解压缩,游戏会直接使用。
后续因为有缓存,所以就不用再下载了。
https://docs.cocos.com/creator/manual/zh/asset/bundle.html#压缩类型
https://developers.weixin.qq.com/minigame/dev/api/file/FileSystemManager.unzip.html#参数

但是好像有大小限制,我之前50m的包,好像就不允许下载解压,就放弃了

1赞

https://developers.weixin.qq.com/minigame/dev/guide/base-ability/subPackage/useSubPackage.html
https://developers.weixin.qq.com/minigame/dev/guide/base-ability/file-system.html

1赞

解压也不应该超过200m,很奇怪。不记得细节了,每个bundle包都没超过20m的,但是微信手机调试,下载bundle自动解压的时候就报错了

  • 脚本内存包含引擎和业务代码、配置表数据, 根据游戏的开发体量,业务代码和配置表数据内存会有几百M的大小,只能用户自己做优化。

整个业务逻辑代码game.js大小不超过10M,加载到内存会有几百M?
还有就是配置表数据加载到内存的大小具体是怎么算的

1赞

会这么说是因为我们真的遇到过项目这些东西运行之后占用了上百 M 内存。业务代码当然是会使用引擎 api 的,所以也把引擎启动之后,游戏业务场景的内存算进来了。

内存这个没法准确计算,因为会有一部分 JS 引擎的优化编译缓存。内存的计算我们之前使用过一个插件: js-size 做初略的计算

image
普通模式下的GFX Buffer Mem
image
高性能模型下的GFX Buffer Mem
在高性能模式下,GFX Buffer值会很高,游戏玩久了,可能去到几百M,清理纹理对象时只能释放一点点,即时切换场景,资源全部释放,GFX Buffer的值也不会怎么下降。
在普通模式下,GFX Buffer的值不怎么高,通常维持在几十M。
请问是什么原因吗?

引擎的脚本内存大概占多少呢

在实际项目上,我们测试过在 3.6.1 版本,打包微信小游戏平台,发布运行在 iphone 8 设备上,加载引擎之后小游戏内存达到 191 MB。这个内存会包含小游戏基础库的内存,大概 70 MB,还有一些 JS 引擎的编译缓存。所以可以粗略定为占用 120 MB 左右。
这个测试的项目是个 2D 项目,基本把需要使用的 3D 模块都剔除了。剔除模块能有效减少引擎内存大小。
这里提醒注意,由于设备分辨率的不同,所以引擎启动时初始化的渲染内存大小也会不同。分辨率越大、游戏设置的 dpr 越大,内存占用越大。

你好,这个问题我们之前没注意到,后面我们找时间看下。