关于游戏的内存优化 我们QA可以做什么

藏书馆

欢迎来到网易游戏学院藏书馆

关于游戏的内存优化 我们QA可以做什么

作者:温琪(QA)

  对于手游来说,运行时内存占用是衡量游戏性能的最重要的指标之一。游戏中内存占用过高,最常见的表现是会经常出现不明原因的闪退,而闪退最影响用户体验,尤其对于已上线或将上线的游戏,每一次闪退都会影响到玩家的留存,因此内存问题是每个QA都要面临的问题,而这通常也是令QA比较头疼的问题。

  内存测试并不像UI测试、逻辑测试那样可以在游戏中看到实际的表现,能够较准确的定位问题出现的位置和原因,它是一个看不见、摸不着、并且一直在变化的东西。对于开发后期的项目,性能数据相对稳定,玩法迭代较少,每周的周版本质量报告可以跟踪到内存的变化情况,出现问题可以及时跟进解决;但对于处于开发中前期的项目,这种方法不太奏效,因为大部分项目开发初期美术资源、程序代码等迭代比较频繁,一周之内内存数据变化很大,根据一个简单的性能数据无法定位问题的根源,而且也没有实际意义。因此,在一个项目的中前期,产品不会太关注内存的数据,而到了中后期,可能内存问题已经比较严重了,这时,我们QA除了发一下每周的周版本质量报告,汇报一下整体的内存占用情况之外,我们还能做些什么呢?

知其然,必先知其所以然

  XX游戏最近经常闪退,产品经理已经注意到解决内存问题的重要性,让QA测试一下内存并反馈给程序,于是便有了下面的对话。

  QA:程序哥哥,这是每个界面和场景的详细内存报告,你看下。(好烦啊,总算测完了)

  程序:哦。(还有好多单要做,哪有时间查内存…)

  (一段时间后…)

  程序:我优化了一些代码,你再测试一下吧

  QA:好的。(又要测一遍…/(ㄒoㄒ)/~~)

  (一段时间后…)

  QA:内存并没有明显改善啊,只减少了几M。(TMD,你玩我…)

  程序:好吧,我有时间在仔细查一下吧。

  (然后…)

  (……)

  (就没有然后了…)

  上面的情况只是一种虚拟的情景,但是大部分项目组都有这种情况,程序同学很忙,不能拿出大量的时间和精力来查找内存问题,而QA测试一次内存也费时费力,改一次测一次,深受其苦。既然程序同学很忙,我们QA也被反复测试的问题所困扰,何不去了解一下内存占用过高的原因,帮程序找到解决问题的入口点呢?

1.谁吃了我们的内存

  到底是哪部分占用的内存最多呢,代码、数据、贴图、音频,一个程序内存中的东西无非是这些。代码部分占用的内存是很有限的,策划表数据也都是些文本,占用的内存空间和转换成脚本之后在硬盘上占用的空间相差不大,普通的游戏音频也不会占用太多内存,优化空间也有限,而美术资源,是游戏内存占用的大头,而且它的内存占用跟在硬盘上存放的大小无关,只跟贴图的尺寸和图片格式有关。

2.一张贴图占用多少内存

  一张图片显示到屏幕上要经过两个过程,首先要预加载到缓存中,然后渲染到场景中。这两个过程都会消耗内存。

  以cocos2d为例,cocos在创建一个精灵的时候,会先将图片加载到缓存中 (CCSpriteFrameCache 或 CCTextureCache),如果缓存中已存在,直接从缓存中提取。这部分缓存就是图片加载后占用的内存。图片加载到内存之后再渲染,内存×2。一张普通的png图片,尺寸为1024*1024,在执行 CCSprite *pSprite = CCSprite::spriteWithFile("test.png")之后,内存增加了4M,再调用 addChild()方法将精灵加载到场景中,内存又增加了4M。也就是说,一张普通png图片从加载到渲染一共会增加长×宽×4×2的内存,当然,多次渲染同一张图片时,内存是不会增加的。

  不同图片格式和纹理像素格式占用的内存不同,修改纹理的像素格式可以优化内存的使用,但是会牺牲图片显示的质量,需要做一个平衡。

工欲善其事,必先利其器

既然美术资源是内存占用的主要部分,我们就从美术资源内存占用情况着手分析。当然,工具方法很重要。

