在性能优化这块,内存是个很重要的指标,内存优化做的不好,可能导致内存泄漏,超出引用程序可使用的内存上线,从而导致卡顿,发热,白屏,闪退等异常。
对于游戏,内存占用主要是资源,代码本身内存占用,动态对象的内存,下面主要从以下几点讲述内存优化思路
资源引用计数管理
游戏启动后,随着游戏不断运行,打开的功能模块越来越多,资源加载到内存中会不断累积,对小型游戏来说资源较少可以不做释放相关管理,但是稍微中重度点的游戏,资源如果不做管理,内存会逐步吃紧。
目前各种主流游戏引擎都提供了资源加载,资源释放相关的偏底层的api,但是一般开发的时候不可能让每个前端自己去手动调用每个资源的释放,这样很容易出错。这块儿一般是基于引擎提供的底层接口,封装资源加载与释放相关,上层模块在对应生命中期处自动调用封装后的接口,实现资源管理。
业内常用的比如基于引用计数的管理,例如cocos creator的Asset基类,提供了addRefer和decRefer两个基础接口管理引用计数,自己封装层加载资源的时候调用addRefer,资源生命周期结束时(比如资源对应的组件所在界面销毁?)自动调用decRefer。
实现基于引用计数的资源管理时要考虑一下几个问题:
- 资源引用计数是否真的要增加。资源只有真正在使用时才需要变更引用计数,比如Image,只有切换图像源src的时候,新资源引用计数+1,旧资源引用计数-1,而不是实现在加载的地方(这块可以改引擎源码,或者直接Hack原型链上的方法,插入对应代码)。想象一下直接调用一个加载接口加载一个图片,但是图片未实际赋值给任何组件,实际引用计数应该还是0的。
- 资源引用计数只有归零才能真正释放资源。一个资源可能在多个界面中使用,不能在A界面关闭销毁的时候就把这个资源给彻底释放了,毕竟还有其他界面在用呢,所以只有一个资源引用计数归零时才能真正释放。
- 设置资源释放白名单。对于公用资源比如通用皮肤组件,窗体通用一般是在预加载就载入的,可以设置永不释放。
- 最好能实现某些资源引用计数归零后的延迟释放。比如界面资源,如果关闭界面对应的资源就立马释放,可能玩家经常在界面之间切换操作,就会不停触发释放与重新加载,卡顿比较明显,体验会比较差,如果有延迟释放比如延迟10秒释放,快速切换时因为缓存还在不会触发重新加载。
- 监控缓存所有对象的大小。上面提的延迟释放,如果玩家进入游戏快速点开了超级多的界面,而这些都设置了延迟释放会导致内存暴涨,给资源缓存这块设置阈值,超过阈值之后优先释放最先打开的窗口对应的资源。
代码自身内存
这里其实就要求代码精简,主要有以下几点
- 主要是代码打包时开启Tree shaking, 未使用到的类这些会被移除
- 像Cocos Creator引擎,引擎库是分模块的,自己使用了哪些模块就勾选对应的模块即可,精简最终发布的引擎库
- 使用到的第三方库,有时候仅仅只使用到了第三方库的某个子功能,导入库的时候尽量导入满足需求的最小化库,而不是全部导入。
- 代码压缩比如minify要开启。代码混淆没要求的就不加。
动态对象内存
- 尽量不要在全局对象比如window/globalThis上挂大内存对象,这种要特别注意手动移除引用
- 对象池的缩容。比如对象池Sprite对象池的大小是30(表示通常情况下30个Sprite就够用),如果某个时候需要60个Sprite对象,使用完毕之后这个60个Sprite被对象池回收,对象池大小变成了60,但是大多数情况下只需要30个Sprite,所以在适当的实际需要将对象池回复到30的大小,即彻底销毁多余的30个Sprite。
- 定时器在生命周期结束后需要移除,定时回调一般会带this上下文,而定时器可能是全局的,会导致定时器持有的上下文无法从内存中释放。
- Tween缓动移除,同样是this引用问题。
- 事件移除,同样是this引用问题。
内存泄漏
内存泄漏问题有的其实很难排查,因为一个对象的不正确释放,不会立马引起什么异常表现,但是积少成多。
尽量在开发期注意不要在全局或者单例上挂大对象,非要挂大对象记得用完移除,全局的定时器回调移除,Tween缓动即使kill,事件有添加就有对应的移除。
自己的模块开发完多关注关注开发者工具的内存曲线,看看频繁打开关闭,最后内存是否会持续增长,如果有内存泄漏,可以抓取内存快照进行分析,比对。
来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |