参考链接:https://docs.unity3d.com/Manual/BestPracticeUnderstandingPerformanceInUnity4-1.html?_ga=2.107957826.1120751560.1556075409-1373067411.1554687911
在Unity中,托管堆扩充其大小比其缩小要容易得多。此外Unity的垃圾回收机制倾向于碎片化内存空间,这种机制导致难以缩小托管堆的大小。
- 托管堆的运作和托管堆的扩充
在Unity中托管堆就是把这段内存托管给Mono 和IL2CPP(准确来说应该是Mono VM和IL2CPP VM)。所有的非空引用类型和装箱的值类型都分配在托管堆中。
GC:Unity的GC使用的是Boehm GC algorithm。该算法有两个特点:1.会扫描整个堆,因此当堆变大时,性能会下降。2.已分配的对象不会重新定位,这以为着两个对象中间的空余空间会碎片化,下图。
GC会在两种情况下触发:1.定时触发(不同平台不同)。2.当剩余的空间不足以分配时(如下图)。
这意味着,即使碎片的空间总和足够大,但没有一个碎片可以装得下,最终也不能分配内存。
这时托管堆就会扩充其大小,不同平台扩充的大小不同,Unity平台一般扩充为原来的两倍。
2.堆扩充带来的问题
- Unity不会经常释放掉扩充的空间,即使有很多空间都没有使用,以备不时之需。
- 在大多数平台,Unity最终会返还这部分空间给操作系统,然而返还的耗时不能保证。
- 托管堆使用的地址空间不会返还给操作系统。
3.减少GC一些简单方法
- 不要在频繁调用的地方分配内存
List<float> m_NearestNeighbors = new List<float>();//缓存起来
void Update() {
m_NearestNeighbors.Clear();
findDistancesToNearestNeighbors(NearestNeighbors);
m_NearestNeighbors.Sort();
// … use the sorted list somehow …
}
- 匿名函数和闭包
(1)在C#中,函数时引用类型,不管是匿名函数还是已定义的函数,作为参数传入时,都会分配堆内存。
(2)当匿名函数中使用外部变量形成闭包时,C#会生成一个匿名函数的类,该类包含外部变量的。匿名函数作为参数传入Sort时,如下代码,会实例化该匿名函数的类,里面包含一个int。因为是对象,所以会分配堆内存。
List<float> listOfNumbers = createListOfRandomNumbers();
int desiredDivisor = getDesiredDivisor();
listOfNumbers.Sort( (x, y) =>
(int)x.CompareTo((int)(y/desiredDivisor)) //调用了外部变量,
);
(3)所有的匿名函数在IL2CPP都会分配堆内存,而Mono不会。
- 减少装箱——值类型隐式转换为引用类型时
int x = 1;
object y = new object();
y.Equals(x);
- 字典和枚举
当使用dic.add(key,value),会调用Object.getHashCode(Object),此时Enum是值类型,会导致装箱。
enum MyEnum { a, b, c };
var myDictionary = new Dictionary<MyEnum, object>();
myDictionary.Add(MyEnum.a, new object());
解决:定一个实现IEqualityComparer接口的类,作为Dictionary的比较器。
public class MyEnumComparer : IEqualityComparer<MyEnum> {
public bool Equals(MyEnum x, MyEnum y) {
return x == y;
}
public int GetHashCode(MyEnum x) {
return (int)x;
}
}
static MyEnumComparer myEnumComparer = new MyEnumComparer();
...
Dictionary<MyEnum, string> dict = new Dictionary<MyEnum, string>(myEnumComparer);
- 不使用foreach循环
(1)foreach循环每次在循环结束的时候,由于Enumerator(值类型)实现了IDisposable接口,所以会导致一次装箱。
(2)通过Enumerator的方法调用遍历要比普通for循环要慢
int accum = 0;
foreach(int x in myList) {
accum += x;
}
- UnityAPI 的数组
mesh.vertices会新分配内存,所以应该缓存起来,而不是放到循环里。
var vertices = mesh.vertices;
for(int i = 0; i < vertices.Length; i++)
{
float x, y, z;
x = vertices[i].x;
y = vertices[i].y;
z = vertices[i].z;
// ...
DoSomething(x, y, z);
}
- 空数组的重复利用
当函数要返回一个空数组时,应使用一个单例数组长度为0的空数组,而不是每次都重复创建一个空数组。