Unity | 性能优化
目录
一、总结Unity开发中耗时高的操作
1.资源及UI
2.场景设置
3.代码
二、堆内存
1.累积分析
(1)常见的会引起堆内存累积的Unity API:
(2)常见的会引起堆内存累积的System操作
(3)其他
2.泄露分析
三、CPU
1.瓶颈函数优化
(1)明显造成耗时的函数
(2)稍明显耗时函数
2.高频函数优化
(1)对于多个高频Update
(2)针对低频逻辑,轮循式改为事件队列
(3)针对高频逻辑,将高频访问的属性分离到单独的Array中
3.IL2CPP
一、总结Unity开发中耗时高的操作
1.资源及UI
- 一般来说,纹理尺寸越大,占用的内存也就越大,一般情况我们推荐纹理尺寸为 512512,如果 512512 显示效果已经够用,那么就不要用 1024*1024 的纹理,因为后者的内存占用是前者的 4 倍。
- 文本使用 Outline 效果会增加 4 倍的顶点数,造成较高的重建开销,可尝试用 shadow 替代。
- 图片RGB32、RGB24 等非压缩格式的纹理占用内存较大,建议尽可能使用硬件支持的压缩纹理格式。
- 图片Wrapmode 使用了 Repeat 模式,容易导致贴图边缘出现杂色。
- 音频源文件为双声道且没有开启Force To Mono。在Android设备上,双声道音频意义不大,但是会占用双倍内存,建议使用单声道。
- 音频文件Streaming 选项开启后,音频加载方式变为边播放边读取,会明显降低内存占用,一般建议背景音乐使用这种方式。
- Read/Write 选项启用后,将会允许从脚本来访问网格数据,同时会产生网格数据的副本,占用额外内存,等同于一个网格数据会有接近2倍的内存消耗。但是对于需要使用StaticBatchingUtility.Combine进行合批的Mesh,以及部分Unity版本中粒子系统里使用到的Mesh,仍需要开启Mesh的Read/Write选项。
- Mipmap开启后,内存会是未开启 Mipmap 的 1.33 倍,因为 Mipmap 会生成一组长宽依次减少一倍的纹理序列,一直生成到 1*1。 Mipmap 提升 GPU 效率,一般用于 3D 场景或角色,UI 不建议开启。
- Tiled 模式的 Image 组件可能产生过多的面片。
- 不建议使用面片数超过 500 的网格。
- 粒子系统的Prewarm操作会在使用时的第一帧中造成相对集中的CPU耗时,很可能会造成运行时局部卡顿,建议考虑是否确实需要开启该选项,如果可以不用则将其关闭。
- 粒子系统:不建议使用尺寸过大(256)的纹理。
- 粒子系统建议不要开启Collison或Trigger功能,否则会有较高的物理开销。
- ApplyRootMotion勾选后,更新动画时会将Root的运动信息应用到Animator所在的 GameObject上,会有一定的开销。一般对于主玩家这样的物体,要用动画控制人物前进 的情况下需要开启ApplyRootMotion,否则建议在ImportSettings中将动画的"Root Motion"bake到曲线中,并取消勾选ApplyRootMotion。
- alpha=0, 且对应的Canvas Renderer组件没有开启Cull Transparent Mesh 的 Image 组件依然会参与渲染,建议进行排除。
- Canvas下UI节点太多,会导致加载及UI重建耗时。
2.场景设置
- 启用雾化效果会导致GPU消耗上升。
- 使用MeshCollider可能导致较高的计算开销;若希望降低物理碰撞计算负担,则可考虑采用更简单的碰撞体替代方案。
- 对于AudioListener组件,在常规情况下最多应配置一个实例。
- 在Unity中开启实时光照会对所有设置了cast shadow的对象产生实时阴影渲染;使用forward rendering对每一个开启阴影的实时光照都会带来显著的渲染消耗。通常建议场景中仅存在1个启用了shadow的实时光照;如需多个光照效果,请考虑关闭其他光照组件的阴影渲染设置。
- 静态物体无需挂Rigidbody,在常规情况下适用于动态物体或角色。
- 发行引擎具备代码剔除功能以减少包体大小;在IL2CPP环境下能优化Build时长。IL2CPP下启用Strip Engine Code后将iOS与Android下的ManagedStrippingLevel均设置为Medium或High级别。
- 启用Optimize Mesh Data操作会在Build过程中剔除未被当前Material使用的顶点属性(如法线、UV通道等),建议在项目初期就将其启用;需要注意的是,在运行时若更改Mesh所使用的Material可能会导致因新Material采用了之前被剔除属性而引发渲染错误的风险。
3.代码
Camera.main的内在实现每次都依赖于FindGameObjectsWithTag函数来进行查找。因此推荐使用缓存机制来存储FindGameObjectsWithTag函数的结果以减少其调用次数。
Log函数将负责为UnityEngine.Debug类分配堆内存,并导致CPU时间的消耗。建议对正式发布版本中的该功能进行优化。
在Update、LateUpdate和FixedUpdate方法中创建新的referenceType实例(例如new Class)可能会导致垃圾回收频率显著提升。可考虑采用valueType替代方案(例如new Struct),此外还可以考虑利用缓存池来重复利用已有的数据。
GetPixels()/GetPixels32() 会产生较高的耗时和堆内存分配,建议减少调用。
TextAsset/WWW.bytes 调用会产生较高的堆内存分配,建议减少调用。
matrices的采集进而导致新的materials实例的生成。建议采用MaterialPropertyBlock这一工具来进行属性的调整。
若容器未预先设定初始大小,在扩容过程中可能会导致系统堆内存溢出。建议开发者在创建新容器时应合理设置其初始大小。
OnGUI 方法会造成较多的堆内存分配,耗时也较高,不建议使用。
在Unity中使用GameObject.SendMessage的方式会带来显著的额外开销
使用String类型中的字符串修改函数可能会导致堆内存占用增加,并建议避免频繁操作以节省资源。
.tag 的使用会导致堆内存分配,建议使用 CompareTag 进行替换。
MonoBehavior组件中的无更新操作(即空Update)、延迟更新(LateUpdate)以及固定更新(FixedUpdate)方法,在执行过程中仍然会被调用。这会带来不必要的资源消耗,并对此类操作建议予以避免。
在Unity开发过程中采用foreach遍历特定容器时会带来较多的内存回收(GC)问题 建议可尝试采用其他类型的容器或者改用for循环进行遍历 本规则所涵盖的具体数据结构类型包括:SortedDictionary Hashtable BitArray Queue SortedList ArrayList 和Stack
调用该函数可能会导致运行时间较长以及较高的内存占用;不建议频繁调用该方法
该方法可能会导致较高的计算开销并占用过多的内存空间,并建议合理控制其调用次数。
Input.touches 会产生堆内存分配,建议减少调用。
二、堆内存
1.累积分析
堆内存累积会引起GC频繁触发。
(1)常见的会引起堆内存累积的Unity API:
- 记录日志信息
- 导入资产包的过程
- 创建游戏对象的过程
- 设置活跃游戏对象的操作
- 获取当前游戏对象名称的方法
- 挂载组件到目标物体上的操作
- 检查物理遮挡情况的过程
- 启动/暂停粒子系统的效果
(2)常见的会引起堆内存累积的System操作
- 字符串操作
- foreach遍历
- System.Delegate.Combine
(3)其他
- 子线程堆内存分配:引起随机卡顿
2.泄露分析
会影响堆内存峰值。
三、CPU
影响帧率和流畅度。
1.瓶颈函数优化
(1)明显造成耗时的函数
- CreateObject
- 调用Resources.Load方法和调用AssetBundle.LoadAsset方法
- 设置活跃对象为true(SetActiveObject(true))
- 添加组件(AddComponent或AttachComponent)
- 移动控制器(MoveController或NavigateController)
- 开始播放音频(StartPlayback或PlayAudio)
- 获取电池信息(GetBatteryLevel或CheckBatteryStatus)
- 检查互联网连接状态(CheckInternetConnectivity或IsOnline)
(2)稍明显耗时函数
- ParticleSystem.Stop/Play操作默认设置为true值,在执行过程中会导致性能开销。
- 输入触控(Input.touches)、网格顶点(Mesh.vertices)以及物理交点检测(Physics.Raycast)之间存在关联关系。
- 在Unity开发中,默认情况下会启用Camera.main组件来处理相机相关的各种事件和属性。
- 常见的数据存储结构包括列表(List)、字典(Dictionary)、以及数组(Array),每个类型都有其特定的数据组织方式和应用场景。
- 在C#编程中,默认情况下会使用Dictionary<T, K>类型的对象来实现键值对的存储功能。
- 非嵌入式界面设计模式更适合桌面应用的稳定性和用户体验优化需求。
- 公共属性public int MyProperty {get; set;}允许对属性进行读取和赋值操作,并且支持数据的有效维护与更新功能。
2.高频函数优化
(1)对于多个高频Update
- 不建议采用Coroutine来进行Update操作。
- 避免在Mono Behaviour Update框架内使用标准更新机制。
- 建议将DoUpdate操作嵌入到一个管理类的Mono Behaviour Update中进行处理。
启动一个Coroutine会稍显费时一些,在此之外的额外开销源于启动该Coroutine所涉及的更为复杂的内部管理流程。
(2)针对低频逻辑,轮循式改为事件队列
如给大量的image修改颜色,NGUI(轮循进行判断)耗时大于UGUI(队列)。
(3)针对高频逻辑,将高频访问的属性分离到单独的Array中
3.IL2CPP
在Unity开发中,默认情况下面对包含大量计算密集型任务的项目时,默认情况下默认情况下默认情况下默认情况下默认情况下默认情况下默认情况下默认情况下默认情况下默认情况下,默认情况下采用IL2CPP通常能带来明显的性能优化以应对这些计算密集型任务而不会影响其他功能由于其高效的中间层特性它能够有效平衡性能与开发便利性从而为开发者提供了一种高效可靠的技术解决方案
在Unity editor中,在ProjectSetting选项卡中的'Scripting Backend'设置项下可以选择(IL2CPP)以启用IL2CPP功能。
——以上二、三源自[逻辑代码的性能瓶颈定位与优化方法 UWA学堂 (uwa4d.com)]
