Unity性能优化(二)-GC优化

Unity性能优化(二)-GC优化

英文原文:https://unity3d.com/cn/learn/tutorials/topics/performance-optimization/optimizing-garbage-collection-unity-games web

垃圾回收(GC)简介

在GC(Garbage Collection)过程当中,垃圾回收器会检查堆(Heap)中的全部对象,搜索它们的引用,来判断这些对象是否还在做用域中。若是对象不在做用域中,它将被标记为须要删除。而后垃圾回收器会将这些被标记为须要删除的对象删除,收回它们所占用的内存空间。堆中的对象和代码中对象引用越多,垃圾回收过程当中要进行的操做就越多,其开销也就越大。正则表达式

堆内存空间不足、系统定时自动回收垃圾以及强制GC都会触发垃圾回收操做。频繁的进行堆内存分配和释放将会致使频繁的GC。在Unity中,只有 值类型局部变量 被分配在栈(Stack)中,它们不会引发GC,其余全部类型的数据都分配在堆中,由GC系统进行回收。数组

由GC引发的性能问题 主要表现 为:帧率低、性能时好时坏以及断断续续的出现卡顿。缓存

下降由GC带来的影响

普遍的说,能够经过如下三种方式下降GC带来的影响:性能优化

  • 减小GC运行所需的时间 —— 减小游戏中的堆内存分配和对象引用数目。
  • 减小GC频率 —— 下降对内存的分配和释放频率。
  • 在非性能瓶颈时期主动触发GC —— 尝试测算GC和堆空间扩张的时间来使其在可预测、适宜的时间发生。

减小垃圾的产生

  • 使用缓存&不要在频繁调用的方法中分配堆内存。若是在须要频繁调用的方法中进行了堆内存分配,而且最后舍弃了这个新建的变量,这将会产生没必要要的垃圾,应该考虑在方法外建立对该变量的引用,而后重复利用它。对于没法缓存的对象,应该尽可能下降方法的执行次数,仅在须要时才去执行它。
  • 使用清空集合替代新建集合。建立新的集合对象将会在堆空间分配内存。若是代码中屡次建立新的集合,应该缓存对集合的引用,在下次须要使用新的集合时,将缓存的集合清空(Clear())而不是创建全新的集合。
  • 使用对象池。若是游戏中须要频繁的建立再销毁某类对象,那么为该类对象创建对象池。

引发堆内存分配的常见缘由

  • 字符串(String/string)。C#中的字符串是引用类型而不是值类型,而且它是不可变的,每次对字符串的修改,其实是建立了一个新的字符串对象。因为字符串在程序中很是经常使用,因此它可能积累出较多的垃圾。听从下面的一些简单规则,能够保证由字符串所产生的垃圾最少:
    • 减小没必要要的字符串建立,将反复使用的字符串存储到变量中。
    • 减小没必要要的字符串修改,使用StringBuilder替换须要常常修改的字符串。
    • 当Debug.Log()再也不使用后马上将其移除,每次打印日志至少会产生一个字符串。
  • Unity方法调用。某些Unity方法的调用会进行堆内存分配,应该谨慎调用那些不是本身写的代码,不管它是来自Unity仍是第三方插件。在Unity中,每次调用返回数组的方法或属性(访问器),都会建立一个新的数组再返回,字符串也是这样。减小此类调用或者使用替代方法,例如使用CompareTag()替换==操做。
  • 装箱。将值类型变量看成引用类型变量使用时会进行自动装箱。在装箱过程当中,Unity在堆内存中建立了一个临时的System.Object对象,使用后该对象做为垃圾被丢弃。装箱操做在代码中很是常见,容易积累垃圾。一个常见的会引发装箱的操做是将枚举类型做为Dictionary的键。
  • 协程。调用StartCoroutine()方法会产生少许的垃圾,由于Unity须要建立一些类的实例用于管理协程。应该提早启动那些须要在高性能要求时期运行的协程,并当心处理可能引发延迟调用的嵌套协程。协程中的yield语句自己不须要进行堆内存分配,但由它所返回的值可能须要分配堆内存。一个常见的错误用法是在yield语句中使用new屡次返回相同的值,例如yield return new WaitForSeconds(1f);,应该将WaitForSeconds(1f)对象缓存起来才对。
  • foreach循环。在比Unity 5.5更早版本的Unity中,每次foreach循环都会产生垃圾,由于该语句引起了装箱操做。在Unity 5.5中,这个问题被修复了。若是没办法将Unity升级到5.5或更高版本,那么使用for或者while替代foreach。
  • 方法引用。在Unity中,不管是对具名方法的引用仍是对匿名方法的引用,都是引用类型变量,它们会引起堆内存分配。将匿名方法转换成闭包会明显的增长内存占用和堆内存分配次数。
  • LINQ和正则表达式。LINQ和正则表达式都会因引起装箱操做而产生垃圾,最好不要使用它们。

优化代码结构使GC带来的影响最小化

不良的代码结构会增长垃圾回收器的工做量。好比,让垃圾回收器去检查本来不须要检查的对象。结构体(struct)是值类型的变量,但若是一个结构体中包含了引用类型的变量,那么垃圾回收器就必须去检查整个结构体。若是系统中有一个包含大量此类结构体的数组,那将会为垃圾回收器增长不少工做量。另外一种状况就是 包含太多没必要要的对象引用。当垃圾回收器为堆中的对象查找引用时,它必须在代码中查找当前对系统中的每一个对象的引用。代码中对对象的引用越少,垃圾回收器要作的工做就越少。闭包

手动进行强制GC

若是知道堆内存中有再也不使用的已分配空间(例如加载资源所产生的垃圾)而且当前系统对性能的需求不苛刻(例如正在显示加载画面),则能够经过下面的代码强制垃圾回收器进行GC:svg

System.GC.Collect();