常见的性能问题
VSS:
Virtual Set Size,虚拟耗用内存。它是一个进程能访问的所有内存空间地址的大小。这个大小包含了 一些没有驻留在RAM中的内存,就像mallocs已经被分配,但还没有写入。VSS很少用来测量程序的实际使 用内存。
RSS
Resident Set Size,实际使用物理内存。RSS是一个进程在RAM中实际持有的内存大小。RSS可能会 产生误导,因为它包含了所有该进程使用的共享库所占用的内存,一个被加载到内存中的共享库可能有很 多进程会使用它。RSS不是单个进程使用内存量的精确表示。
PSS:
Proportional Set Size,实际使用的物理内存,它与RSS不同,它会按比例分配共享库所占用的内存。 例如,如果有三个进程共享一个占30页内存控件的共享库,每个进程在计算PSS的时候,只会计算10页。 PSS是一个非常有用的数值,如果系统中所有的进程的PSS相加,所得和即为系统占用内存的总和。当一个 进程被杀死后,它所占用的共享库内存将会被其他仍然使用该共享库的进程所分担。在这种方式下,PSS 也会带来误导,因为当一个进程被杀后,PSS并不代表系统回收的内存大小。
USS:
Unique Set Size,进程独自占用的物理内存。这部分内存完全是该进程独享的。USS是一个非常有用 的数值,因为它表明了运行一个特定进程所需的真正内存成本。当一个进程被杀死,USS就是所有系统回 收的内存。USS是用来检查进程中是否有内存泄露的最好选择。
DrawCall
是CPU调用底层图形接口的操作。比如有上千个物体,每一个的渲染都需要去调用一次底层接口,而每一次的调用CPU都需要做很多工作,那么CPU必然不堪重负。
GC
是用来处理内存回收的,但是却增加了CPU的开销(GC一次开销可长可短,有时长达100ms)。因此对于GC的优化目标就是尽量少的触发GC。
Unity运行时的内存占用情况
内存标准
1.限定内存占用不超过200M(iPhone4接近容易Crash,低端机型),目前iPhone6上建议不要超过500M
2.项目中Reserved Total(总体分配)内存尽量控制在150M以内,如下 Texture 50M Mesh 20M AnimationClip 15M AudioClip 15M Mono堆内存 40M 字体等 10M。iPhone 6在此基础上可以乘以2.5
3.项目中尽量严格控制,即使在中高端机型可较大内存运行。
Mono内存管理策略
1.字符串连接处理,建议StringBuilder
2.尽量不使用foreach,Unity5.4以上解决了GC
3.问题不要频繁实例化和销毁对象,建议对象池管理
4.场景切换时,主动调用System.GC.Collect(),及时清理内存
Mono通过垃圾回收机制(Garbage Collect,简称GC)对内存进行管理。Mono内存分为两部分,已用内存(used)和堆内存(heap),已用内存指的是mono实际需要使用的内存,堆内存指的是mono向操作系统申请的内存,两者的差值就是mono的空闲内存。当mono需要分配内存时,会先查看空闲内存是否足够,如果足够的话,直接在空闲内存中分配,否则mono会进行一次GC以释放更多的空闲内存,如果GC之后仍然没有足够的空闲内存,则mono会向操作系统申请内存。
Mono内存泄漏分析
1.Mono通过引用关系,判断哪些内存不再使用
2.【Mono内存泄漏】对象已经不再使用,却未被GC回收Mono内存泄漏使空闲内存减少,GC频繁,mono堆不断扩大,最终导致游戏内存占用的增大
3.大部分mono内存泄漏的情况都是由于静态对象的引用引起
4.不再需要的对象将其引用设置为null,使其可以被GC及时回收
资源优化
Texture:分辨率大小、格式、Mipmap、Read/Write
1.Android 透明使用两张ETC1压缩(更高级一张ETC1上下Alpha分离),ETC2只支持OpenGL ES3.0设备,在不支持的设备上会自动转成RGBA32/ARGB32格式,对于RGBA Compressed ETC2 8bits纹理内存占用就增大4倍
2.iOS 透明使用一张RGBA PVRTC 4bits或RGBA16或两张RGB PVRTC Alpha分离,尽量不要使用RGBA32位
3.单张图最大不超过1024*1024
4.Mesh:SubMesh数量、顶点数量、压缩、Read/Write Mesh合并、不勾选Read/Write、大型场景使用LOD
5.AnimationClip:动画曲线数量、Constant曲线数量、Dense曲线数量、Stream曲线数量、以及动画事件数量
6.动画压缩:无用的曲线删除,调整float精度
7.AudioClp:格式、加载方式、时长以及频率 BGM背景音:ogg SFX声音特效:wav (有些项目BGM\SFX都用mp3)
8.Material:关联的Shader和Texture
9.Shader尽量使用mobile速配的
10.ETC2 的格式理论上只在OpenGL ES 3.0 的设备上被支持,而在不被支持的设备上则会内部自动转成 RGBA32/ARGB32的格式,这对于 RGBA Compressed ETC2 8bits 的纹理就是放大了 4 倍。因此,如果希望在 OpenGL ES 2.0 的设备上对透明材质进行压缩,那么可以尝试使用分离 Alpha 通道的方式,用两个 ETC1 来进行压缩(目前市场上大部分机型都支持ETC2)
DrawCall优化
1.先了解下DrawCall相关概念,便于优化
DrawCall是CPU调用底层图形接口的操作
DrawCall_Num = 25K * CPU_Frame * CPU_Percentage / FPS
DrawCall_Num : DrawCall数量(最大支持)
CPU_Frame : CPU 工作频率(GHz单位)
CPU_Percentage:CPU 分配在DrawCall这件事情上的时间率 (百分比)
FPS:希望的游戏帧率
2.DrawCall Batching(DC批处理)
Dynamic Batching(动态批处理)
Static Batching(静态批处理)
3.Bus总线带宽
CPU完成一次DrawCall,除了需要调用一次DrawCall的命令之外,还需要把内存中顶点数据、纹理贴图、shader参数通过bus总线拷贝到内存分配给GPU的显存之中,注意这是拷贝,不是指针传递,速度不快。项目中不会同时出现的资源不要打包到一起,保证单张合并纹理不大于1024*1024一般就不会有问题了。
CPU优化最直接的方法
1.VSync(垂直同步)是CPU优化最直接的方式(发热、耗电原因之一)
2.打开Edit-Project Settings-Quality找到V Sync Count
Don’t Sync 不同步
Every V Blank 每一个垂直同步
Every Second V Blank 每一秒垂直同步
通常我们选择Don’t Sync,同时Application.targetFrameRate设置目标FPS,让性能保持一个好的状态。注意选择其他项,Application.targetFrameRate设置不生效。
科普:VSync垂直同步又称场同步(Vertical Hold),垂直同步信号决定了CRT从屏幕顶部画到底部,再返回原始位置的时间。从CRT显示器的显示原理来看,单个像素组成了水平扫描线,水平扫描线在垂直方向的堆积形成了完整的画面。显示器的刷新率受显卡DAC控制,显卡DAC完成一帧的扫描后就会产生一个垂直同步信号(决定于屏幕的刷新率)。我们平时所说的打开垂直同步指的是将该信号送入显卡3D图形处理部分,从而让显卡在生成3D图形时受垂直同步信号的制约(注意是制约)。如果我们选择等待垂直同步信号(也就是我们平时所说的垂直同步打开),那么在游戏中或许强劲的显卡迅速的绘制完一屏的图像,但是没有垂直同步信号的到达,显卡无法绘制下一屏,只有等垂直同步的信号到达,才可以绘制。这样FPS自然要受到操作系统刷新率运行值的制约。而如果我们选择不等待垂直同步信号(也就是我们平时所说的关闭垂直同步),那么游戏中作完一屏画面,显卡和显示器无需等待垂直同步信号就可以开始下一屏图像的绘制,自然可以完全发挥显卡的实力。但是不要忘记,正是因为垂直同步的存在,才能使得游戏进程和显示器刷新率同步,使得画面更加平滑和稳定。取消了垂直同步信号,固然可以换来更快的帧率,但是在图像的连续性上势必打折扣。
GPU优化
GPU接收顶点数据作为输入传递给顶点着色器。顶点着色器的处理单元是顶点,输入进来的每个顶点都会调用一次顶点着色器。(顶点着色器本身不可以创建或销毁任何顶点,并无法得到顶点与顶点之间的关系)。顶点着色器是完全可编程的,它主要完成的工作有:坐标变换和逐顶点光照。 坐标变换:就是对顶点的坐标进行某种变换—把顶点坐标从模型空间转换到齐次裁剪空间。顶点的多少直接决定了三角形面的多少,也直接决定了GPU的渲染流水线的工作量,所以减少顶点数是一个比较重要的优化点。那么减少顶点怎么操作呢,又有哪些途径?
1.顶点着色器 优化基本几何体(模型减面减顶点) 使用LOD(Level of detail)技术 使用遮挡剔除(Occlusion culling)技术
2.中间操作 曲面细分着色器:是一个可选的着色器,主要用于细分图元 几何着色器:是一个可选的着色器,可用于执行逐图元的着色操作,或者被用于产生更多的图元。 裁剪:这一阶段是可配置的。目的是把那些不在视野内的顶点裁剪掉,并剔除某些三角形图元的面片。部分在视野内的图元需要做裁剪处理,在裁剪边缘产生新的顶点和三角形进行处理。 屏幕映射:这一阶段是可配置和编程的,负责把每个图元的坐标(三维坐标系)转换成屏幕坐标(二维坐标系)。
3.三角形设置:开始进入光栅化阶段,不再是数学上点了,而会把所有的点都映射到屏幕的具体像素坐标上,计算每条边上的像素坐标而得到三角形边界的表示方式即为三角形设置。 三角形遍历:这一阶段会检查每个像素是否被一个三角风格所覆盖。如果覆盖的话,就会生成一个片元(一个片元并不是真正意义上的像素,而是包含了很多状态的集合,这些状态用于计算每个像素的最终颜色。这些状态包括了屏幕坐标、深度信息,及从几何阶段输出的顶点信息,如法线和纹理坐标等。),这样一个查找哪些像素被三角形覆盖的过程就是三角形遍历。
4.片元着色器 尽量减少overdraw 减少实时光照 不要使用动态阴影 尽量使用简单的shader
片元着色器的输入就是上一阶段对顶点信息插值得到的结果,更具体点说,是根据从顶点着色器中输出的数据插值得到的。而这一阶段的输出是一个或者多个颜色值。这一阶段可以完成很多重要的渲染技术,如纹理采样,但是它的局限在于,它仅可以影响单个片元。片元着色器是比较花时间的,因为它是最终颜色的计算者,在某些情况下,例如复杂灯光环境下,片元着色器会出现GPU流水线主要的拖后腿的存在。为了让片元着色器的计算更加快,我们需要从很多方面进行提前的优化:片元着色器最容易拖后腿的情况就是,overdraw!和Android app的开发一样,就是同一个像素点绘制了多次,某些情况会造成计算力的浪费,增加耗电量。前面提到的遮挡剔除有减少overdraw非常有用。在PC上,资源无限,为了得到最准确的渲染结果,绘制顺序可能是从后往前绘制不透明物体,然后再绘制透明物体进行混合。但是在移动平台上,对于不透明物体,我们可以设置从前往后绘制,对于有透明通道的物体(很多UI纹理就是含有透明通道的),再设置从后往前绘制。unity中shader设置为“Geometry” 队列的对象总是从前往后绘制的,而其他固定队列(如“Transparent”“Overla”等)的物体,则都是从后往前绘制的。这意味这,我们可以尽量把物体的队列设置为“Geometry” 。对于GUI,尤其要注意和设计师商量,能用不透明的设计就用不透明的,对于粒子效果,也要注意不要引入透明值,多半情况下,移动平台的粒子效果透明值没有作用。
移动平台的最大敌人。一个场景里如果包含了三个逐像素的点光源,而且使用了逐像素的shader,那么很有可能将Draw Calls提高了三倍,同时也会增加overdraws。这是因为,对于逐像素的光源来说,被这些光源照亮的物体要被再渲染一次。更糟糕的是,无论是动态批处理还是动态批处理(其实文档中只提到了对动态批处理的影响,但不知道为什么实验结果对静态批处理也没有用),对于这种逐像素的pass都无法进行批处理,也就是说,它们会中断批处理。所以当你需要光照效果时,可以使用Lightmaps,提前烘焙好,提前把场景中的光照信息存储在一张光照纹理中,然后在运行时刻只需要根据纹理采样得到光照信息即可。当你需要金属性强(镜面)的效果,可以使用Light Probes。当你需要一束光的时候,可以使用体积光去模拟这个效果。
动态阴影很酷,但是对于片元着色器来说是灾难,阴影计算是三角投影计算,非常耗性能。如果想要阴影,可以使用
1.简单的使用一个带阴影的贴图
2.烘焙场景,拿到lightmaps
3.创建投影生成器的方法
4.使用ShadowMap的方法
1.建议尽量使用Unity自带mobile版本的(built-in)Shader,这些大大提高了顶点处理的性能。当然也会有一些限制。
2.自己写的shader请注意复杂操作符计算,类似pow,exp,log,cos,sin,tan等都是很耗时的计算,最多只用一次在每个像素点的计算,还有有些除法运算尽量该能乘法运算等。3.避免透明度测试着色器,因为这个非常耗时,使用透明度混合的版本来代替。
4.浮点类型运算:精度越低的浮点计算越快。
5.不要在Shader中添加不必要的Pass.
Unity优化工具
1.MAT(Memory Analyzer Tool) 需要导入HPROF文件再分析 只能查看java层的内存情况,看不到native堆的详情
2.Xcode Instrument工具 只能用于Mac,iOS 只能查看C++ 或 object C 的情况,看不到mono堆的详情
3.Unity自带Profiler 需要单独编译develop版本 在PC上执行,没法捕获真机数据 内存数据跟实际真机的数据差异很大、多的时候有几十M差距 只能看到最近一段时间的数据,看不到总体的详情
官方开源Memory Profiler
1.Unity5.3及其以上
2.使用IL2CPP,比如iOS平台
3.构建时开启Development Build
UWA和腾讯WeTest性能分析工具
一、程序方面
1、务必删除脚本中为空或不需要的默认方法;
2、只在一个脚本中使用OnGUI方法;
3、避免在OnGUI中对变量、方法进行更新、赋值,输出变量建议在Update内;
4、同一脚本中频繁使用的变量建议声明其为全局变量,脚本之间频繁调用的变量或方法建议声明为全局静态变量或方法;
5、不要去频繁获取组件,将其声明为全局变量;
6、数组、集合类元素优先使用Array,其次是List;
7、脚本在不使用时脚本禁用之,需要时再启用;
8、可以使用Ray来代替OnMouseXXX类方法;
9、需要隐藏/显示或实例化来回切换的对象,尽量不要使用SetActiveRecursively或active,而使用将对象远远移出相机范围和移回原位的做法;
10、尽量少用模运算和除法运算,比如a/5f,一定要写成a乘以0.2f。
11、对于不经常调用或更改的变量或方法建议使用Coroutines & Yield;
12、尽量直接声明脚本变量,而不使用GetComponent来获取脚本; iPhone
13、尽量使用整数数字,因为iPhone的浮点数计算能力很差;
14、不要使用原生的GUI方法;
15、不要实例化(Instantiate)对象,事先建好对象池,并使用Translate“生成”对象;
二、模型方面
1、合并使用同贴图的材质球,合并使用相同材质球的Mesh;
2、角色的贴图和材质球只要一个,若必须多个则将模型离分离为多个部分;骨骼系统不要使用太多;
3、当使用多角色时,将动画单独分离出来;
4、使用层距离来控制模型的显示距离;
5、阴影其实包含两方面阴暗和影子,建议使用实时影子时把阴暗效果烘焙出来,不要使用灯光来调节光线阴暗。
6、少用像素灯和使用像素灯的Shader;
7、如果硬阴影可以解决问题就不要用软阴影,并且使用不影响效果的低分辨率阴影;
8、实时阴影很耗性能,尽量减小产生阴影的距离;
9、允许的话在大场景中使用线性雾,这样可以使远距离对象或阴影不易察觉,因此可以通过减小相机和阴影距离来提高性能;
10、使用圆滑组来尽量减少模型的面数;
11、项目中如果没有灯光或对象在移动那么就不要使用实时灯光;
12、水面、镜子等实时反射/折射的效果单独放在Water图层中,并且根据其实时反射/折射的范围来调整; 13、碰撞对效率的影响很小,但碰撞还是建议使用Box、Sphere碰撞体;
14、建材质球时尽量考虑使用Substance;
15、尽量将所有的实时反射/折射(如水面、镜子、地板等等)都集合成一个面;
16、假反射/折射没有必要使用过大分辨率,一般6464就可以,不建议超过256256;
17、需要更改的材质球,建议实例化一个,而不是使用公共的材质球;
18、将不须射线或碰撞事件的对象置于IgnoreRaycast图层;
19、将水面或类似效果置于Water图层
20、将透明通道的对象置于TransparentFX图层;
21、养成良好的标签(Tags)、层次(Hieratchy)和图层(Layer)的条理化习惯,将不同的对象置于不同的标签或图层,三者有效的结合将很方便的按名称、类别和属性来查找;
22、通过Stats和Profile查看对效率影响最大的方面或对象,或者使用禁用部分模型的方式查看问题到底在哪儿;
23、使用遮挡剔除(Occlusion Culling)处理大场景,一种较原生的类LOD技术,并且能够“分割”作为整体的一个模型。
三、其它
场景中如果没有使用灯光和像素灯,就不要使用法线贴图,因为法线效果只有在有光源(Direct Light/Point Light/Angle Light/Pixel Light)的情况下才有效果。2.1渲染
1.不使用或少使用动态光照,使用light mapping和light probes(光照探头)
2.不使用法线贴图(或者只在主角身上使用),静态物体尽量将法线渲染到贴图
3.不适用稠密的粒子,尽量使用UV动画
4.不使用fog,使用渐变的面片(参考shadow gun)
5.不要使用alpha –test(如那些cutout shader),使用alpha-blend代替
6.使用尽量少的material,使用尽量少的pass和render次数,如反射、阴影这些操作
7.如有必要,使用Per-Layer Cull Distances,Camera.layerCullDistances
8.只使用mobile组里面的那些预置shader
9.使用occlusion culling
10.远处的物体绘制在skybox上
11.使用drawcall batching: 对于相邻动态物体:如果使用相同的shader,将texture合并 对于静态物体,batching要求很高,详见Unity Manual>Advanced>Optimizing Graphics Performance>Draw Call Batching 规格上限
- 每个模型只使用一个skinned mesh renderer
- 每个mesh不要超过3个material
- 骨骼数量不要超过30
- 面数在1500以内将得到好的效率
2.2物理
1.真实的物理(刚体)很消耗,不要轻易使用,尽量使用自己的代码模仿假的物理
2.对于投射物不要使用真实物理的碰撞和刚体,用自己的代码处理
3.不要使用mesh collider
4.在edit->project setting->time中调大FixedTimestep(真实物理的帧率)来减少cpu损耗
2.3脚本编写
1.尽量不要动态的instantiate和destroy object,使用object pool
2.尽量不要再update函数中做复杂计算,如有需要,可以隔N帧计算一次
3.不要动态的产生字符串,如Debug.Log("boo" + "hoo"),尽量预先创建好这些字符串资源
4.cache一些东西,在update里面尽量避免search,如GameObject.FindWithTag("")、GetComponent这样的调用,可以在start中预先存起来
5.尽量减少函数调用栈,用x = (x > 0 ? x : -x);代替x = Mathf.Abs(x)
6.String的相加操作,会频繁申请内存并释放,导致gc频繁,使用System.Text.StringBuilder代替
2.4 shader编写
1.数据类型 fixed / lowp - for colors, lighting information and normals, half / mediump - for texture UV coordinates, float / highp - avoid in pixel shaders, fine to use in vertex shader for position calculations.
2.少使用的函数:pow,sin,cos等
2.5 GUI
1.不要使用内置的onGUii函数处理gui,使用其他方案,如NGUI
2.贴图压缩格式:ios上尽量使用ASTC,Android上使用ETC