技术分享连载(五十)

技术分享连载(五十)

本期话题:内存占用分析、多线程渲染 、渲染模块性能瓶颈定位、动态合批规则...精选5个性能优化问题,建议阅读时间15分钟,认真读完必有收获。如果您有任何独到的见解或者发现也欢迎联系我们,一起探讨。

UWA问答社区:answer.uwa4d.com
UWA QQ群:793972859


内存管理

Q1:在我的UWA性能检测报告中,纹理才占50MB,但为什么Unity内存会有300MB之多,请问这部分是怎么计算的?内存大小难道不是由资源大小决定的吗?
请输入图片描述

对于绝大多数平台而言,Reserved Total内存 = Reserved Unity内存 + GFX内存 + FMOD内存 + Mono内存。Reserved Unity为Unity引擎自身各个模块内部的内存分配,包括各个Manager的内存占用、序列化信息的内存占用、WebStream的内存占用和部分资源的内存占用等等,所以纹理资源仅是Unity引擎使用内存的一部分。除纹理资源外,研发团队还需要注意Mesh资源、AnimationClip、RenderTexture、Font资源等等,同时还需要关注AssetBundle文件在内存中的驻留情况(可在UWA报告中的“资源管理->具体AB使用情况”页面中查看),因为它可能会导致SerializedFile和Webstream内存(Unity5.3版本后不再有WebStream)的内存占用过高。


渲染优化

Q2:在Android平台上,PlayerSettings里开启/关闭Multithreaded Rendering,在性能和稳定性上有多大的区别? 一般是勾还是不勾?
请输入图片描述

就我们目前深度优化过的项目而言,Android App中开启多线程渲染确实可以在渲染模块中带来较大的性能提升,但我们并没有详细地定量分析过,所以很难给出“到底能提升多少”的定量说明。对于稳定性来说,现在市面上已经有不少Unity 5.3版本的开启多线程渲染的项目在成功运营,就目前反馈的效果来看都是不错的。所以,对于使用Unity 5.3版本以后(包括Unity 5.3)的项目,我们一般建议尝试开启多线程渲染这一选项,至于能具体提升多少,可自行在项目中进行比较。

同时需要指明的是,并不是开启多线程渲染就一定会出现质的飞越,因为我们也看到一些项目(特别是使用了UGUI的项目)在开启多线程渲染后,其半透明耗时出现了非常不稳定的运行效果,如下图所示。因此,建议研发团队在开启多线程渲染后,多进行效率对比,密切关注多线程渲染带来的效率变化。关于多线程渲染性能,我们将在3月份举办的UWA Day 2017 大会上带来更为详细的性能分析,尽情期待!
请输入图片描述


渲染优化

Q3:物体的动态合批条件和是否是透明物体没什么关系吧?比如粒子系统的物体一般都是透明的,是不是也可以合批?

动态合批并不限制物体是否为半透明或不透明物体。合批的首要要求是Material一致,其本身是半透明Material还是不透明Material均没有限制。粒子系统同样是可以合批的,只要其材质一致,深度较为接近且中间没有其他材质的物体阻隔,那么Unity引擎将会将其进行合批。关于动态合批更为详细的要求,可以查看Unity引擎的官方文档:https://docs.unity3d.com/Manual/DrawCallBatching.html


渲染优化

Q4:在UWA报告的渲染模块中,网格数和Draw Call数量都不高,但为什么不透明渲染消耗这么高呢?我该如何进一步定位并优化这个问题呢?
请输入图片描述

研发团队可以在UWA报告中查看“Camera.Render”的具体性能堆栈(高CPU占用函数列表中),通过该堆栈即可看到该项目在渲染模块中详细的性能瓶颈。
请输入图片描述
如上图所示,我们可以看到不透明渲染中,场景模型渲染耗时占了13%,蒙皮网格渲染耗时占了8%。对此,我们需要对场景的静态模型和蒙皮网格进行进一步地完善。而对于静态模型来说,在Draw Call和Triangle都很正常的情况下,其CPU占用一般来说应该是较低的。但如果较高,则建议研发团队对模型中(特别是地形)渲染Shader的采样纹理数量进行进一步检测。比如,我们经常发现一些项目中地表模型使用了4~5层混合贴图来进行渲染(T4M地形等),这些在中低端机器上往往会造成不透明渲染较高的CPU开销。对于这种情况,建议尝试降低Shader所使用的采样纹理数量。当然,以上仅为我们遇到的较为普遍的情况,如果你的项目或性能报告中也存在一些难以解释的问题,欢迎随时来跟我们进行沟通。


资源管理

Q5:关于资源冗余,我从AssetBundle中加载一张贴图后,即使Unload(false),是否还会在内存中保留一个没有被任何资源引用的贴图呢?是否非得UnloadUnusedAssets()才能把这个“僵尸”资源给卸载掉?

是的,从AssetBundle中加载资源并执行Unload(false)后,其加载出的资源如果没有被引用,那么会处于“游离”状态,即问题中所谓的“僵尸”资源。除Resources.UnloadUnusedAssets()外,Resources.UnloadAsset和AssetBundle.Unload(true)均可以将其进行卸载。

今天的分享就到这里。也欢迎热爱进步的你加入UWA的QQ群(793972859),也许你的方法恰能解别人的燃眉之急;而他山之“石”,也能攻你之“玉”。 比起闭门造车,我们更乐意与大家各抒己见,畅所欲言;比起形而上的泛泛而谈,我们更乐意与大家直击痛点,对症下药。