1.静态界面检查

  很多项目的界面都是由一个UIManager的类来管理的,界面在定义时会向UIManager 注册自己的销毁方式,UIManager 在适当的时机控制界面的销毁。

  界面销毁的方式通常有三种:永不销毁、内存警告时销毁和关闭时销毁。一般,考虑到界面打开速度等原因,一般界面都是内存警告时才销毁。但是界面销毁时,是否做了充分的清理工作呢,QA又是否要多次打开一个界面去验证内存是否增加呢?

  在cocos2d的项目中,一个界面主要由场景、层、菜单、精灵等组成,而他们都继承自 CCNode类,所以我们只需要在一个纯净的环境下观察CCNode对象的数量变化即可。

  检查静态界面的内存,我们需要借助以下工具或条件:

  1)场景不存在动态变化的对象干扰(移动的对象、自动弹出的系统消息等)。

  2)程序帮忙实现打印出当前场景节点数的功能。

  3)程序提供一个模拟内存警告的按钮(用于关闭界面后立即清理)。

  上图是一个没有内存泄露的例子。打开界面前,结点总数为5618,模拟内存警告清理一下当前的内存使用,然后打开界面,节点数变为5822,关闭界面,节点数仍为5822,可见关闭之后内存并没有释放,此时再模拟一次内存警告,清理内存,节点数恢复5618。

2.动态场景资源检查

  这里说的动态场景是指进入和退出场景时都要经过一个loading界面进行资源的加载和释放的场景,比如战斗场景。

  与静态界面不同的是,动态场景存在许多变化因素,节点数变化没有规律,无法用上面的方法进行测试。

  回想一下动态场景创建的过程我们不难发现,场景的进入和退出都有资源的加载和释放,我们只要能跟踪到这部分变化的资源,对资源进行分析就能定位是否有资源未释放或者误加载了。

  Cocos项目中很多的特效都是通过ccb制作的,我们可以让程序在代码中ccb节点构造和析构的地方打印调试log,比如“+++ccb名称”、“---ccb名称”,这样在进入场景和退出场景的时候我们就能有如下的log信息。

  除了ccb之外,cocos中自带的函数dumpCachedTextureInfo()可以帮助我们获取到缓存中的贴图信息,包括贴图的尺寸、引用数量等信息。

  在进入和退出场景时,可以打印并记录出贴图信息。在拿到特效和贴图的log信息后,可以通过比较前后的变化确定哪些资源未释放,哪些资源误加载等。相信怎么处理这些log对QA来说就是小菜一碟了。

3.自动化工具

  有了检查美术资源内存占用的方法后,我们就可以开始测试工作了,一次次的手动采集数据?no,no,no,对于QA来说,这太low了。当然要自动化啦。

  相信现在很多手游组都有远程连接客户端并控制客户端的方法,cocos项目基本的原理是在游戏客户端开一个线程监听socket连接,另一端可以用PC写一个简单的脚本与客户端建立连接并按协议发送数据,客户端接收后,解包翻译成脚本语言,动态执行。下图是一种用控制台连接游戏客户端并打开一个界面的例子。

  QA可以把测试流程编写成脚本,连接客户端并顺序执行,同时获取客户端返回的log信息加以处理,分分钟搞定内存测试。

  现在你可以拿着报告再去找程序哥哥了。

  QA:这是我刚做的内存测试结果,xxx界面内存占用20M,打开时加载了xxx等无用贴图…xxx界面在销毁时存在内存泄露,有xx个节点未释放…xx贴图占用内存3M,可以考虑压缩……

  程序:好的,我查一下,马上修复。

写在最后

  美术资源是内存占用的主要部分,除了美术资源以外,其他的问题比较容易定位和优化。但是有些内存问题却不是我们能控制的。

  比如调用系统资源产生的一些内存,虚拟键盘、拍照等,这些内存与游戏具体逻辑无关,很难优化。某些脚本的垃圾回收机制,很多脚本语言通过一个垃圾池来存放不用的内存,垃圾收集器会定时清理垃圾池,当垃圾池内废弃的内存达到一定数量之后,垃圾收集器还没来得及清理,这时垃圾池会申请更多的内存来扩展容量,这部分内存只会增加不会减少。具体表现是,游戏中快速连续操作,中间会产生大量临时变量,垃圾池一直在扩容,内存飙升,且不会下降,内存警告也无法释放。当然通过减少GC间隔或程序手动GC可以避免这部分内存增加,但是也要付出流畅性的代价。除此之外,可能还有很多奇怪的问题会导致内存增加,这里不再一一列举。

  本文是笔者跟进cocos2d项目内存优化的一些经验,对于基于其他引擎的手游暂时还没有涉及,不过原理大同小异,希望对大家有帮助。