Unity学习-优化_卡顿原因定位以及优化方案
Posted leixuan111
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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操作
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计算的开销,但是对用户的体验肯定没有实时光照那么好,一般都是出现在手游中
阴影和烘焙的道理差不多,为了降低实时光照的计算量,可以将阴影设置为假阴影,阴影在是圆的情况,不会产生旋转的变化,同时因为烘焙的原因,光线没有入射角的变化,也不会产生阴影的投射角度变化,也是在优化性能上比较常用的方式
以上是关于Unity学习-优化_卡顿原因定位以及优化方案的主要内容,如果未能解决你的问题,请参考以下文章