记录部分Unity性能优化知识
1 GC优化
GC (Garbage Collector)负责自动管理内存。当堆上的某个对象从任何GC Roots(包括栈、静态变量等)出发都不可达时,该对象会被标记为垃圾。GC会在特定时机(如内存分配预算达到阈值)执行回收,并视情况重新整理(压缩)堆内存以释放空间。
然GC是一个极其消耗性能的工作,每次都需要遍历整个堆内存,因此我们要尽量避免GC。
如何避免频繁触发GC
——减少频繁分配内存
1 | void Update(){ |
像上面这种写法,每帧都在使用new进行内存分配,我们完全可以改成下面这种写法:
1 | private List<int> list = new List<int>(); |
这种写法只有在容器被创建或扩容时才会有堆分配,从而减少了垃圾的产生。
——运用对象池
在运行时大量对象的创建和销毁依然会引起GC问题,用对象池技术可以让对象复用而不是重复的创建和销毁。
——字符串
在C#中,String是引用类型,它的值是不可变的,一旦被初始化后就不能改变其内容,频繁的修改字符串建议使用StringBuilder。
——装箱
值类型转换为引用类型的过程称为装箱,装箱会产生GC。
——协程
避免yield return 0,因为会产生GC,因为int类型的0被装箱,而使用yield return null替代则不会产生装箱操作,还比如在协程中避免多次new同一个WaitForSeconds对象。
1 | while(!isComplete){ |
——Linq表达式
LINQ和正则表达式由于在后台会有装箱操作而产生垃圾,在有性能要求的时候最好不使用。
2 Draw Call优化
在Unity里,Draw Call指的是CPU发出的绘制请求,其中包含了绘制所需的所有信息,如纹理信息、着色器等。
Draw Call的合并条件:
- 材质必须相同
- 即使两个材质球的 Shader 代码一模一样,但只要它们是两个独立的材质实例(Instance),引擎通常也会认为它们是不同的状态,从而无法直接合批(除非使用了类似数据缓存的优化手段)
- 纹理必须相同
- 使用 Texture Atlas(纹理图集)。将多张小图拼成一张大图,让 A 和 B 共享这张大图,通过不同的 UV 坐标 来采样
- 网格(Mesh)的处理方式(因合批方案而异)
- 静态合批:不需要 Mesh 相同。引擎会把不同的 Mesh 合并成一个巨大的合并网格
- 动态合批:不需要 Mesh 相同,但对总顶点数/属性数量有严格限制(通常为了防止 CPU 计算坐标变换太慢)
- GPU Instancing:Mesh 必须完全相同。因为它是“一模多画”,GPU 内存里只存一份原始网格,通过 ID 去索引不同的位置数据
- 渲染队列和层级一致
- 如果两个可以合批的物体/UI被一个材质不同的物体挡住了,合批就会被打断
为什么我们要优化Draw Call?
在现代硬件中,GPU的处理能力通常很强,假设一个场景有2000个Draw Call,CPU可能需要花20ms才能把这些指令发完,而GPU画完它们只需要5ms。也就是说,Draw Call太多的后果是GPU大部分时间都在等CPU发指令,这时游戏帧率就会卡在CPU提交这一步。
Draw Call的优化手段:批处理技术。
三种主流合批方案:
- 静态合批(Static Batching)——给不动的东西用
- 适用于场景中位置固定、永不移动的物体
- 实现原理:在游戏运行前(或加载时),引擎将这些共享相同材质的静态物体网格(Mesh)合并成一个巨大的网格,并一次性存入显存
- 优点:渲染时只需要一个DrawCall即可绘制大量的物体
- 缺点:内存开销大。引擎为了减少 DrawCall,会把这100把椅子的顶点全部抓出来,在世界空间下重新计算位置,然后合并成一个巨大的、全新的网格。因此内存里会存储100把椅子的顶点数据
- 动态合批(Dynamic Batching)——给极小的东西用
- 适用于每一帧都在移动、旋转的小物体
- 实现原理:每一帧由 CPU 动态地将多个小物体的顶点变换到世界坐标空间,并组合成一个大的缓冲区(Vertex Buffer)发送给 GPU
- 缺点:
- 极度消耗 CPU。因为每一帧都要重新计算顶点坐标
- 而且通常有严格的顶点限制(如 Unity 中限制在 300 个顶点以内),如果模型太复杂,CPU计算的时间可能比节省下来的DrawCall还贵
- GPU实例化(GPU Instancing)——给大量相同的东西用
- 当你有大量网格完全相同(Mesh 相同)但实例数据不同(坐标、旋转、缩放、颜色不同)的物体时,CPU 只向 GPU 发送一份网格数据和一串“属性列表”。GPU 根据这一份网格模板,按照列表里的坐标画出成千上万个分身
- 操作:在材质球(Material)的底部勾选 Enable GPU Instancing
- 优点:
- 内存极省:显存里只存一份网格
- 效率极高:CPU 几乎不参与坐标变换,全部交给 GPU 完成
- 缺点:
- 要求所有实例必须用同一个Mesh
- 要求使用支持 Instancing 的 Shader
- SRP Batcher——现代管线的核心
- 如果你使用 URP 管线,系统会默认开启 SRP Batcher
- 它不合并网格,而是通过减少 渲染状态(SetPass Call) 的切换开销来提速。只要物体使用相同的 Shader 变体,哪怕材质参数不同,它也能显著降低 CPU 压力