性能优化笔记-内存

Unity引擎开发的移动游戏,内存的开销大致可以分为以下三部分:

1.资源内存占用;

2.引擎模块自身内存占用;

3.托管堆内存占用。


资源内存占用:

在一个较为复杂的大中型项目中,资源的内存占用往往占据了总体内存的70%以上。因此,资源使用是否恰当直接决定了项目的内存占用情况。一般来说,一款游戏项目的资源主要可分为如下几种:纹理(Texture)、网格(Mesh)、动画片段(AnimationClip)、音频片段(AudioClip)、材质(Material)、着色器(Shader)、字体资源(Font)以及文本资源(Text Asset)等等。其中,纹理、网格、动画片段和音频片段则是最容易造成较大内存开销的资源。

纹理:

纹理资源可以说是几乎所有游戏项目中占据最大内存开销的资源。一个6万面片的场景,网格资源最大才不超过10MB,但一个2048*2048的纹理,可能直接就达到16MB。因此,项目中纹理资源的使用是否得当会极大地影响项目的内存占用。


纹理格式:

纹理格式不仅影响纹理的内存占用,同时还决定了纹理的加载效率。一般来说,应尽可能根据硬件的类型选择硬件支持的纹理格式。比如Android平台的ETC,IOS平台的PVRTC,Windows PC上的DXT等等。


在使用硬件支持的纹理格式时,可能会有以下问题:

1.色阶问题

由于ETC、PVRTC等格式均为有损压缩。因此,当纹理色差范围跨度较大时,均不可避免地造成不同程度的“阶梯”状的色阶问题。因此,很多研发团队使用RGBA32/ARGB32格式来实现更好的效果。但是,这种做法将造成很大的内存占用。比如,同样一张1024*1024的纹理,如果不开启Mipmap,并且为PVRTC格式,则内存占用为512KB,而如果转换为RGBA32位,则很可能占用达到4MB。所以更为明智的选择是尽量减少纹理的色差范围,使其尽可能使用硬件支持的压缩格式进行存储。

2.ETC1不支持透明通道问题

在Android平台上,对于使用OpenGL ES2.0的设备,其纹理格式仅能支持ETC1格式,该格式有个较为严重的问题,即不支持Alpha透明通道,使得透明贴图无法直接通过ETC1格式来进行存储。建议将透明贴图尽可能分拆成两张,即一张RGB24位纹理记录原始纹理的颜色部分和一张Alpha8纹理记录原始纹理的透明通道部分。然后,将这两张贴图分别转化为ETC1格式的纹理,并通过特定的Shader来进行渲染,从而来达到支持透明贴图的效果。这种方法不仅可以极大程度上逼近RGBA透明贴图的渲染效果,同时还可以降低纹理的内存占用。


当然,目前已经有越来越多的设备支持了OpenGL ES3.0,这样Android平台上就可以进一步使用ETC2甚至ASTC,这些纹理格式均为支持透明通道且压缩比更为理想的纹理格式。


纹理尺寸:

一般来说,纹理尺寸越大,则内存占用越大。所以,尽可能降低纹理尺寸,如果512*512的纹理对于显示效果已经够用,那么就不要使用1024*1024的纹理,因为后者的内存占用是前者的四倍。


Mipmap功能

Mipmap技术有点类似于LOD技术,但是不同的是,LOD针对的是模型资源,而Mipmap针对的纹理贴图资源。使用Mipmap后,贴图会根据摄像机距离的远近,选择使用不同精度的贴图。
缺点:会占用内存,因为mipmap会根据摄像机远近不同而生成对应的八个贴图,所以必然占内存!

优点:会优化显存带宽,用来减少渲染,因为可以根据实际情况,会选择适合的贴图来渲染,距离摄像机越远,显示的贴图像素越低,反之,像素越高!

Mipmap旨在有效降低渲染带宽的压力,提升游戏的渲染效率。但是,开启Mipmap会将纹理内存提升1.33倍。对于具有较大纵深感的3D游戏来说,3D场景模型和角色一般是建议开启Mipmap功能。有时候部分UI纹理也开启了Mipmap功能,其实是没有必要的,绝大多数UI均是渲染在屏幕最上层,开启Mipmap并不会提升渲染效率,反倒会增加无谓的内存占用。


Read&Write

一般情况下,纹理资源的“Read&Write”功能在Unity引擎中是默认关闭的。开启该选项将会使纹理内存增大一倍。


网格:

网格资源在较为复杂的游戏中,往往占据较高的内存。对于网格资源来说,需要注意以下几个方面:

Normal&&Color&&Tangent

