Unity Mono托管内存优化

引子

优化Mono的托管内存
Mono的托管内存的优化主要是代码的优化,以下部分是我在网上收集的资料:

内容

1.尽量不要动态的Instantiate和Destroy Object,使用Object Pool。看看我之前写的一篇文章就基本懂了!

2.尽量不要再Update函数中做复杂计算,如有需要,可以隔N帧计算一次。

3.不要动态的产生字符串,如Debug.Log(“boo” + “hoo”),尽量预先创建好这些字符串资源。

4.Cache一些东西,在update里面尽量避免search,如GameObject.FindWithTag(“”)、GetComponent这样的调用,可以在Start中预先存起来。

5.尽量减少函数调用栈,用x = (x > 0 ? x : -x);代替x = Mathf.Abs(x)

6.下面的代码是几个GC“噩梦”。 String的相加操作,会频繁申请内存并释放,导致gc频繁,使用System.Text.StringBuilder代替

1
2
3
4
5
6
7
8
void ConcatExample(intArray: int[]) {  
var line = intArray[0].ToString();
for (i = 1; i < intArray.Length; i++) {
line += ", " + intArray[i].ToString();

}
return line;
}

7.在函数中动态new array,最好将一个array、传进函数里修改

8.Update处理可改为每5帧处理一次:

1
void Update() { DoSomeThing(); }
1
void Update() { if(Time.frameCount % 5 == 0) { DoSomeThing();}}

9.定时重复处理用 InvokeRepeating 函数实现 比如,启动0.5秒后每隔1秒执行一次 DoSomeThing 函数:

1
void Start() { InvokeRepeating("DoSomeThing", 0.5, 1.0); }

10.优化 Update, FixedUpdate, LateUpdate 等每帧处理的函数 函数里面的变量尽量在头部声明。比如:

1
void Update() { var pos: Vector3 = transform.position; }

可改为

1
private var pos: Vector3; void Update(){ pos = transform.position; }

11.主动回收垃圾,给某个 GameObject 绑上以下的代码:

1
void Update() { if(Time.frameCount % 50 == 0) { System.GC.Collect(); } }

12.优化数学计算,比如:如果可以避免使用浮点型(float),尽量使用整形(int),尽量少用复杂的数学函数比如 Sin 和 Cos 等等。

13.减少固定增量时间,将固定增量时间值设定在0.04-0.067区间(即,每秒15-25帧)。您可以通过Edit->Project Settings->Time来改变这个值。这样做降低了FixedUpdate函数被调用的频率以及物理引擎执行碰撞检测与刚体更新的频率。如果您使用了较低的固定增量时间,并且在主角身上使用了刚体部件,那么您可以启用插值办法来平滑刚体组件。

14.减少GetComponent的调用,使用 GetComponent或内置组件访问器会产生明显的开销。您可以通过一次获取组件的引用来避免开销,并将该引用分配给一个变量(有时称为”缓存”的引用)。例如,如果您使用如下的代码:

1
2
3
void Update () {  
transform.Translate(0, 1, 0);
}
1
2
3
4
5
6
7
var myTransform : Transform;  
void Awake () {
myTransform = transform;
}
void Update () {
myTransform.Translate(0, 1, 0);
}

15.避免分配内存,您应该避免分配新对象,除非你真的需要,因为他们不再在使用时,会增加垃圾回收系统的开销。您可以经常重复使用数组和其他对象,而不是分配新的数组或对象。这样做好处则是尽量减少垃圾的回收工作。同时,在某些可能的情况下,您也可以使用结构(struct)来代替类(class)。这是因为,结构变量主要存放在栈区而非堆区。因为栈的分配较快,并且不调用垃圾回收操作,所以当结构变量比较小时可以提升程序的运行性能。但是当结构体较大时,虽然它仍可避免分配/回收的开销,而它由于”传值”操作也会导致单独的开销,实际上它可能比等效对象类的效率还要低。

16.使用内置数组,内置数组是非常快的。ArrayList或Array类很容易使用,你能轻易添加元件。但是他们有完全不同的速度。 内置数组有固定长度,并且大多时候你会事先知道最大长度然后填充它。内置数组最好的一点是他们直接嵌入结构数据类型在一个紧密的缓存里,而不需要任何额外 类型信息或其他开销。因此,在缓存中遍历它是非常容易的,因为每个元素都是对齐的。

