目录
1、客户端性能
1.1、客户端性能基础知识
2、客户端性能工具介绍与环境搭建
2.1.1、perfdog的使用
2.1.2、renderdoc的使用
1、客户端性能
1.1、客户端性能基础知识
客户端性能知识这里对2D和3D类游戏进行展开进行,讲述的有内存、CPU、GPU、帧率这几个模块内容。其中不管是2D还是3D类游戏,都一定会包含有图片、字体、音频这些资源。2D类游戏主要的资源是图片,所以对于渲染这块的内容核心在于CPU的draw call方面;而3D类游戏包含的资源很多,有模型、物理组件、光影效果、网格、shader、材质纹理等内容,所以3D类游戏在渲染这块的内容核心包括CPU的draw call以及GPU的渲染计算方面。下面分别对影响内存、CPU、GPU性能方面进行讲解。
影响内存的因素
对于一款游戏,基本的可以分为脚本和资源两部分,而脚本通常会包括代码和导表数据。对于代码中,没有及时回收的无用变量,会占用了内存空间,慢慢积累后也就是成了我们所说的内存泄漏。导表数据也是一个我们需要关注的点,游戏里面有某些表格往往会比较大,有的甚至达到几M的数据,而在一个场景中,可能要加载多个表格的数据,这就需要将这些需要用到的表格都加载进入到内存当中,但是实际上,当前场景所有到的数据往往仅是数据表格的一小部分数据而已,这就导致了有大量的表格数据在内存中是无用的,但是又必须要加载进去。所以,表格数据的优化也是一个比较重要的内容之一,常规的表格优化策略有:
- 通过功能数据放在独立的表格
- 去除表格中未被使用的冗余字段
- 对数据量大的表格进行分块处理,使用中间文件做索引,做到分块导表,按需加载
而对于资源内容,通用的资源有字体、音频、UI、纹理图片,这通用资源里面其实已经包含了2D的了,因为2D的资源也就是以上的这些内容。而3D的则还有网格、材质、着色器这些内容。下面针对这些影响因素逐个介绍。
字体,一款游戏中使用到的字体往往有多种,而一个字体小的有几兆,大的甚至十几兆,而游戏中加载字体都是全部加载进去且大部分是常驻内存的,并且游戏在开发期时,可能使用了多款字体,但是实际上游戏中使用的没有么多,导致字体在内存中占用了许多的内存。所以针对这点,我这里也拿出来提及一下,因为字体的加载大小几乎等同于字体实际的大小,可以说是冗余的字体和多种字体既占包体量也占内存。
音频,游戏音频也是会对内存造成一定的占用的,这点也同样需要在性能测试过程需要考虑的。游戏里面常用的音频有ogg格式的,mp3格式的,wav格式的以及wwise音频引擎的使用bnk格式作为音频资源。对于游戏音频,一种常规的优化策略是:
- 对于长时间的音乐,建议采用压缩格式的mp3
- 对于短时间的音效,建议采用非压缩给是wav
对于unity里面的音效设置,刚开始的音源文件可能是1.4Mb,如果采用的格式是加载时解压缩,那么最后的音源在内存中的大小将高达10.2Mb。所以音源的加载策略也是一个很重要的影响因素。一般的加载方式有加载时解压缩、压缩内存、流式处理。这几种方式各有优缺点,无非就是在速度与内存空间两个方面的取舍程度,需要项目里面根据实际的需求选择合适自己项目的方式,这里我就不给建议了。
UI,游戏UI界面往往容易造成的性能问题是卡顿、界面打开慢等,导致UI的过度绘制。在游戏中,UI的销毁处理一般有三种情况,一种是关闭UI后立即销毁,不做任何的缓存;一种是第一次打开UI后,对UI先进行缓存处理,关闭UI后,不销毁UI,下次打开速度快,等到切换场景时才销毁;第三种是关闭UI后,不销毁UI,直到内存达到预警值时再进行销毁;对于我们测试来说,需要了解UI在内存的处理方式后,还需要关注该种模式下是否存在掉帧、卡顿的情况,是否出现内存泄漏的情况。如果有,那么我们需要定位出来是那些UI导致的掉帧、卡顿或者内存泄漏。常规的做法有:
- 叫程序在客户端打印每个场景界面的UI列表log,列出UI的 名字、尺寸、大小,测试人员可以收集前后的UI数据定位出问题所在
- 使用renderdoc查看某界面的资源加载情况,确认哪个资源,那一帧导致的掉帧、卡顿问题,renderdoc的使用方法请参考:renderdoc的使用
- 使用prefdog等工具查看内存是否存在泄漏情况,并确认是否是UI重复打开关闭导致的
纹理图片,纹理对于内存的消耗是巨大的,一个2048*2048的纹理,采用32为颜色深度编码的话,将消耗16M的内存,一个1024*1024的纹理也需要消耗4M的内存(大体计算方式:(大小)*(编码位数/8),即1024*1024*(32/8)byte =1024*4kb = 4Mb。
以上场景的纹理贴图中2048*2048规格的的图片占用内容会比较大,这些纹理贴图需要美术优化。
需确认纹理在512*512、1024*1024、2048*2048几种大小以及在dds、tga两种格式下的内存占用情况,目标如下:
纹理的处理可以经过压缩,不过压缩后,效果可能不是很理想。一种做法是在原图的基础上,将纹理的长宽分辨率比例放大一倍,然后在保持原有的压缩格式,这样压缩出来的结果还是要比原图要小一些。另外一种处理就是进行高低配分级处理,先设置好分级,针对中低档设备则进行纹理压缩,针对高端档设备,可以保持不压缩纹理。
3D类游戏的资源
网格,需要能够确定出场景模型的网格占用情况,对于unity3D类的模型中,有些模型没有勾选【Rig】下的Optimize GameObject(优化游戏对象)选项,这个选项可以把SceneManager中用于Skinning的节点都去掉,从而节省场景节点树更新以及查询的CPU消耗。
与此同时,网格文件Mesh里面会包含有大量的color数据,normal数据,tangent数据。这些数据也会对内存占用比较大。
材质贴图,3D的材质贴图主要是纹理贴图,纹理贴图中我们需要关注的有纹理格式,纹理的尺寸、mipmap功能,勾选了read&write功能等。这些都会影响到游戏内存的。
对于纹理格式,需要设备硬件支持,不过目前为止基本大部分设备都支持了各种格式的纹理了,不过可能还会遇到如下的两个问题:
纹理的尺寸问题,纹理的大小的计算和上面贴图所示的估算公式一致。
纹理的mipmap和read&write功能可以在unity中的纹理属性中看到是否有勾选。一般情况下模型的mipmap建议勾选,这对提高渲染效率,尽管勾选后,内存会增加1.33倍,但是渲染效率大大提高,还是值得的,但是UI的纹理建议不要勾选了,UI本身就渲染在屏幕上,渲染提高不大,反而会增加了内存;然后read&write选项建议不要勾选。
着色器(shader),shader作为能增强炫丽的渲染效果的脚本,很多时候程序、TA为了增强效果,加了好多shader,这个可能会对内存有较大的开销。如下图是我自制的一个很小的单场景demo下shader所占用的内存空间就多大5.1Mb了。那在一款成型的3D类游戏中,可能会有更多的shader内存了。
对于内存影响因素的内容这里就讲述上述的这几个,当然还是有其他的影响因素,但是相对没那么重要的,核心的或者说常规的就是上面的这些因素,如需了解其他的因素的话,请自行去搜索学习,下面讲CPU的性能影响因素。
影响CPU性能的因素
游戏中影响CPU性能除了本身游戏逻辑代码处理之外,CPU对于渲染指令的处理也是影响其性能的主要原因之一。主要就是这两块影响的,对于逻辑代码这块,主要就是编程规范和算法设计的不合理导致CPU出现性能压力。同时CPU在对图形渲染这块的处理也是常常会出现性能瓶颈问题的。在渲染这块中,draw call调用、Batches批次、UI调用、物理组件的处理以及GC调用都是比较影响CPU性能的,所以这四块的内容优化会直接影响到CPU的性能。所以这里也主要针对这四块进行说明。
Draw call
Draw call是CPU对底层图形接口的调用,是CPU提交数据 、指令、状态等内容给GPU的时候调用的,但由于GPU的计算速度往往比CPU调用draw call的快,如果CPU出现负载,那么会造成卡顿掉帧。优化的做法就是对同类资源进行合批渲染,静态资源看看能否进行合批,从而较少draw call次数。一般建议的draw call在100以内。
Draw call可以的查看可以在unity的性能分析器里面看,程序那边一般也都会在游戏中指令打印出来的,也可以通过指令查看。
Batches批次是合批,合批有静态合批与动态合批,合批的目的是减少draw call的调用,从而降低CPU的性能压力。静态合批是手动设置的,1、通过设置游戏中需要合批的模型资源,然后在游戏导出时,在Player setting里面设置static batching,这样设置后导出时unity就好帮我们合批了,不过这样会增大包体大小,但是会运行快一些;2、在unity里面直接勾选static属性,这样设置时,物体是在加载过程才进行合批,这样包体会更小,但是运行会慢一些,内存也会相应增大。动态合批是unity引擎自动帮我们合批的,我们可以不用理会。不过动态合批有许多的限制:
- 动态合批处理的物体顶点数量不超过900个,包括shader里面的顶点设置
- 如果GameObjects在Transform上包含镜像,则不会对其进行动态合批处理
- 使用不同的Material实例会导致GameObjects不能一起批处理,即使它们基本相同
- 带有光照贴图的GameObjects有额外的渲染器参数:保存光照贴图的索引和偏移/缩放。一般来说,动态光照贴图的GameObjects应指向完全相同的光照贴图位置才能被动态合批处理
静态合批有渲染上的优点,但是也有缺点,就是合批后,在运行过程中会增加内存的消耗,所以对于CPU与内存间的优化需要衡量两者间的短板所在,择优而选,而不能一昧的追求极端,反而会影响到了其他方面的性能。
在Unity中的Frame Debugger也可以抓帧查看详细的渲染与合批过程,在unity中,因为有Batch的存在,在一个Batch批次中,可能会有出现合批了几十个甚至上百个Draw Call。也就是说,Unity的Batch并不能实际减少Draw Call的数量,但是合批次后性能会有较大的提升。原因是Batch合批次后,尽管没有降低Draw Call的数量,但是会将能够合批次的数据一次性提交了,这样Draw Call里面的大头的数据被抽出来一次提交了,剩余的Draw Call在CPU提交的时候带宽压力就会小很多。大致可以理解为假设一个Draw Call是一个1K大小的文件,合批时将1千个1K大小文件里面90%大小的数据一次性提交了,剩余的文件数量经过不变,但是每个文件的数据量被抽减到了只剩下10%的数据,也就是说处理器原本需要处理的1K大小的文件,变成了只需要处理0.1K大小的数据,压力自然就提升了。
UI调用也挺会增加CPU的负担的,不合理的UI布局和调用会导致CPU过渡绘制和调用,增加CPU性能压力。一般的优化策略有
- 同一个UI界面的图片尽量合到一张图集里面去,减少draw call
- 通用的图片放到一张共享图集里面去
- 不同格式的图片放到不同的图集
- 减少图片的尺寸
- 合理使用UI层级,在一个UI工程里面,尽量不要使用复杂的UI结构
- 动态UI与静态UI进行分离,减少不必要的绘制
- 透明UI要慎用,可能会增加重绘
物理组件,物理组件的大量使用也会增加CPU的计算压力,对于物理组件的优化策略有:
- 尽量不使用网格碰撞器
- 选择使用合适的Fixed Timestep
这个设置在edit-项目设置-时间-固定的时间步进里面设置,默认是0.02s,也就是20ms。
GC调用,尽管GC是用来处理优化内存的,但是频繁的调用也是会增加CPU的逻辑处理开销,因此对于CPU的优化,那么减少GC的调用触发也是一个操作。
GC被触发的条件有:
- 堆内存不足,自动触发GC调用
- 程序手动调用GC
问题一般会出现在程序手动频繁调用GC,同时也是一些编程不够规范引发了垃圾数据,是的系统堆内存过低而触发GC。
影响GPU性能的因素
对GPU性能的影响主要体现在渲染方面的压力,导致的结果有游戏掉帧、耗电量过高等表现。我们可以大致了知道GPU的功能就是计算,那么影响到GPU计算效率的无非就是两大方面:一是面数过多,像素计算复杂;另一个是GPU显存带宽。
所以针对上面的这两点,就可以有对应的优化策略。主要的优化策略有:
- 减少顶点数量
- 纹理图片尺寸不易过大
- 使用LOD技术
- 使用遮挡剔除
- 使用合图图集
- 大场景使用烘焙光影替代BPR光影
- 移动端类游戏,使用mobile版的shader
- UI元素不使用mipmap设置(合理使用mipmap)
对于客户端性能的基础知识这里就可以讲这么多,当然还有unity性能管线、Shader、纹理等方面的内容,由于时间关系这里先不写了。
后面有空再写一篇关于如何定位、优化性能问题。
2、客户端性能工具介绍与环境搭建
2.1.1、perfdog的使用
腾讯旗下的perfdog可以用于检测客户端帧率、CPU甚至GPU等数据情况,关于perfdog的使用请参考官网说明:https://perfdog.qq.com/
这里可以讲一下关于perfdog的用例设计,我给出几点总结:
- 用例设计时,尽量保持单一变量,从而使得检测数据更具有说服力
- 测试过程,每个步骤尽量在ferfdog中的timeline(时间轴)上面的tag编写清除,便于对比与定位问题
- 收集录制的文件命名尽量用下划线拼接,便于阅读,如:小米6x_主线
2.1.2、renderdoc的使用
Renderdoc这个工具可以抓帧定位资源渲染加载过程的问题,同时也可以找出被抓取的界面的的资源名字与大小,用于定位一些资源、渲染速度、加载过程的性能问题。关于renderdoc的使用,请参考附件;
1.3客户端性能测试点
客户端性能的测试点,不同项目功能不一样,不过可以抛砖引玉的给出一些思路,如下图所示给出一些常规的测试过程的几种情况。当然,具体的用例设计还是要在围绕下面的几种情况的同时,针对具体的功能进行细化。