总纲
性能优化无外乎两个关键概念:时间和空间。
时间,对应着速度、性能、流畅度等等。空间,对应着内存、显存等等,计算机是有限的资源,得合理利用。
工具篇
兵马未动粮草先行。工欲善其事必先利其器。性能优化,最重要的是能找到瓶颈。连瓶颈都不能找到,谈何优化?
内置工具:
内置工具包括以下几种:Profiler,FrameDebugger,
Profiler
CPU剖析
CPU性能主要衡量游戏中花费的时间,即运行速度。CPU的主要功能是解释计算机指令以及处理计算机软件中的数据。影响运行速度的性能指标包括:CPU的工作频率、Cache容量、指令系统和逻辑结构等。
当有大量逻辑需要处理,或陷入大量循环时,CPU会明显增高,即,运行速度会下降,表现为来不及处理后续任务。
CPU运算速度,直接表现是一个计算任务的执行时间。
虽然现在处理器都具有多核结构,即多CPU,能并行处理任务,但在单核机器上,CPU往往会成为游戏重要的瓶颈部分。
在渲染中,引擎部分往往还涉及到一个缓存命中率性能问题,这个问题很细微,主要是缓存的数据不匹配问题,通常通过提高Cache容量即可解决。
Unity的CPU剖析器,可探测:
- 渲染:直接表现为帧率。帧率高且稳是最终追求。渲染部分的瓶颈,通常在渲染计算,即坐标转换、顶点裁切等计算,其中最为明显的是矩阵运算和四元组运算。这里通常涉及的直接概念是DrawCall。DrawCall是将CPU为GPU准备的数据经由总线发给GPU的过程。
- 脚本:脚本方法调用。即Update、FixedUpdate等消耗的时间。主要涉及:游戏逻辑、输入响应、AI等
- 物理:物料模拟等计算所消耗的时间。
- 动画:动画蒙皮、顶点动画。
- 垃圾回收:数据标记并回收。
- 垂直同步:垂直同步是用于等待帧,以防止前一帧绘制完成后一帧未准备完全时,画面撕裂效果。
- 全局光照:
- UI:
- 其他:
在Unity中,采用线程执行任务:主线程、渲染线程和Workers。Works中,又分了Load、Script等。
在Unity中,当出现CPU瓶颈时,通常从脚本、物理着手。
内存
内存Unity内部保留了内存池,以避免频繁询问操作系统内存。内存是与CPU沟通的桥梁。内存用于暂存CPU的运算数据以及与硬盘等外部存储器交换的数据。即,内存暂存输入数据和结果。
当内存明显增大时,很大部分原因是:内存分配过多。最大的直接原因是大量new对象,并且不释放。对于Unity而言,则通常是资源加载过多。
内存的优化手段主要着手点是资源量。资源包括两种:内部资源和外部资源。内部资源是在程序内部new的资源,如变量、常量等。外部资源则包括:纹理、材质、网格、音频、视频、动画、资源包等。
- 总分配内存
- Texture内存
- Mesh内存
- Material 数量
- 对象数量:创建的对象总数。如果此数字随时间而增高,则游戏会创建一些永不销毁的对象。
- 总GC已分配内存
- GC已分配
在Unity中,出现内存瓶颈时,通常采用:压缩、卸载、内存池等方式。
GPU:硬件渲染计算
GPU与CPU类似,主要衡量标准是频率、缓存容量。主要用于将显示信息进行转换并向显示器提供扫描信号以显示内容,承担显示图形的任务。
GPU的数据来源于内存,即CPU计算之后,将需要处理的顶点信息通过DRAW CALL传递给GPU,GPU再进行处理。GPU基于大吞吐量设计,有很多算术计算单元可快速完成计算和很少的Cache。缓存服务于GPU线程。
GPU是渲染管线中非常重要的部分,主要处理两种Shader:Vertex Shader和Fragment Shader。主要涉及的计算包括:顶点变换、光照、贴图、混合等。
Unity的GPU Profiler能探测:
- Opaque:不透明物体
- Transparent:透明物体
- Shadow/Depth:阴影和深度
- Defferred PrePass:
- Defferred Lighting:延迟光照
- PostProcess:后期处理
- Other
GPU的瓶颈,主要是与渲染有关,往往从顶点数、透明物体渲染、阴影、后期处理、动态光等方面着手。
Render 渲染
Unity的渲染Profiler主要涉及:
- Batches:批次
- SetPass Calls:
- Triangles:面数。
- Vertices:顶点数。
Audio & Video:音频与视频
Unity的Audio Profiler主要涉及:
- Playing Sources:当前播放的音频源数
- Audio Voices:播放的
- Total Audio CPU:处理Audio所用CPU
- Total Audio Memory:音频资源所占内存空间。
音频处理通常在子线程中。音频一般不易出现问题,当出现瓶颈时,可从同时播放的音频源数量、混响效果、音频文件大小几个方面着手。
Physics
Unity的物理Profiler主要涉及:
- Active Dynamic
- Active Kinematic
- Static Colliders
- Rigidbody
- Triggle Overlaps
- Active Constraints
- Contacts
Global Illumination 全局光照
全局光照主要涉及的就是间接光照, 是各种光的混合计算。主要涉及:
-
Total CPU:计算GI使用的CPU。
-
Light Probe:光探针,是让动态物体从场景接收间接光的有效方法。是一种廉价的间接光计算方式。这里是探针更新所需时间。
-
Setup:在安装阶段所花时间。
-
Environment:处理环境光照的时间。
-
Input Lighting:处理输入照明所花时间。
-
Systems Time:更新系统所需时间。
-
Solve Tasks:运行光能传递解算任务所花时间
-
Dynamic Objects:更新动态对象所花时间
-
Other Commands:处理其他命令所花时间
-
Blocked Command Write:在阻塞状态下所花费的时间。
外部工具
特定平台的工具:
- iOS:Instruments和XCode Frame Debugger
- Android:SnapDragon Profiler
- IntelCPU/GPU:VTune和Intel GPA
- PS4:Razor和VR Trace
- XBox:Pix
Memory Profiler
可识别重复资产
RenderDoc
VS Graphics Debugging
附:优化执行标准
贴图:
- 贴图大小控制为2的幂,以避免GfxDriver对其进行分割为2份
- 不超过1024*1024
- 压缩,去除ALPHA通道
- 合理合为贴图集,合并载入
- 不同设备使用不同纹理贴图
模型:
- 缩减面数
- 使用LOD/AutoLOD
- 使用的Material数量
- 如果不通过脚本修改Mesh,则禁用其Read/Write Enabled标志
- 禁用非角色模型自动添加的Animator组件。
- 尽量压缩网格
- 如果无需阴影,则禁用之
动画:
- 动画压缩
- 减少骨骼
声音:
- 压缩
- 尽量使用单声道、低比特率的音频
场景:
- 尽量使用static物体和光
- 如果可以避免,则去掉动态光。
- 尽量使用统一缩放
- 尽量使用Prefab
- 尽量降低Camera视野距离
- 尽量使用遮挡剔除,剔除无用渲染对象。
- 场景顶点数保持3M以下,移动设备上少于1M。
- 尽可能共用材质。
- 如果不需要,则去掉Fog。
- 物体挂载的组件如无用则删除
- 限制Light Probe数量
- 在光照变化大的地方(如屋前)使用LightProbe,其他地方尽量避免使用LightProbe
- 尽量使LightProbe稀疏分布,并在地面上
- 使用的Material尽量启用GPU Instancing。
资源:
- 按需加载,不用则卸载,通用则按需常驻
- 减少常驻内存资源量
物理:
- 合理降低碰撞精度
- 最小化碰撞检测请求
- 合理设置碰撞矩阵,去掉无用碰撞层
- 尽量避免使用MeshCollider
AI:
- 合理设置AI更新频率
代码:
- 避免新对象的频繁创建。
- 使用内存池复用内存
- 预先获取并引用。暂存中间值,再使用。
- Component
- Obect
- 删掉无用/空消息函数
- 多使用for,尽量避免foreach循环。foreach通过迭代器遍历,如果是值类型,则会产生装箱拆箱操作。
- 无用对象,及时销毁或SetActive(false)
- 尽量使用IL2CPP
- 注意,禁用对象,不会停止协程
- 尽量使用协程,并且避免让协程阻塞
- Collection的Clear清除值,但并不释放内存。尽量重用它们。
- 在性能敏感的地方,尽量最小化使用闭包。
- 尽量避免装箱拆箱(值类型和引用互相类型转换)
- 使用正则表达式的实例.Match函数,而非静态函数
- 尽量使用ScriptableObject进行数据配置
- 多使用数字hash或ID
- 多使用非分配内存版本的函数(函数调用过程中,不产生临时内存)实现相同功能
- 尽量在密集循环将对象与null比较
- 计算中,优先计算简单数学运算(整数>浮点>向量>四元组>矩阵)
- HTML格式颜色和本机Color互换,使用ColorUtility
- 尽量消除Find、FindObjectByType
- 使用Camera.main时,考虑在Start将其暂存。Camera.main在内部使用了FindObjectWithTag
- 尽量使用type[x][y] 而非type[x,y],如果可以用一维数组代替,则使用一维数组
- 尝试使用粒子系统池
- 考虑使用UpdateManager,集中管理Update进行分发,这样通过代理按需调用Update可减少很多不必要的调用
- 考虑使用快速插入/删除的数据结构替代delegate
- 尽量为每个Thread任务设定相应的优先级
- 多使用Culling Group组织群集对象移动
- 注意虚函数和接口方法不能内联,因此注意尽量降低方法调用的开销
- const可在编译时内联,会将引用替换成值,因此不适用于大对象。在const不适合的地方,使用static only。
Shader:
- 尽量使用Mobile里的Shader
- 尽量使用贴图混合替代多通道计算
- 多使用half/fixed
- 尽量避免pow,sin,cos,tan,log等复杂运算函数