作者:吴小含
导语 :Unity中频繁的垃圾回收往往是造成手游性能瓶颈的一大元凶,本文对常见的造成频繁垃圾回收的原因做一个扫描,让开发者在日常开发中可以有意识的避开这些问题。
- Struct 会分配在栈上,但是 Struct[] 会分配在堆里。
- GetType() 方法会产生 GC Alloc ,每次调用会产生 20 Bytes 的大小。
- Delegate 在赋值操作时,等同于一次 new Delegate。
- Delegate 在进行 =操作时,如果原本Delegate是 Simple Delegate则会有一次转换,并更新InvocationList。
- 由于Unity的GC是采用 Boehm GC 原理, 对象数量 > 引用关系复杂度 > 对象尺寸这一优化原则可以作为通用的优化原则。
- 利用数组对于GC是一个对象的原则,可以对原本储存在List中的对象进行一些属性分离来优化GC。
比如有100个对象存在一个List里,那GC的次数就是101次(List本身占一次)
代码语言:javascript复制class Foo
{
int a;
float b;
bool c;
string str;
}
但是如果将它们的属性进行拆分,将所有值属性放到一个struct里,如:
代码语言:javascript复制struct Foo_S
{
int a;
float b;
bool c;
}
Foo_S[] fooArray;
string[] strArray;
那这100个对象的GC次数就会降低为2次
- 单个的值属性 ValueType 分配是在栈上进行,但是ValueType[] 永远是在堆上。
- 避免在代码中频繁调用会分配内存的 accessors (如 .vertices/.normals/.uvs/.bones)。
- 避免频繁调用 Int.ToString() 及其它类型的衍生。
- 避免在 Update() 内使用 GameObject.Tag 和 GameObject.Name。
- 避免在 Update() 内 GetComponent() 和 GetComponentInChildren()。
- 避免在 Update() 内访问 animation 组件。
- 避免在 Update() 内 FindObjectsOfType()。
- 避免在 Update() 里赋值给栈上的数组,会触发堆内的反复分配。
- 避免频繁使用 Mathf.Max 等函数的数组版,重载中的多参数都会调到数组版。
- 避免频繁使用参数中带 params 修饰的函数。
- 在不需要时避免使用 GUILayout - OnGUI 时把 useGUILayout 关掉
- 避免使用 foreach,可以先拿到迭代器,然后进行迭代。
- 避免使用枚举或 struct 做 Key 进行字典查找,由于C# 在Dictionary 的主要接口 Add / ContainsKey / TryGetValue 在被调用时都需要对传进来的 TKey 调用默认的 EqualityComparer 来判断是否相等
this.comparer = comparer ?? EqualityComparer<TKey>.Default;
而 EqualityComparer 的内部有私有方法 CreateComparer() 来创建真实的 Comparer,内建类型(int/float 等等)已经实现了良好的 Equality 判断,而用户定义的 struct 则没有,每次调用 Add / ContainsKey / TryGetValue 等接口时,EqualityComparer 会为你创建一个Comparer。
要避免这一问题,可以人为指定对象的Comparer 或者对你的自建struct实现Equals() 和 GetHashCode() 等方法。