内存管理(二)

Q1:在UWA的帮助下,我们追踪到了一个Reserved GFX的内存占用,并且显示比较高。我们应当如何降低该内存占用呢?

一般来说,Reserved GFX 中的内存,主要是纹理和网格资源,可以尝试对纹理格式进行检测,尽可能使用硬件支持的压缩纹理;而对于网格资源,则可以从减少顶点或者顶点属性入手。 具体信息也可以查看UWA文档。
另外,更重要的是检测纹理和网格资源是否存在冗余(多份一样的资源)或者泄露(比如,主城中的大纹理出现在战斗场景中),这是需要极力避免的。关于资源冗余、内存泄露,开发者可以参考之前的文章《 性能优化,进无止境---内存篇(下)》。

Q2:如果一个模型对应Skinned Mesh Renderer实例,那其所占的内存会随着角色增加而增长么 ?

简单地从一个角色Prefab实例化(Instantiate)出多个实例时,Mesh并不会出现多份(这个行为与其他资源是一致的,包括Texture,AnimationClip,Material等等)。如果在内存中发现多份,可以考虑从项目中AssetBundle的加载方式入手,因为即使是同一个AssetBundle中的同一个角色Prefab,如果被反复进行“加载-实例化-卸载”操作,依然是会导致Mesh出现多份的(当然其他的资源也是一样)。

Q3:我们的游戏玩了20分钟后,Texture2D的内存涨到了60MB多,并且重复的资源很多,是否由于没有卸载完全?还是打包AssetBundle依赖性的问题?用的是UGUI。

内存管理(二)_第1张图片
Blog-TechSharing_7-6.jpg

存在如下两个原因:
1、AB依赖关系打包存在问题,即atlas没有被依赖打包;
2、加载卸载的管理问题,可能是加载了一次后被一个Container索引了,这时又加载了一次同样的AssetBundle又被索引。如果这些一直没有释放,也会出现这种情况。

Q4:我们在Profiler中发现了一个AssetDatabase的内存占用,其存在于Assets中,单个占用内存非常大。 请问该占用是如何发生的呢?是否可以避免?

内存管理(二)_第2张图片
Blog-TechSharing_8-5.jpg

理论上开发者在真机运行项目时是不会看到此选项的,只有在 Editor 中运行时能看到。由于在 Editor 下运行游戏时,Profiler 中获取的内存是包含了 Editor 本身占用的内存的,因此并不准确,建议连接真机来进行内存的查看。

Q5:同样的App,安装在不同的机型上,使用同样的自动化脚本进行自动化测试,App的内存消耗(ADB Dumpsys Meminfo)在不同的机型上结果迥异,有些机型只有200MB多的内存消耗,有些机型高达600MB。我们已经分析过OpenGL ES 2.0和OpenGL ES 3.0对ETC2贴图解压的影响。200MB左右的机型有些是ES2.0,600MB消耗的机型有些是ES3.0的,并且多次测试可重现。请问你们如何分析这个问题?是否有标准来确定游戏真正的内存消耗?

Android系统的内存消耗是根据芯片、OS的不同而不同的。ADB反馈出来的PSS其实仅是本机当前该App线程的内存消耗,该值与其他机器的信息其实是没有任何可比性的。
Unity的项目,其内存我们建议主要还是通过Profiler来看其内存消耗。因为它表示的是引擎开辟和分配的真实物理内存。

Q6:首先用户或者产品只关心整体消耗,并不会以Profiler为准,内存不足的崩溃也不会以Profiler正常而消失。其次,我们降低Profiler统计的消耗后,OS的消耗也会大大降低,甚至下降的更多。我们据此判断:这两个数字不一样,但应该是有联系的。我们想知道联系是什么?

可以这样理解,Profiler 中的数字表示 Unity 向系统索要的内存值,而系统则会根据当前的内存情况、内存管理策略来进行分配,不同的硬件采取的策略是不同的(比如是否保留缓存,何时换页,内存回收的频率等等)。
PSS 就是获取当前的系统内存分配信息,这并不代表 app 的真实内存需求。
另外,还关乎 PSS 获取算法的具体实现,比如有些硬件不把显存计算在内,有些会计算等等。
关于PSS和Profiler之间的数值关系,我们认为确实是存在联系的,但该联系在不同的硬件上是不同的,且需要由硬件厂商来提供,这也是我们仅仅把 PSS 作为参考,把 Profiler 作为关注点的原因所在。

Q7:正常情况下游戏如果一直玩下去,Mono是不是会一直增加? 比如频繁打开一个界面,界面里有脚本会不断创建一些东西 ,那么Mono是否会不断增加?对性能上会不会造成影响呢?

A:Mono 确实是不会下降,但并不应该一直上升。
创建出来的东西,如果被引用在一个容器里,或者被某些脚本的变量引用,那么这部分堆内存就释放不掉;但如果没有被任何容器或者变量引用(比如,临时拼一个 String),那么这部分堆内存会在 GC 的时候释放(释放是指变为空闲的堆内存,堆内存的总量是不会下降的)。
对于后者,频繁地 new 对象虽然不会一直增加堆内存,但是会加速 GC 调用的频率,所以同样是需要尽量避免的。

Q8:下图是UWA性能检测报告,图中标红的这两个函数常常导致很高的堆内存分配,那么我该如何检查或者避免呢?


内存管理(二)_第3张图片
Blog-TechSharing_12-2.png

Canvas.SendWillRenderCanvases为UGUI中非常重要的接口,经常会出现较高的性能开销。当Canvas中的UI元素出现了长、宽或Alpha变化时,UGUI会更新其所在Canvas中所有UI元素的Transform、状态等等。Canvas中UI Mesh顶点较多的话,则该项将会出现较高的CPU开销。
Loading.UpdatePreloading为Unity引擎的主要加载函数。场景中的资源加载(包括Texture、Mesh、Shader、AnimationClip等)和相关序列化操作均在其中体现。因此,如果该值开销较高,建议研发团队对资源进行进一步的优化和控制。

Q9:如果脚本引用了GameObject,那转换场景的时候脚本和GameObject都没了,还会产生堆内存的吗

如果脚本是MonoBehaviour,而且在切换场景后所挂的Game Object被释放了,那么这个脚本对象所引用的堆内存就会在GC的时候被释放。 但有一种例外,如果是通过Static变量引用的堆内存,那么依然是释放不掉的,除非手动解开引用,比如变量置Null,数组Clear等等。

Q10:我们现在有一个场景,Draw Call和面数都在正常范围内,Camera的距离也和其他场景一样,但是VBO却非常高。下图是该场景的数据情况,该场景下有很多复用的模型,如果不勾选Static那VBO会下降,但是Draw Call会上升。那我能否通过合并模型的方式把VBO降下来呢?


内存管理(二)_第4张图片
Blog-TechSharing_14-3.png

当你勾选Static时,Unity 会将其进行 Static Batching,进而将生成一个较大的VBO来进行渲染,同时降低Draw Call。而如果不勾选时,则引擎无法对其进行Batch,所以VBO会降低,而Draw Call升高。
对此,我们的建议如下:
1、一般来讲,降低Draw Call的意义大于降低VBO;
2、在Draw Call较低的情况下,比如当前的50+,可以考虑适当增加一些Draw Call来降低VBO的占用,一方面可以降低带宽的压力,另一方面也可以适当降低一些内存。

你可能感兴趣的:(内存管理(二))