Unity学习-优化_卡顿原因定位以及优化方案

除了Unity的一些组件优化技巧之外,更多的细节处于代码层面上

最近学习优化,看到一篇文章,写的很详细,从底层原理到我们

的实际处理,都有一些非常好的建议,可以推荐给小伙伴们看看

https://www.jianshu.com/p/289de89a6609

===========如何定位程序的哪一个环节产生了过大的开销============

使用Uinty的Profiler工具,可以比较精准快速的定位程序的哪一个位置产生了大开销

首先在build setting里面勾选Autoconnect Profiler

然后在windows栏选择proflier即可

 

打开之后即可监控游戏每一帧的运行状态了,可以根据FPS和每一帧运行的时间判断游戏的卡顿程度

确定某一帧的占用率过高之后

 

点击波状图,就会暂停游戏

下方就会列出当前游戏运行的消耗详细信息了

跟进total栏的百分比,即可定位是哪个位置产生了最大的消耗,从而进行相应优化了

这个工具是用来定位优化点的,具体的优化方案可以参考下面的优化方式

由于篇幅有限,而且这个工具应该大部分人都知道而且会用,就不过多赘述了。

不明白的可以参阅这篇作者的 https://www.jianshu.com/p/a7cee5e548cf 

写的非常详细

==================代码层优化====================

一、内存管理

  1:GC原理

      C#的垃圾回收是自动托管的,垃圾回收系统也有一套生命周期和统计流程,下面就是关于GC的整体流程:

    1)一次GC的过程分为2个阶段:

                标记清除阶段,GC会假设堆中所有对象都可以被回收,然后找出不能回收的对象,打上清除标记,剩下的就是要被回收的了。找的过程就是检查对象  有没有被其他的对象引用的过程,如果这个对象在程序中没有被引用到,那么就会被打上清除的标记。

               重新地址排列阶段,标记清除的对象被清除之后,堆里面的空间就会变成不是连续的了,GC的第二部就会开始重新排列还存在的对象,使堆中的地址  分配变成连续的。

    2)整个.net的GC流程:  

                在进行.Net的GC阶段,是不止一次GC操作的,C#采用了分代算法,先对程序里面的内存进行分代管理,再根据不同的代,进行不同力度的清理。

这里面的生命周期为3代, 第0代是新创建的代码,在达到了0代集合的阈值之后,触发0代的GC,幸存的对象会进入1代集合, 同理,在1代集合达到了阈值    的时候,也会进行GC,但是这次GC是 0代和1代一起执行,以此类推,2代集合GC的时候也会进行1代 2代的CG操作。按消耗量的比例应该是 1:10:100

这种分代算法是基于 老的对象生命周期一般都比新的对象生命周期长,就像公司的员工一样,时间较长的员工公司看来一般都比新进来的员工稳定性要大。

      2:优化策略

    明白了大致GC的流程之后会发现,GC会消耗大量的CPU性能,因为这里面会经历很多次的运算以及遍历等

    接下来是弄清楚GC什么时候被触发,以及如何规避影响用户体验的GC操作

    何时会触发GC?
    三种情况下会触发GC:

    1:跟进分代算法,在容量达到阈值的时候会发生,

    2:GC会不时的自动运行(频率因平台而异)。

    3:手动强制调用GC

        大致的优化思路就是 降低每一次GC的运行时间(减少垃圾对象,使GC的过程中尽量少的遍历),降低GC的频率(降低触发GC机制的次数),在加载地图等需要用户等待的游戏流程里面主动GC。

     接下来就是跟进3种触发机制做我们代码上的优化了

  1)、尽量少new不必要的字段

 1 object obj = null;
 2 update()
 3 {
 4      object = somebody;
 5 }
 6 
 7 update()
 8 {
 9     object obj =somebody;
10 }

      上面的赋值方式只会在开辟一个内存空间,第二种会反复开辟内存空间,这些空间一般在一代GC里面就会被释放掉。属于最无用的代码方式(没必要的  情况下)。

  2).使用对象池

       对象池的使用会大大降低新内存空间的使用,他会在一个内存空间反复给新的值。

       3).尽量使用缓存机制,少使用Instantiate实例化新对象,因为这里Unity会初始化他身上的组件以及各种序列化的操作,各个物体会根据自身的组件,创建耗时都  不相同

   

       4).字符串的操作

            string的拼接操作是在内部重新new 一个新的出来,因为string在C#是不可变更到,所以在每一次的+= 就相当于new了一个新的字符串出来,如果出现比较频繁   的拼接操作,stringBuilder会比string 更好,但是string在对字符串的操作上会比stringBuider好,至于使用哪一种就看具体的需求了。

       5).在地图加载等需要等待的过程中主动进行GC。

避免内存消耗还有很多的方式,我也是刚刚开始比较全面的学习性能优化,在以上也是我在网上搜集了一些自己能理解的处理方式。

CPU性能管理:

  一、代码层:

     1、 尽量避免空的Update(),只要写了Update(),不管是不是空的,Unity都会去执行,这里会增加开销

     2、Find  getcomponent 等查找的方法,Unity都会去遍历场景对象和组件,在大型项目中,场景对象一旦变多就会产生很大的开销了,尽量在start里面调用而不  是update里面反复使用

         3、Update里面尽量少做遍历,一次UpDate 就会遍历一次,这是一个很大的开销了。

         4、在业务需求不影响的情况下,可以让Update里面的逻辑使用计时器,增加间隔,1秒调用一次,2秒调用一次等。

         5、在可以知道触发条件的情况下,尽量使用委托的方式处理触发效果,而不是一次次的遍历目标的触发状态

         6、和上面一样,尽量在设计代码的时候,使用观察者模式,理论上来说,游戏的大部分逻辑都可以使用触发后再执行,特别是UI。(在使用lua热更新的项目      中,大部分的游戏逻辑流程都是通过事件消息的触发来完成的,因为里面没有Uinty那么专业的生命周期)

        7、for 和 foreach 的取舍 :

    在固定长度或长度不需要计算的时候for循环效率高于foreach.

    在不确定长度,或计算长度有性能损耗的时候,用foreach比较方便

 二、渲染层:

      渲染层的优化包括 图集的结构设计、模型的处理、LOD、mipMap、烘焙等、阴影处理

     图集的结构设计是为了减少额外的draw call,尽量以模块划分,因为原则上每个模块之间的UI元素是不会互相耦合的。Unity的texture的大小是根据2的幂来计算到。如果你的图片真实大小是1025,那么他会创建一块2048的texture,也是浪费开销的行为。

模型的处理要结合LOD的使用,在固定视角、固定摄像机深度的游戏中,LOD的发挥效果不是很大,更多的是制作模型的时候就确定了模型精度

    LOD和MipMap的使用会带来很大的性能提升,但是会受项目影响,具体看项目而定

    烘焙就是在游戏发布之前,场景融合光照提前产生新的贴图,使这个场景不用进行动态光照计算 就可以达到光照的效果,降低了CPU计算的开销,但是对用户的体验肯定没有实时光照那么好,一般都是出现在手游中

阴影和烘焙的道理差不多,为了降低实时光照的计算量,可以将阴影设置为假阴影,阴影在是圆的情况,不会产生旋转的变化,同时因为烘焙的原因,光线没有入射角的变化,也不会产生阴影的投射角度变化,也是在优化性能上比较常用的方式

posted on 2019-04-19 17:06  年轮下的  阅读(8185)  评论(0编辑  收藏  举报

导航