unity性能优化-实际开发中需要注意的点
性能优化建议
在进行下面优化之前,建议先使用 Unity Profiler 定位真实的瓶颈。
优化核心原则:
能缓存的就缓存(变量、组件、材质)。
能不更新就不更新(降低 update 频率,使用事件驱动或Coroutine)。
避免在循环中分配内存(减少 new 关键字,使用对象池)。
1.复杂数学计算,比如mesh顶点数据的创建,大小和归一化属性都非常耗费 CPU(都涉及计算平方根)
可以使用对象池来空间换时间处理,涉及开根号的用平方比较

2.shader的合批处理


3.一些List,Vector不要用集合,而是指定它的大小

4.不要再update里面去大量调用Component(新手容易忽略重要)

5.优化字符串对比


6.常量的字符串写成类的成员变量

7.裁剪引擎,去掉音频

8.裁剪引擎,去掉物理,注意此时包括包围盒都不能使用

9.固定帧率,减少帧率

10.避免使用拆箱装箱
在C#中,装箱(Boxing)和拆箱(Unboxing)是指值类型与引用类型之间的转换。装箱是将值类型转换为对象类型(通常是将值类型包装在堆上),而拆箱则是从对象类型还原为值类型。
装箱(Boxing)开销
内存分配:
装箱过程中,会在堆上分配内存来存储值类型的数据。堆内存的分配通常比栈内存的分配慢,因为堆内存需要处理垃圾回收(Garbage Collection),而且堆内存分配涉及更多的管理操作。
数据复制:
装箱需要将值类型的数据从栈上复制到新分配的堆对象中。这涉及到数据的复制操作,增加了额外的开销。
垃圾回收:
装箱产生的堆对象最终会由垃圾回收器(GC)回收。垃圾回收是一个耗时的过程,会影响应用程序的性能。
拆箱(Unboxing)开销
类型检查:
拆箱过程中,需要进行类型检查,以确保对象确实是预期的值类型。这涉及到运行时的类型元数据访问和检查,增加了开销。
数据复制:
拆箱需要将堆上的数据复制回栈上。这同样涉及数据的复制操作,带来了额外的性能开销。
避免装箱和拆箱的方法
使用泛型:泛型可以减少装箱和拆箱的需要,因为泛型可以在编译时确定类型,而不需要在运行时进行类型转换,但是泛型带来另一个问题,即转换为il2cpp的时候,代码会膨胀,这个后面可以再说
C#
// 使用泛型避免装箱和拆箱 List<intnumbers = new List<int(); numbers.Add(42); int number = numbers[0]; |
避免频繁的值类型到引用类型的转换:在设计程序时,尽量减少值类型与引用类型之间的转换。如果可能,尽量使用值类型本身来进行计算和处理。
使用struct而不是类:在某些情况下,可以使用struct来代替类,因为struct是值类型,不需要装箱和拆箱。
避免接口调用:接口调用通常需要装箱,特别是在接口接受值类型参数时。尽量避免在接口方法中使用值类型参数
11.GameObject.Instantiate 和 SetParent
C# public class ESDRoadEdgePool { //public int amountToPool = 100; // 池中对象的数量 public int amountToPool = 25; pooledObjects = new List<GameObject>();
for (int i = 0; i < amountToPool; i++) { GameObject obj = Instantiate(objectToPool); // 实例化可以缩减 obj.SetActive(false); // 可以预先设置预制体的activeSelf obj.transform.parent = this.transform; // 可以合并到Instantiate中 pooledObjects.Add(obj); GameObject obj = Instantiate(objectToPool, transform); pooledObjects.Add(obj); } } |
12.嵌套循环中new Vector3等
C#
for(int i = 0; i < count; i++) { ... Vector2 p1l_uv = new Vector2(offest.x, uvStart / 10f); Vector2 p1r_uv = new Vector2(offest.y, uvStart / 10f); Vector2 p2l_uv = new Vector2(offest.x, (uvStart + distance) / 10); Vector2 p2r_uv = new Vector2(offest.y, (uvStart + distance) / 10); ... }
// 可以将对象创建挪到循环外进行复用
Vector2 p1l_uv = new Vector2(); Vector2 p1r_uv = new Vector2(); Vector2 p2l_uv = new Vector2(); Vector2 p2r_uv = new Vector2();
for(int i = 0; i < count; i++) { ... // 同时可以使用Vector3.Set来一次性对向量的每个分量进行赋值 p1l_uv.Set(offest.x, uvStart / 10f); p1r_uv.Set(offest.y, uvStart / 10f); p2l_uv.Set(offest.x, (uvStart + distance) / 10); p2r_uv.Set(offest.y, (uvStart + distance) / 10); ... }
|
13.每帧循环调用的计算逻辑,可以使用对象池替代
C#
for (int i = 0; i < len - 1; i++) { ... SquareCell cell = new SquareCell(); ... } // 加入对象池 public class SquareCellPool { public static SquareCell Allocate() { if (pool.TryDequeue(out var cell)) { totalCount--; return cell; } for (int i = 0; i < poolInterval; i++) { SquareCell newCell = new SquareCell(); pool.Enqueue(newCell); totalCount++; } if (totalCount > 65535) { Debug.LogError("Pool Out of Count!" + totalCount); return null; } cell = pool.Dequeue(); totalCount--; return cell; } } |
14.Shader中处理逻辑能不使用高精度可以使用低精度的


15.避免 Camera.main
在 Unity 中,Camera.main 实际上是调用了 GameObject.FindWithTag("MainCamera"),这是一个非常耗时的操作

发现unity文档里面,这块其实也是做了缓存gameobject

但是缓存肯定还是最快的,因为gameobject也有开销
本文档会陆续补充更新,优化无止境