Mesh资源的数据中经常会含有大量的Color数据、Normal数据和Tangent数据。这些数据的存在将大幅度增加Mesh资源的文件体积和内存占用。其中,Color数据和Normal数据主要为3DMax、Maya等建模软件导出时设置所生成,而Tangent一般为导入引擎时生成。

更为麻烦的是,如果项目对Mesh进行Draw Call Bathching操作的话,那么将很有可能进一步增大总体内存的占用。比如,100个Mesh进行拼合,其中99个Mesh均没有Color、Tangent等属性,剩下一个则包含有Color、Normal和Tangent属性,那么Mesh拼合后,CombinedMesh中将为每个Mesh来添加上此三个顶点属性,进而造成很大的内存开销。


引擎模块自身占用

引擎自身中存在内存开销的部分纷繁复杂,可以说是由巨量的“微小”内存所累积起来的,比如GameObject及其各种Component(最大量的Component应该算是Transform了)、ParticleSystem、MonoScript以及各种各样的模块Manager(SceneManager、CanvasManager、PersistentManager等)

一般情况下,上面所指出的引擎各组成部分的内存开销均比较小,真正占据较大内存开销的是这两处:WebStream和SerializedFile。其绝大部分的内存分配则是由AssetBundle加载资源所致。简单言之,当使用new WWW或CreateFromMemory来加载AssetBundle时,Unity引擎会加载原始数据到内存中并对其进行解压,而WebStream的大小则是AssetBundle原始文件大小+解压后的数据大小+DecompressionBuffer(0.5MB)。同时,由于Unity5.3版本之前的AssetBundle文件为LZMA压缩,其压缩比类似于Zip(20%-25%),所以对于一个1MB的原始AssetBundle文件,其加载后WebStream的大小则可能是5-6MB。因此,当项目中存在通过new WWW加载多个AssetBundle文件,且AssetBundle又无法及时释放时,WebStream的内存可能会很大。

对于SerializedFile,则是当使用LoadFromCacheOrDownload、CreateFromFile或new WWW本地AssetBundle文件时产生的序列化文件。

对于WebStream和SerializedFile,需要注意以下两点:

1.是否存在AssetBundle没有被清理干净的情况。可以通过Unity Profiler直接查看其使用的具体使用情况,并确定Take Sample时AssetBundle的存在是否合理;

2.对于占用WebStream较大的AssetBundle文件(如UI Atlas相关的AssetBundle文件等),建议使用 LoadFromCacherOrDownLoad或CreateFromFile来进行替换,即将解压后的AssetBundle数据存储于本地Cache中进行使用。这种做法非常适合于内存特别吃紧的项目,即通过本地的磁盘空间来换取内存空间。


托管堆内存占用


对于目前绝大数基于Unity引擎开发的项目而言,其托管内存是由Mono分配和管理的。“托管”的本意是Mono可以自动地改变堆的大小来适应所需的内存,并且适时地调用垃圾回收(Garbage Collection)操作来释放已经不需要的内存,从而降低开发人员在代码内存管理方面的门槛。

但是这并不意味这可以在代码中肆无忌惮地开辟委托堆内存,因为目前Unity所使用的Mono版本存在一个很严重的问题,即:Mono的堆内存一旦分配,就不会返还给系统。这意味着Mono的堆内存是只升不降的。举个例子,项目运行时,在场景A中开辟了60MB的托管堆内存,而到下一场景B时,只需要使用20MB的托管堆内存,那么Mono中将会存在40MB空闲的堆内存,且不会返还给系统。这是不愿意看到的现象,因为对于游戏(特别是移动游戏)来说,内存的占用可谓是寸土寸金,让Mono毫无必要地锁住大量的内存,是一件非常浪费的事情。

不必要的堆内存分配主要来自于以下几个方面:

1.高频率地New Class/Container/Array等。切记不要在Update、FixUpdate或较高调用频率的函数中开辟堆内存,这会对项目内存和性能均造成非常大的伤害。做个简单的计算,假设项目中某一函数每一帧只分配100B的堆内存,帧率是1秒30帧,那么1秒钟游戏的堆内存分配则是3KB,10分钟后就已经分配了1.8MB。如果有10个这样的函数,那么10分钟后,堆内存的分配就是18MB,这期间,它可能会造成Mono的堆内存峰值升高,同时又可能引起多次GC的调用。

2.Log输出。仅需保留关键Log,以避免不必要的堆内存分配。

3.UIPanel.LateUpdate。这是NGUI中CPU和堆内存开销最大的函数。它本身只是一个函数,但NGUI的大量使用使它逐渐成为一个不可忽视规则。该函数的堆内存分配和自身CPU开销,其根源上是一致的,即是由UI网格的重建造成。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值