性能优化问题的本质

  • 慢与快的问题

  • 前提

    • 稳定性:不能因优化造成稳定性变差

    • 兼容性:不能因优化导致兼容性变差

    • 性价比:优化要有度,考虑成本与复杂度

性能优化的流程

  1. 发现问题(什么平台、什么操作系统、什么情况下出现问题,一般问题还是特例问题等)
  2. 定位问题(什么地方造成的性能问题,我们要用什么工具、什么方法确定瓶颈)
  3. 研究问题(确定用什么方案处理这个问题,要考虑性能优化的前提)
  4. 解决问题(按问题研究的结论去实际处理,并验证处理结果与预期的一致性)

影响性能的问题

CPU

通常,CPU 渲染时间的最大贡献者是向 GPU 发送渲染命令的成本。渲染命令包括绘制调用(绘制几何形状的命令),以及在绘制几何形状之前更改 GPU 上设置的命令

减少 Unity 渲染的对象数量

  • 考虑减少场景中对象的总数,例如:使用 skybox 来创建遥远几何的效果
  • 执行更严格的剔除,以便 Unity 绘制更少的对象。考虑使用遮挡剔除来防止 Unity 绘制隐藏在其他物体后面的物体,减少相机的远夹平面,以便更远的物体落在其果实之外,或者,对于更细粒度的方法,将物体放入单独的层,并使用 Camera.layerCullDistances 设置每层剔除距离

减少 Unity 渲染每个对象的次数

  • 在适当的情况下,使用 light mapping 来烘焙(预计算)照明和阴影。这增加了构建时间、运行时内存使用和存储空间,但可以提高运行时性能
  • 如果应用程序使用 Forward rendering,请减少影响对象的每像素实时灯光的数量
  • 实时阴影可能是非常资源密集型,因此请谨慎而高效地使用它们
  • 如果应用程序使用反射探针,请确保您优化其使用

GPU

填充率的限制

GPU 试图每帧绘制的像素比它所能处理的要多,如果是这种情况,请考虑以下选项:

  • 识别并减少应用程序中的透支。透支最常见的贡献者是重叠的透明元素,如 UI、粒子和 Sprite,在 Unity 编辑器中,使用 Overdraw Draw模式 来识别有问题的区域
  • 降低片段着色器的执行成本
  • 如果您使用的是 Unity 的内置着色器,请从 Mobile 或 Unlit 类别中选择一个。它们也适用于非移动平台,但它们是更复杂的着色器的简化和近似版本
  • 动态分辨率是一个 Unity 功能,允许您动态缩放单个渲染目标

内存带宽的限制

GPU 正在尝试向其专用内存读取和写入比它在帧中可以处理的更多数据。这通常意味着有太多的纹理,或者纹理太大。如果是这种情况,请考虑以下选项:

  • 为与相机距离在运行时变化的纹理启用 mipmap(例如,3D场景中使用的大多数纹理),这增加了这些纹理的内存使用量和存储空间,但可以提高运行时 GPU 的性能
  • 使用合适的压缩格式来减小内存中纹理的大小。这可能会导致更快的加载时间、更小的内存占用和更高的 GPU 渲染性能。压缩纹理仅使用未压缩纹理所需的内存带宽的一小部分

顶点处理的限制

这意味着GPU正在尝试处理比它在帧中处理的更多的顶点。如果是这种情况,请考虑以下选项:

  • 降低顶点着色器的执行成本
  • 优化您的几何形状:不要使用不必要的三角形,并尽量保持紫外线映射接缝和硬边(加倍顶点)的数量
  • 使用 Level of Detail 系统

降低渲染频率

有时,降低渲染帧速率可能会使应用程序受益,这不会降低渲染单个帧的 CPU 或 GPU 成本,但它减少了 Unity 这样做的频率,而不会影响其他操作(如脚本执行)的频率

可以降低应用程序部分或整个应用程序的渲染帧速率。降低渲染帧速率,以防止不必要的电力使用,延长电池寿命,并防止设备温度上升到 CPU 频率可能受到限制的程度。这在手持设备上特别有用

隐藏的几类小问题

  • 功耗比
  • 填充率
  • 发热量

性能问题可能的情况

  • 瓶颈可能性按由高到低的顺序排列(UP 经验总结)
    • CPU 利用率
    • 带宽利用率
    • CPU / GPU 强制同步
    • 片元着色器指令
    • 几何图形到 CPU 到 GPU 的传输
    • 纹理 CPU 到 GPU 的传输
    • 顶点着色器指令
    • 几何图形复杂性

经常用的优化思路

  • 升维与降维

升维:优化性能,但算法不易理解 降维:算法容易理解,但性能优化差

  • 维度转换,如空间与时间、量纲转换

{%note primary%}

AoS and SoA

结构数组(AoS),数组结构(SoA)或数组结构数组(AoSoA)是排列内存中记录序列的对比方法,涉及交错,并且对 SIMD 和 SIMT 编程有关

标准 C# 数组是 AoS,但 SoA 的结构适用于使用 CPU 缓存,CPU 缓存比主内存快,以及 SIMD 的超快速并行处理

AoS

面向对象思想,不同字段的数据在其中交错,这通常更直观,并由大多数编程语言直接支持

-----------------------------------------------------------------------------
| double | int | char | *pad* | double | int | char | *pad* | double | int | char |
-----------------------------------------------------------------------------

SoA

面向数据思想,将记录的元素(或 C 编程语言中的“结构”)分离成每个字段一个并行数组。在大多数指令集架构中,使用打包的 SIMD 指令更容易操作,因为单个 SIMD 寄存器可以加载同质数据,可能由广泛的内部数据路径(例如128位)传输

-----------------------------------------------------------------------------
| double | double | double | *pad* | int | int | int | *pad* | char | char | char |
-----------------------------------------------------------------------------

如果只需要记录的特定部分,则只需要迭代这些部分,允许将更多数据放入单个缓存行中。缺点是遍历数据时需要更多的缓存方式,以及低效的索引寻址

差异

  1. 由于每个对象都保存在一起,因此 AoS 对程序员来说更容易阅读

  2. 如果结构的所有成员一起访问,AoS 可能会有更好的缓存位置

  3. SoA 可能更有效率,因为将相同的数据类型组合在一起有时会暴露矢量化

  4. 在许多情况下,SoA 使用的内存较少,因为填充仅在数组之间,而不是在每个结构之间

StructureOfArraysGenerator

Structure of arrays source generator 插件制造 CPU 缓存和 SIMD 友好的数据结构制造高性能代码用于 .NET 和 Unity 平台

principle

点击链接以了解更多

{%endnote%}

参考