1
2
3
4
5
6
private var positions : Vector3[];
void Awake () {
positions = new Vector3[100];
for (var i=0;i<100;i++)
positions[i] = Vector3.zero;
}

17.使用静态类型

使用静态类型替代动态类型。Unity使用一种技术叫做类型推理的技术来自动转换JavaScript为静态类型脚本。
var foo = 5;
上面例子中的foo将自动被推断为一个整数值。因此,Unity可能使用大量的编辑时间进行优化,而不使用耗时的动态名称变量查找等。这就是为什么Unity的JavaScript执行平均速度是其他JavaScript的20倍的原因之一。
唯一的问题是有时不是所有的东西都能做类型推断,Unity将会为这些变量重新使用动态类型。通过这样,编写JavaScript代码很简单,但也会使代码运行速度变慢。
看个例子:

1
2
3
4
5
void Start ()  
{
var foo = GetComponent(MyScript);
foo.DoSomething();
}

这里foo将是动态类型,因此呼叫函数DoSomething必须要较长的时间,因为foo的类型未知,它必须弄明白是否支持DoSomething函数,如果支持,调用函数。

1
2
3
4
5
void Start ()  
{
MyScript foo = GetComponent(MyScript);
foo.DoSomething();
}

这里我们强制foo为指定类型,你将获得更好的性能。

18.使用#pragma strict

现在问题是,你通常不会意识到你在使用动态类型。#pragma strict可以解决这个问题!简单的添加#pragma strict在脚本顶部,之后Unity将禁用脚本的动态类型,强制你使用静态类型。如果有一个类型未知,Unity将报告编译错误。下面,foo将在编 译时报错:

1
2
3
4
5
6
#pragma strict  
void Start ()
{
var foo = GetComponent(MyScript);
foo.DoSomething();
}

19.缓存组件查找

另一个优化是组件缓存。这种优化需要一些代码并且不是总有必要。但是如果你的代码真的很大,并且你需要尽可能的性能提升,它会是很好的优化。
当你通过GetComponent获取一个组件或一个变量时,Unity必须从游戏物体里找到正确的组件。这时你便能通过一个缓存组件引用到一个私有变量。
将:

1
2
3
4
5
6
7
8
9
10
11
12
13
void Update () {  
transform.Translate(0, 0, 5);
}

//转换为:

private var myTransform : Transform;
void Awake () {
myTransform = transform;
}
void Update () {
myTransform.Translate(0, 0, 5);
}

后面的代码运行较快,因为Unity不用在每一帧寻找变换组件。同样,支持脚本组件。你可以使用GetComponent获取组件或其他快捷属性。

20.如果没有必要不要调用函数

最简单,最好的优化是执行最少的工作。如,当一个敌人在远处时,让他处于睡眠状态,大多时候是可行的。直到玩家靠近,可以这样处理:

1
2
3
4
5
6
void Update ()  
{
// Early out if the player is too far away. if (Vector3.Distance(transform.position, target.position) > 100)
return;
perform real work work...
}

这 并不是很好的方法,虽然Unity不得不在每一帧访问update函数。更好的方法是禁用这个行为直到玩家靠近。有3中方法做这个:使用 OnBecameVisible和OnBecameInvisible。这些调用与渲染系统相联系。一旦摄像机看到物体,OnBecameVisible 将被调用,不看他时,OnBecameInvisible被调用。这有时很有用。但是对于AI来讲通常是没有用的,因为你背转敌人,敌人就变成不可用了。

1
2
3
4
5
6
7
8
function OnBecameVisible () {  
enabled = true;
}

function OnBecameInvisible ()
{
enabled = false;
}

21.使用触发器。一个简单的球形触发器能引发惊人效果。你可以调用OnTriggerEnter/Exit,当进入你想要的作用范围。

1
2
3
4
5
6
7
8
9
10
11
void OnTriggerEnter (Collider c)  
{
if (c.CompareTag("Player"))
enabled = true;
}

void OnTriggerExit (Collider c)
{
if (c.CompareTag("Player"))
enabled = false;
}

22.使用协同程序。Update的问题是他在每帧都发生。很可能只需要5秒钟检查一次玩家的距离。这可以节约大量的处理周期。