【求知探新】分享一次查找GfxDriver内存暴涨的经历

【求知探新】分享一次查找GfxDriver内存暴涨的经历

【求知探新】是UWA新推出的栏目。在大家做性能优化的过程中,常常会遇到一些未知的问题,在这里我们将分享研究这些问题的完整过程。当然需要说明的是,一个好的问题没有标准的答案,在此也欢迎大家来积极拍砖!

GFX内存为底层显卡驱动所反馈的内存分配量,主要由渲染相关的资源量所决定,它的大小也和项目的运行内存息息相关,所以我们在优化时也需要重点关注。本文,作者李星(联系邮箱:lx458004975@163.com)分享了项目研发过程中,由于GFX暴涨导致了内存崩溃的排查过程,值得参考。


一、问题描述

近期我们项目从Unity 5.5直接切换到了Unity 2017.3,颜色空间也由之前的Gamma切换到了Liner。为了测试切换大版本后,对游戏的FPS影响有多大,我们开始对一些战斗场景进行多人PVP的测试,然而经过几分钟后游戏闪退了,多次测试并且通过Logcat我们发现显存爆掉了。为了进一步的确认,我们切换到了Debug版本,通过Profiler发现GfxDriver撑的特别高,一般被爆掉的时候高达300MB以上。


二、问题分析

一般而言GfxDriver统计的内存其实就是Textures + Buffer(顶点和索引)的显存,这个在Unity 4源码中其实GfxDriver的统计也只是在提交Textures以及顶点和索引的使用调用这个统计宏,且Unity中文论坛中也有相应帖子解释http://forum.china.unity3d.com/thread-1091-1-1.html

然而,我们观察发现Profiler统计的Textures + Meshes的内存远小于GfxDriver统计的内存,这就排除了因为资源管理不当没有及时释放资源导致的问题,因为一定时间后Textures和Meshes的内存已经相对稳定了。


三、问题排查

1、Auto Graphics API勾选的影响

之前依稀记得UWA官方群里面关于勾选Auto Graphics API后Gfxdriver暴涨的讨论,后来网络也搜索了下,找到了两个。

一个来自知乎:
https://www.zhihu.com/question/60371399

一个是UWA问答:
https://answer.uwa4d.com/question/5a741b6fe3b569146eeaeb3a

从这两个搜索到的帖子来看勾选Auto Graphics API后导致GfxDriver暴涨的可能性也还是比较大的,因此开发大神去掉了这个勾选,选择了OpenGL ES3,经历过漫长的打包等待后,再次测试验证,发现问题依然存在,并没有解决问题。

2、监控PSS,对Memory进行Detailed监控

在发现去掉勾选Auto Graphics API并不能解决问题后,又对其进行了勾选回去,同时我们对Memory进行Detailed监控,也并没有发现任何可疑的地方,因此我们再监控了下PSS,发现PSS的走势是随着GfxDriver的增长而增长。这个问题是因为显存爆掉而产生的,之前用Unity 5.5版本的时候就遇到过Android 6.0的机器会因为这个而闪退,而Android 7.0的机器却不会闪退,那么会不会是不同系统、不同手机而结果不一样呢?我们两台小米5,一台是Android 6.0,一台是Android 7.0的,测试都会闪退,用红米Note4来测试,发现居然不闪退,而且GfxDriver也挺高的。开始怀疑人生了,资源没问题,内存详情里面也看不出蛛丝马迹,关键是不同的手机居然还有不闪退的,会不会是Unity的Bug导致?

PS:这里有一点忽略了,其实在事后我们解决了问题后再回过头来看,在Detailed模式下进行Take Sample还是有一点踪迹可寻的,在Scene Memory项下可以看到Sprite的数量是飙升的,当时主要是Scene Memory的总内存数占用不大,与GfxDriver统计到的300MB多相差太远,而且我们Take Sample的次数也较少,同时在Simple面板可以看到Total Object Count的数量在涨,当时进入了误区,以为是Unity的Bug,因为Unity的坑有时候也有遇到嘛。

3、对测试场景的单个资源单个技能一一测试

在经历这么多尝试之后依然没有解决问题,同事开始了针对当前测试场景的每个特效、每个技能逐一单独进行排查。在经历了几天的排查之后告诉我找到了,是某一个游戏技能引起的,我立马打开编辑器运行到其场景,然后定位到其相应的角色及UI上,因此对其部分代码进行屏蔽,打包再次测试,终于有结果了,造成GfxDriver暴涨的就是这一段代码。

这段代码有两个影响点,一个是阴影,一个是Sprite的。因为在Memory Detailed模式下,我们并没有发现和阴影相关的问题,所以怀疑是Sprite,在粗略地浏览了下封装的Sprite的代码后,我们屏蔽了Sprite.OverrideGeometry的提交,因为这是唯一能影响显存的代码,结果事实并不能如我们所愿,因此我们改而屏蔽文字阴影,结果也是一样;后来再回到Sprite的封装上面来看,发现一个可疑之处,那就是Sprite.Create的调用,因为事前这段代码是有条件控制的,然而通过断点调试,发现每次这个UI Active后就会被调用到,那么在多人PVP的时候就会被频繁地调用,不停地去Create。最终在修改了这段逻辑控制之后,问题得到完美的解决。在开发的注释中能够发现,出现这一Bug也是为了避免Unity Sprite本身的Bug而产生的(Sprite不能Destroy掉,在多线程模式下可能会有概率在Editor、Android环境下闪退,只能等UnloadUnuse()来GC,开发传参传错了,导致每次都会创建)。当然这个问题在Unity 5.5的时候并未出现,在项目切换到Unity2017.3后才暴露出来,经过Demo测试,在高通芯片的机型上GfxDriver在几分钟内能飙升到300M+,导致闪退;而MTK芯片的机型上GfxDriver的内存则不会增长。


四、总结

问题已经解决,到了划重点的时候了,当GfxDriver内存暴涨,而Textures和Meshes的总内存又比较平稳的时候,除了可能是Auto Graphics API的问题以外,项目中是否有使用GPU Instancing呢?(这个一般比较好排除,因为当前场景有什么花啊草啊的一目了然,而且Demo验证过除非极限地去刷GPU Instancing,不然对GfxDriver的影响是非常小的),项目中是否有使用Sprite呢?而我们项目就是出在了Sprite的使用上,并且一开始通过Memory Detailed模式选项下都不能定位到问题。这里要仔细地查看每一项,而且多次对比后可能才会发现,此次就忽略了Sprite的数量,只考虑了内存大小,没有点开Scene Memory列表看,因为每次内存占用才20MB左右。

很多时候我们会遇到一些奇奇怪怪的问题,尤其是这种内存问题比较难查,可能是资源的问题,也有可能是代码的问题,还有可能是Unity本身隐藏的Bug(概率小但不排除)。个人认为排查的方法很重要(唉,技能树严重缺技能呀),再者就是需要足够的耐心和细心程度(不放过任何可疑的地方)。


文末,再次感谢李星将这篇极具参考价值的技术分享进行分享。同时,我们也欢迎大家在UWA问答社区(answer.uwa4d.com)积极提交研发过程中遇到的问题。也许随着时间的流逝,科技的进步,答案将变得廉价,但问题会变得更有价值,因为提问和研究将比回答更有力量。