优化内存
一、内存与 OOM
Unity WebGL 游戏通常比普通 H5(JS) 游戏占用更大的内存,在操作系统的控制策略下超出阈值时非常容易被 OOM。
为了提高游戏在中低端机型的稳定性,内存优化极为重要。那么,多大的内存是合理的呢? 我们建议内存峰值控制在以下范围(性能评估标准):
Android: 低档机 < 1.2G, 中高档机 < 1.5G iOS: 低档机 < 1G, 中高档机 < 1.4G 相对而言,Android 机型的内存更为宽松。
iOS 低档机为 iPhone 6sp/7/8 等 RAM 2G 机型为主,中高档机为i Phone 7P/8P/iPhoneX/iPhone11 等 RAM 3G 或以上机型。
二、Unity WebGL 适配小游戏的内存结构
Unity WebGL 是以 WebAssembly+WebGL 技术为基础的应用,运行在浏览器环境,因此游戏内存的分配也是完全托管在这个环境中。
适配在小游戏后,小游戏进程也就成为了“容器”,虽然不再是标准的浏览器,但内存组成结构基本一致
基础库+Canvas:在小游戏环境中并不存在 DOM,但依然会存在一些基本消耗,比如小游戏公共库,Canvas 画布等。典型地,小游戏公共库约占用内存 100~150MB,Canvas 画布与设备物理分辨率相关,比如 iPhone 11 Promax 占用约 80MB。
Unity Heap: 托管堆、本机堆与原生插件底层内存。举例,游戏逻辑分配的 C# 对象等托管内存、Unity 管理的 AssetBundle 和场景结构等本机内存、第三方原生插件(如 Lua )调用的 malloc 分配。
WASM编译: 代码编译与运行时指令优化产生的内存,在 Android v8、iOS JavascriptCore 中还需要大量内存进行 JIT 优化
GPU内存:纹理或模型 Upload GPU 之后的显存占用, 由于 Unity2021 之前不支持压缩纹理,纹理内存会造成明显膨胀。
音频:Unity 将音频传递给容器(浏览器或小游戏)后,播放音频时将占用的内存。目前 UnityAudio 将自动适配小游戏,特别地请避免使用 FMOD 播放长音频
Emscripten 使用文件系统模拟 Linux/POSIX 接口,代价是占用与文件同等大小的内存。 请勿使用首资源包、Addressable Cache 机制、WWW.LoadFromCacheOrDownload 等 Cache API*** 网络请求造成的浏览器端JS临时内存、垃圾回收
三、内存查看工具
进程级别: Perfdog、Android Studio、 Mac Xcode Instrument
UnityHeap (CPU主内存): 性能面板、ProfilingMemory、JavaScript Heap
引擎与资源:UnityProfiler
四、内存优化方案
计算公式: 小游戏基础库 + Cavnas + 编译内存 + UnityHeap + Gfx显存 + 音频 + JavaScript内存。 UnityHeap = max(托管/Mono内存) + max(Native/Reserved 内存 + C 原生代码内存)
以 iOS 高性能模式为例,一款代码(导出目录/webgl/Build/xxx.code.unityweb或code.wasm)大小为 30MB 的游戏占用内存为: 小游戏基础库(130MB) + Cavnas(70MB) + 编译内存(300MB) + UnityHeap + Gfx显存 + 音频 + JavaScript(通常<100MB)。
假如游戏需要支持低档机型,将内存控制到 1G 以内,业务侧(UnityHeap, Gfx显存,音频,JavaScript)需控制在500MB左右。我们此处给出转换游戏中最容易遇到的内存问题与解决方案,如果开发者遇到内存问题时请逐个排查优化
4.1 WASM 代码编译内存
问题原因:Unity WebGL 将所有代码(引擎、业务代码、第三方插件)编译为跨平台的 WebAssembly 二进制代码,运行时需进行编译执行。编译所占用内存占用非常大(如在iOS系统,30MB未压缩代码需300MB运行时编译内存)
解决办法:
- 使用代码分包工具能降低原编译代码内存50%以上。
- 手动删除多余插件,减少不必要的 Unity 模块引入(如物理、Unity 数据统计等)
4.2 GPU 内存
问题原因:Unity 2021 才开始支持移动平台的压缩纹理,使用 RGBA、DXT 等纹理格式将导致巨大的内存开销与运行时解压消耗。
解决办法:
- 压缩纹理优化能最大程度地减少内存与解压开销。
- 升级引擎至 2021 使用 ASTC 压缩纹理
- 关闭 HDR,标准渲染管线在"GraphicsSetting-tier2"(WebGL使用tier2)取消勾选"Use HDR";URP 管线通
- 过 renderer 配置取消
- 使用高性能+模式将显著降低 GPU 内存
4.3 UnityHeap
问题原因:UnityHeap 是用于存储所有状态、托管的对象和本机对象,往往由于场景过大或由于业务原因造成瞬间内存峰值。由于 Unity WebGL 在单首帧内无法GC,单帧内瞬间的内存使用非常容易造成 crash。同时,Heap 是只增不减且存在内存碎片的。
解决办法:
- 转换面板设置合理的“UnityHeap预留内存”,切忌超出使用
- 避免场景过大导致瞬间峰值
- 避免过大的 AssetBundle 导致瞬间峰值
- 避免单帧内分配过多的对象, 切忌产生跳跃峰值
如何设置“UnityHeap预留内存”?该值仅表示UnityHeap的峰值进行预留,避免内存不足时导致扩容产生的尖刺;
导出面板勾选“显示性能面板”或 unity-namespace.js中enableProfileStats 变量打开性能面板
将游戏运行一段时间,观察 DynamicMemory 的峰值
UnityHeap=DynamicMemory+ 少量静态内存(通常<10MB),因此转换面板的“UnityHeap预留内存”设置为略大于 DynamicMemory 峰值(可多预留50-100MB,以实际游戏为准)。建议值:超休闲游戏256,中度游戏(模拟经营、卡牌成长)496,重度游戏(SLG,MMO)768。
UnityHeap 不宜过大,当 UnityHeap>=1024MB 时,大部分设备将启动失败;UnityHeap>=500MB 时与 iOS 普通模式大概率启动失败,建议中轻度游戏不超过该值。
4.4 首资源包与 AssetBundle 内存
问题原因:首资源包永远占用内存且无法释放;首资源包和 AssetBundle 自带的 cache 机制都会使用 Emscripten 使用文件系统,应避免使用。
解决办法:
减少首资源包大小,此部分始终占用内存无法释放, 使用 AssetBundle;
AssetBundle 按需加载,及时释放以节省内存;
AssetBundle 使用时被解压占用 Unity Native 内存,应减少 AssetBundle 大小;
避免使用 Unity 自带的文件缓存机制, 首资源包和 AssetBundle 都不应使用文件 Cache;
音频内存
问题原因:音频将占用小游戏环境的内存
解决办法:
不要使用 FMOD 播放长音频,如游戏 BGM
控制音效数量,同时存在的音频数不应该超过 20 个
尽量强制使用单声道音频,双声道会产生 2 倍内存消耗
