Unity优化

Unity中性能优化的一些经验与总结(脚本优化篇)

Spread the love

最近一个星期,公司的项目正在做项目调优。

也在不断的尝试当中找到了一些优化代码性能的思路。

发现经常是在不经意的一些小地方,大大拉低了系统整体的性能。

 

在编写Unity程序的时候我们应该时刻养成较好的编码习惯,有意地去避免一些不必要的性能消耗是很重要的。

特别是在性能敏感的使用场景下:频繁调用的函数例如Update或者是处理长连接服务端发送过来的快照消息时尤其要注意。

 

本篇专门讲述代码部分的优化,之后的资源篇与渲染篇会有关于那两块的总结。

不过冯乐乐的文章已经很好的总结了渲染中种种需要的调优方式:【Unity技巧】Unity中的优化技术

冯乐乐写的文章真的都非常不错,佩服之余也希望自己也能成长为她一样。

 

C#性能调优相关

在项目调优的过程当中,我们发现集合操作常常是非常耗时的,特别是需要进行遍历或者进行排序的时候。

  • 我们在写程序的时候,经常会用List。其实很多不需要随机访问的情况下我们可以使用Queue或者Stack来代替List。前者O(1)的复杂度会比后者好很多。类似的,List的操作比Dictionary的操作要更廉价。
  • 尽量使用泛型容器避免不必要的装箱拆箱对于性能来说也会有所提升。对于频繁且大量的访问操作,装箱与拆箱会带来巨大的性能差距。
  • 在Dictionary中进行查找的时候,我们最好去查找已经重载了比较操作的自定义类型或者是基本类型,当使用基本类型时使用较小的类似int、枚举等类型而非string。由于C#自带的比较等操作的实现效率并不高,所以在较为敏感的场景下使用自定义类做索引需要注意。
  • 为容器初始化容量:当容量不足时扩容的代价是非常昂贵的,所以最好一开始就有一个大概的估计。
  • 平时要注意常用集合操作的复杂度,例如Add()在普通情况下是O(1),如果遇到扩容则会变为O(n),Remove()、Insert()、Contains()等操作为O(n),在使用时避免不必要的操作。RemoveAt也有O(n)复杂度且n=Count-index。建议从尾部移除。批量移除时使用RemoveRange提高移除效率。
  • 减少多余的访问,例如Dictionary的使用中我们常常会先用一次ContainsKey再进行访问,相当于访问了两次。使用TryGet替代会更好。
  • 减少创建冗余的集合,例如利用OrderBy后我们会获得一个新的集合,但是我们将这个集合创建成一个新的List或者别的集合时会有一定的消耗,例如ToList、ToDictionary同理。

当然在其他的一些方面C#也有很多的优化手段。

  • 考虑重写struct中自带的函数。就像前面所说的,系统自带的函数实际上性能非常低下,很多地方甚至使用了反射,对于性能敏感的代码来说是不能容忍的。
  • 考虑使用Cache。在很多时候我们必须使用一些性能较差的功能,例如反射。当我们需要经常性使用的时候考虑用Cache来空间换取时间。例如我想将字符串反射出一个类类型,那么我可以将该TypeCache到一个Dictionary中,以供下次直接进行查找而非再次反射。
  • 不要滥用静态对象:由于静态对象始终存在于内存当中,过度滥用的话容易出现占用内存过多的情况。当不再需要的时候,将静态大对象置空,保证GC能够正常进行。
  • 避免在循环或者是Update中进行字符串操作,能用变量保存下来就用变量保存下来,并且尽量使用StringBuilder而非操作符。
  • 可以使用for就不使用foreach,foreach产生的迭代器可以产生GC以影响性能。
  • 使用尾递归而非其他的递归,尾递归的性能好于头递归。

给大家推荐一本书:《Effective C#中文版——改善C#程序的50种方法》

C#其实与Java有许多的相通之处,即使是是优化上也有异曲同工之处。

