Unity 开发中常见的 C# 性能问题
for VS. foreach
老版本的 Unity (测试用的是 Unity 5.0.2f1) foreach 是有大概 40B 的 GC,所以,项目重遍历 IList、ArrayList 和 Dictionary 时都不要用 foreach。
使用 for 替代,或者使用如下写法:
List 的非 foreach 遍历
1
2
3
4
5List<int> data = new List<int>();
var e = data.GetEnumerator();
while(e.MoveNext())
{
}Dictionary 的非 foreach 遍历
1
2
3
4
5
6var enumerator = dictionary.GetEnumerator();
while (enumerator.MoveNext())
{
var element = enumerator.Current;
element.Value.DoSomethine();
}注意:Unity 5.5 已修复此问题。
字符串的拼接
连接次数只有几次(10 以内),此时应该直接用 +
号连接,不产生 GC。实际
上,编译器已经做了优化。
其余的使用 StringBuilder, StringBuilder 内部 Buffer 的缺省值为 16,
按 StringBuilder 的使用场景, Buffer 肯定得重新分配。经验值一般用 256 作
为 Buffer 的初值。当然,如果能计算出最终生成字符串长度的话,则应该按这
个值来设定 Buffer 的初值。使用 new StringBuilder(256) 就将 Buffer 的初始
长度设为了 256。
Struct VS. Class
Struct 在栈中不产生 GC, Class 在堆中,会产生 GC。
对 Struct 的结点修改时,修改完以后记得重新赋值。 因为 Struct 赋值是 copy
而不是引用,修改完以后,以前的不生效。
使用情景:
- 堆栈的空间有限,对于大量的逻辑的对象,创建类要比创建结构好一些。
- 结构表示轻量对象,并且结构的成本较低,适合处理大量短暂的对象。
- 在表现抽象和多级别的对象层次时,类是最好的选择。
- 大多数情况下该类型只是一些数据,且数据的类型为值类型时,结构是最佳的选择。
数组、ArrayList 和 List 的区别?
- 数组:内存中是连续存储的,索引速度非常快,赋值与修改元素也很简单。但不利于动态扩展以及移动。因为数组的缺点,就产生了 ArrayList。
- ArrayList:使用该类时必须进行引用,同时继承了 IList 接口,提供了数据存储和检索,ArrayList 对象的大小动态伸缩,支持不同类型的结点。ArrayList 虽然很完美,但结点类型是 Object,故不是类型安全的,也可能发生装箱和拆箱操作,带来很大的性能耗损。
- List 是泛型接口,规避了 ArrayList 的两个问题。
Enum
Enum 会产生 GC 的两种情况:
- 把枚举当 TKey 使用。也就是以枚举作 Dictionary 的键。
- 把枚举转成 string 使用。
把枚举作为 Dictionary 的键会产生 GC,是因为将 enum 值类型装换成 object 引用类型会产生装箱。
Dictionary 本质上,比较键的方法是使用EqualComparer<T>.Default
,然后调用GetHashCode()
以找到正确的存储桶,并Equals
比较存储桶中是否有我们要寻找的值。详细解释请看 http://stackoverflow.com/a/26281533 。
闭包
1 | using UnityEngine; |
上面的代码会有 112B 的 GC。
使用闭包的时候,涉及到大约 112B 的 GC,随着闭包引用内容的增加而增大。看自己的需求,如果函数调用频繁的话,要考虑是否不使用闭包来实现。
委托
1 | public void TestDelegate1(String param) |
TestCall1()
调用有 GC 问题,而 TestCall2()
调用没有。
1 | using System.Collections; |
在 Unity3D 2017.4.0 上测试,TestCall1()
和 TestCall3()
的调用都有 104B 的 GC,而 TestCall2()
和 TestCall4
则没有 GC。