使用Profiler工具分析内存占用情况
作者与原文信息
- 作者:Ricky Yang
- 原文链接:http://blog.csdn.net/yangyy753
引言
Unity3D为开发者提供了强大的性能分析工具Profiler。本文将使用Profiler详细分析官方示例AngryBots的内存使用信息。
使用Profiler分析内存
首先,打开Profiler并选择Memory选项。在游戏运行的某一帧查看Detailed选项数据(Simple模式的数据较为直观,能让我们了解内存大体被哪部分占用,网上有很多相关介绍,此处不再赘述)。选中后,Unity会自动获取该帧的内存占用数据,主要分为Other、Assets、BuiltinResources、Scene Memory、NotSaved五大部分,下面逐一分析。
Other部分
由于记录数据项众多,受篇幅和时间限制,我们重点分析占用大小排行榜靠前的几项。
- System.ExecutableAndDlls:系统可执行程序和DLL,属于只读内存,用于执行所有脚本和DLL引用。不同平台和硬件环境下,该值会有所不同,可通过修改Player Setting的Stripping Level来调节大小。不过,作者尝试修改Stripping Level后未发现明显改变,且该部分虽占用内存较大,但不影响游戏运行,故可暂时忽略。
- GfxClientDevice:GFX(图形加速/图形加速器/显卡 (GraphicsForce Express))客户端设备。尽管占用较大内存,但它是游戏运行的必备项,难以进行优化,可忽略。
- ManagedHeap.UsedSize:托管堆使用大小,是重点监控对象。建议移动游戏中该值不要超过20MB,否则可能引发性能问题。
- ShaderLab:Unity自带的着色器语言工具相关资源,大家较为熟悉,可忽略。
- SerializedFile:序列化文件,用于将Prefab、Atlas和metadata等资源加载到内存中,是重点监控对象。需关注哪些预设在序列化过程中占用的内存大小,并根据需求进行优化。
- PersistentManager.Remapper:与持久化数据重映射管理相关,涉及AssetBundle等持久化数据。需注意监控相关文件。
- ManagedHeap.ReservedUnusedSize:托管堆预留不使用的内存大小,仅由Mono使用,无法进行优化。
Assets部分
- Texture2D:2D贴图及纹理,是重点优化对象,可从以下方面进行优化:
- 压缩贴图格式:许多贴图采用ARGB 32 bit格式,保真度高但内存占用大。在不失真的前提下,可使用ARGB 16 bit格式,内存占用减半;Android平台采用RGBA Compressed ETC2 8 bits,iOS平台采用RGBA Compressed PVRTC 4 bits,可进一步减半。对于不需要透贴但有alpha通道的贴图,Android可转换为RGB Compressed ETC 4 bits,iOS可转换为RGB Compressed PVRTC 4 bits。
- 及时回收资源:加载新的Prefab或贴图后,若不及时回收,它们会永驻内存,即使切换场景也不会销毁。应在物体不再使用或长时间不使用时,先将物体置空(null),然后调用Resources.UnloadUnusedAssets()释放内存。
- 优化图集:对于大量空白的图集贴图,可使用TexturePacker等工具进行优化,或考虑合并到其他图集中。
- AudioManager:音频管理器,其占用内存随音频文件数量增加而增大。
- AudioClip:音效及声音文件,是重点优化对象。播放时长较长的音乐文件可压缩为.mp3或.ogg格式,时长较短的音效文件可使用.wav 或.aiff格式。
- Cubemap:立方图纹理,常见于天空盒,目前较难找到优化方法。
- Mesh:模型网格,需检查是否存在重复资源,并尽量减少点面数。
Scene Memory部分
- Mesh:场景中使用的网格模型,要注意其点面数,能合并的mesh尽量合并。
Builtin Resources部分
这些是Unity的内部资源,对项目内存分析价值不大,暂不分析。
Profiler内存重点关注优化项目
- ManagedHeap.UsedSize:移动游戏建议不超过20MB。
- SerializedFile:异步加载(LoadFromCache、WWW等)时留下的序列化文件,需监视是否被卸载。
- WebStream:通过异步WWW下载的资源文件在内存中的解压版本,比SerializedFile大几倍或几十倍,当前项目中暂未出现。
- Texture2D:重点检查是否存在重复资源,以及超大内存的贴图是否需要压缩。
- AnimationClip:重点检查是否存在重复资源。
- Mesh:重点检查是否存在重复资源。
项目中可能遇到的问题
Device.Present问题
- GPU耗时问题:GPU的presentdevice操作非常耗时,通常出现在使用复杂shader的情况下。
- Vsync等待问题:GPU运行速度快,但因Vsync原因需等待较长时间;或者其他线程耗时过长,导致等待时间增加,如过量AssetBundle加载时容易出现该问题。
- Shader卡顿问题:Shader在runtime阶段(非预加载)可能会出现卡顿,如华为K3V2芯片。
- Debug API问题:StackTraceUtility.PostprocessStacktrace()和StackTraceUtility.ExtractStackTrace()一般由Debug.Log或类似API造成,游戏发布后需屏蔽Debug API。
Overhead问题
- 一般由Vsync导致。
- 通常出现在Android设备上。
GC.Collect问题
原因
- 代码分配内存过量(恶性)。
- 系统按一定时间间隔调用(良性)。
占用时间
- 与现有Garbage size相关。
- 与剩余内存使用颗粒相关(如场景物件过多、利用率低时,GC释放后需进行内存重排)。
GarbageCollectAssetsProfile问题
- 引擎执行UnloadUnusedAssets操作(该操作耗时,建议在切场景时进行)。
- 尽量避免使用Unity内建GUI,防止GUI.Repaint过渡GCAllow。
- 将
if(other.tag == a.tag)改为other.CompareTag(a.tag),因为other.tag会产生180B的GC Allow。 - 少用foreach,每次foreach会产生一个enumerator(约16B的内存分配),尽量使用for循环。
- 注意Lambda表达式的使用,不当使用可能导致内存泄漏。
其他建议
- 少用LINQ:部分功能在某些平台无法使用,且会分配大量GC Allow。
- 控制StartCoroutine次数:开启一个Coroutine(协程)至少分配37B的内存,Coroutine类的实例占21B,Enumerator占16B。
- 使用StringBuilder:替代字符串直接连接。
- 缓存组件:每次GetComponent会分配一定的GC Allow,每次Object.name会分配39B的堆内存。