Unity代码性能调优相关

  • 避免频繁地SetActive操作,由于SetActive本身也有一定消耗,而且一些特殊的组件类似于:Text、MaskGraphic等,在OnEnable与OnDisable时有较为明显的消耗,建议在频繁进行SetActive的操作时采用先移出屏幕等待一段时间之后再将物体隐藏,保证不过度频繁地将物体重复Active或者Inactive。而在一些不适用于移出屏幕的物体,类似于UI,考虑减少该类操作,或者使用将Text设为空或者透明度设为0来避免调用OnEnable与OnDisable操作。
  • Transform的子类型过多时避免频繁地进行Transform操作,大量的子物体会带来大量的操作。
  • 在设置需要频繁使用的材质属性时,尝试将字符串转换为数字并且保存下来,调用时使用数字进行查找属性,也是减少字符串索引的方法。
  • 支持分级Log(自定义logger),避免大量且频繁的Log,在构建时屏蔽log。
  • 避免频繁地Find、GetComponent。
  • 避免创建大量不必要的碰撞盒。
  • 使用gameObject.CompareTag(“XXX”)而非gameObject.tag,后者会产生额外的内存与性能消耗。
  • 使用消耗更小的运算:例如1/5使用1*0.2来代替、用位运算代替简单乘除。(不过在性能并不是非常非常敏感的地方可以忽略位运算这一条,毕竟可读性还是要的。
  • 使用内建的常量,例如Vector3.zero等等,避免频繁创建相同的对象。
  • 自己可以写一个工具类,使用Update替代简单的协程,例如等待若干秒等等,可以消除创建协程的消耗。

Unity代码内存调优相关

内存的优化最多的还是在资源以及渲染上面,所以在代码这方面可能会相对欠缺一些,因为C#本身托管的属性,所以其代码内存泄漏的情况非常的少。

  • 在UI中使用池时考虑使用分级机制:在池中越不频繁出现的UI就应该更快地被销毁以释放内存,而频繁出现的UI则等待更长的时间。
  • 使用延迟加载的方式,一些不常用的资源在第一次使用的时候再进行加载。
  • 在使用池进行内存管理时特别要注意,当一个物体你不再需要的时候,请将其置为null。例如你封装了一个数组,其中装入了许多的对象,当你移除一个对象的时候或许并没有将其真正置空,而是移动了目前指向的位置,那么你本应移除的对象就泄漏了出去。
  • 不要主动调用GC。而是通过良好的代码,即时去除不需要的对象的引用可以更好地让我们使用GC来回收垃圾。

IL2Cpp代码性能优化

这一块我并不是很熟悉,而是参考了网络上的文章,记录下来给大家参考一下。

网络性能调优

  • 注意消息发送的细粒度。避免发送相同的数据。我们在做强实时游戏中,需要将场景快照发送到客户端,场景快照的大小尽量减小,而将一些消息封装为别的类型信息,当改变的时候再进行发送可以减少流量消耗的同时提高游戏性能。
  • 拒绝发送冗余数据:例如在Moba游戏中,迷雾之中的角色同步消息我们不需要了解,就不需要实时发送消息。
  • 将protobuf的反序列化放到另一条线程中进行处理,可以很大程度上减轻CPU的压力。

GC调优

2月9日补上。发现减少GC对于游戏体验也是非常重要,当进行GC的时候游戏会出现明显的卡顿,GC触发的方式无非就是因为堆内存不足,所以我们就需要减少内存的开支。

原网址:【Unity】减少GC回收

  • 减少字符串直接使用操作符连接,因为当连接操作符的时候,之前使用的短字符串都会成为垃圾,一旦数量一大就容易触发GC。
  • 减少使用foreach,上面也有提到,迭代器会带来垃圾。据说迭代一次可以带来24Bytes的垃圾。
  • 不要访问GameObject上的tag,上面也提到过了。
  • 使用对象池,依旧提过了。
  • 减少使用Linq。
  • 缓存组件,例如GetComponent、GameObject.name都会产生堆内存。
  • 开启协程会分配一定内存,可以利用工具类放到update中执行。
  • 减少不必要的new消耗。
  • 不要滥用Lambda表达式。
  • 对于数据类型使用结构体而不是类。

暂时就总结到这边了,越是普通的地方就越容易被忽略。但是要知道百分之八十的性能消耗其实来自于百分之二十的代码——没错,就是经典的二八原则。

代码优化是一件让人烦恼的事情,但是优化后所带来的成就感不是普通的编程工作能得到的。

希望我的总结能够帮助大家在优化过程中少走弯路,如果我的文章中有所疏漏或者错误的话欢迎大家指正。

2 thoughts on “Unity中性能优化的一些经验与总结(脚本优化篇)

  1. 使用内建的常量,例如Vector3.zero等等,避免频繁创建相同的对象。
    关于这句话 ,unity的struct到了c++ 使用的是str.memoryCopy.也就说struct赋值一定会使用新空间.我记得是这样把

    1. C#的值类型肯定是进行拷贝操作的,C#中的struct与class区别还是很大的,一个是按值传递,而另一个是引用传递。

发表评论

电子邮件地址不会被公开。 必填项已用*标注