http://blog.csdn.net/u3d_ysj/article/details/48051011
前言:
对于GC,大家可能不陌生把,也就是内存回收。同时笔者在做自己的小游戏中发现很多细节都会影响GC,现在就给大家梳理下一些需要注意的地方。
进入主题:
在说CPU优化时,谈起GC是不是觉得很奇怪?其实笔者不这么觉得,虽然GC是用来处理内存回收的,但是却增加了CPU的开销。因此对于GC的优化目标就是尽量少的触发GC。
首先我们要知道所谓的GC是Mono运行时的机制,而非Unity3D游戏引擎的机制,所以GC也主要是针对Mono的对象来说的,而它管理的也是Mono的托管堆。 明白了这一点,你也就明白了GC不是用来处理引擎的Assets(贴图,音效,模型等等)的内存释放的,因为U3D引擎也有自己的内存堆而不是和Mono一起使用所谓的托管堆。
其次我们还要清楚什么东西会被分配到托管堆上?对,就是引用类型。引用类型包括:用户自定义的类,接口,委托,数组,字符串,Object.而值类型包括:几种基本数据类型(如:int,float,bool等),结构体,枚举,空类型。所以我们我们就应该尽量不要区使用那些引用类型。
那么GC什么时候会触发呢?两种情况:
- 当我们的堆的内存不足时,会自动调用GC来回收内存。
- 我们自己也可以手动的调用GC,用System.GC.Collect(),一般情况下,不建议手动去手动进行内存回收,因为 容易出现问题的!
因此为了达到优化CPU的目的,我们就不能频繁的触发GC。而上文也说了GC处理的是托管堆,而不是unity3d引擎的那些资源,其实说白了GC的优化也就是代码的优化。一下有事隔热你总结有些是参考了网上写的不错的文章。具体需要注意的地方如下:
注意:
- 字符串连接的处理。因为将两个字符串连接的过程,其实是生成一个新的字符串的过程。而之前的旧的字符串自然而然就成为了垃圾。而作为引用类型的字符串,其空间是在堆上分配的,被弃置的旧的字符串的空间会被GC当做垃圾回收,可以使用StringBuilder来解决(注意:C#没有StringBuffer,Java里才有!!String 在进行运算时(如赋值、拼接等)会产生一个新的实例,而 StringBuilder 则不会。所以在大量字符串拼接或频繁对某一字符串进行操作时最好使用 StringBuilder,不要使用 String)
- 尽量不要使用foreach,而是使用for。foreach其实会涉及到迭代器的使用,而据传说每一次循环所产生的迭代器会带来24 Bytes的垃圾。那么循环10次就是240Bytes。
- 不要直接访问gameobject的tag属性。比如if (go.tag == “human”)最好换成if (go.CompareTag (“human”))。因为访问物体的tag属性会在堆上额外的分配空间。如果在循环中这么处理,留下的垃圾就可想而知了。
- 不要实例化(Instantiate)和(Destory)对象,事先建好对象池,以实现空间的重复利用,大家可以看看我已经的ObjectPool这篇文章就懂了。
- 不使用LINQ命令,因为它们一般会分配中间缓器,而这很容易生成垃圾内。
- 避免分配内存,您应该避免分配新对象,除非你真的需要,因为他们不再在使用时,会增加垃圾回收系统的开销。您可以经常重复使用数组和其他对象,而不是分配新的数组或对象。这样做好处则是尽量减少垃圾的回收工作。同时,在某些可能的情况下,您也可以使用结构(struct)来代替类(class)。这是因为,结构变量主要存放在栈区而非堆区。因为栈的分配较快,并且不调用垃圾回收操作,所以当结构变量比较小时可以提升程序的运行性能。但是当结构体较大时,虽然它仍可避免分配/回收的开销,而它由于"传值"操作也会导致单独的开销,实际上它可能比等效对象类的效率还要低。他们有如下区别:
(1) 类在传递的时候,传递的内容是位于托管内存中的位置,结构体在传递的时候,传递的内容是位于程序堆栈区的内容。当类的传递对象修改时,将同时修改源对象,而结构体的传递对象修改时,不会对源对象产生影响。
(2)在一个类中,可以定义默认的、不带参数的构造函数,而在结构体中不能定义默认的、不带参数的构造函数。两者都可以定义带有参数的构造函数,通过这些参数给各自的字段赋值或初始化。
(3)类支持继承类,结构体不支持结构体但是可以继承接口。
(4)继承的基类不同,但都是从object派生的 。
(5)类成员在没有修饰符情况下默认私有(我记得Java好像是default,那是我还被老师提问来着,呵呵!),结构体默认public。详情如下
(如果一个类的成员没有任何权限修饰,那么它门就是缺省包访问权限,也就是同一个 包内其它类可以访问,但包外就不可以。对于同一个文件夹下的、没有用package的classes,Java会自动将这些classes初见为隶属于该目录的default下)。
7.尽量不要再Update函数中做复杂计算,如有需要,可以隔N帧计算一次,比如:
8.在使用数组或ArrayList对象时应当注意,缓存其长度: 9.定时重复调用可以使用InvokeRepeating函数实现,比如,启动1秒后每隔1秒执行一次 DoSomeThing 函数:
10.少使用临时变量,特别是在Update OnGUI等实时调用的函数中。
可改为:
11.Cache一些东西,在update里面尽量避免search,如GameObject.FindWithTag("")、GetComponent这样的调用,可以在Start中预先存起来。
12.尽量减少函数调用栈,用x = (x > 0 ? x : -x);代替x = Mathf.Abs(x).
13.不要使用SendMessage之类的方法,他比直接调用方法慢了100倍,你可以直接调用或通过C#的委托来实现。
14.使用JavaScript或Boo语言时,你最好确定变量的类型,不要使用动态类型,这样会降低效率,你可以在脚本开头使用#pragmastrict 来检查,这样当你编译你的游戏时就不会出现莫名其妙的错误了。比如:
这里foo将是动态类型,因此呼叫函数DoSomething必须要较长的时间,因为foo的类型未知,它必须弄明白是否支持DoSomething函数,如果支持,调用函数。
这里我们强制foo为指定类型,你将获得更好的性能。
15.优化数学运算,尽量避免使用float,而使用int,特别是在手机游戏中,尽量少用复杂的数学函数,比如sin,cos等函数。改除法/为乘法,例如:使用x*0.5f而不是 x/2.0f 。
16.压缩 Mesh 。导入 3D 模型之后,在不影响显示效果的前提下,最好开 Mesh Compression。 Off, Low, Medium, High 这几个选项,可酌情选取。对于单个Mesh最好使用一个材质。
17.删除空的Update方法。当通过Assets目录创建新的脚本时,脚本里会包括一个Update方法,当你不使用时删除它。
18.使用内置数组,内置数组是非常快的。ArrayList或Array类很容易使用,你能轻易添加元件。但是他们有完全不同的速度。 内置数组有固定长度,并且大多时候你会事先知道最大长度然后填充它。内置数组最好的一点是他们直接嵌入结构数据类型在一个紧密的缓存里,而不需要任何额外 类型信息或其他开销。因此,在缓存中遍历它是非常容易的,因为每个元素都是对齐的。比如: Vector3.zero;
19.不要使用原生的GUI方法,用NGUI代替最好!
20.需要隐藏/显示或实例化来回切换的对象,尽量不要使用SetActiveRecursively或active,而使用将对象远远移出相机范围和移回原位的做法或者使用OnBecameVisible()和OnBecameVisible();
21.只在一个脚本中使用OnGUI方法;
22.对于方法的参数的优化:善于使用ref关键字。值类型的参数,是通过将实参的值复制到形参,来实现按值传递到方法,也就是我们通常说的按值传递。复制嘛,总会让人感觉很笨重。比如Matrix4x4这样比较复杂的值类型,如果直接复制一份新的,反而不如将值类型的引用传递给方法作为参数。
..........
最后的最后:
感觉CPU的GC Script的优化方面会很多,暂时先暂时现总结这么点,之后我会不断补充的,如有更好的优化方面,可以在下面留言哦,谢谢咯。