CPU 和 GPU 优化提示

本文档介绍了如何使用工具来识别和解决 CPU 和 GPU 瓶颈,从而优化游戏性能。

CPU 优化

如果分析显示游戏受 CPU 限制,则必须进一步调查。 这需要确定导致瓶颈并降低 FPS 的特定线程或 API。

对于 CPU 优化,通用解决方案通常效果不佳。相反,您必须根据游戏或场景确定最繁重的工作负载,然后优化相关逻辑和函数。

游戏引擎时间轨迹工具

以下工具可帮助您进行此分析:

Unreal Insights

在 Unreal Engine 项目中,Unreal Insight 工具可帮助分析构成帧的各个线程的时序轨迹信息。

举例来说,GameThread 通常会占用最大比例的 CPU 时间,这主要是因为 Tick Time。此外,与 FActorComponentTickFunction 关联的任务会消耗相当一部分 Tick Time。

为了优化 FActorComponentTick,必须排除计算并针对位于相机视野范围之外的角色和对象实现剔除。此外,利用基于 LOD(细节级别)的动画可以进一步提升性能。

Unreal Insight 轨迹时间轴,显示了 GameThread、RenderThread 和 RHIThread 的执行时间
包含 GameThread、RenderThread 和 RHIThread 的 Unreal Insight 轨迹(点击可放大)。

Unity Profiler (Unity)

使用 Unity 性能分析器 进行分析后发现,主 线程 消耗的时间超过 45 毫秒,其中 PostLateUpdate.FinishFrameRendering 占用了 16.23 毫秒,是耗时最多的操作。在此期间,观察到多次调用 Inl_RenderCameraStack。建议确定已启用摄像头的必要性,并据此进行优化。

Unity Profiler 时间轴显示了主线程等待 Gfx.WaitForPresentOnGfxThread
Unity 分析器中的 GPU 绑定示例(点击可放大)。

系统级分析工具

使用以下性能分析工具:

Perfetto

借助 Perfetto 跟踪记录,您可以确定 Android 设备上每个线程的 CPU 核心分配和执行详细信息。这样,您就可以通过分析线程执行数据来发现性能瓶颈。

CPU 开销情况

该轨迹表明,GameThread 和 RenderThread 上的工作负载导致 RHI 线程的 QueuePresent 出现延迟,从而导致基于 VSync 的 CPU 绑定场景。

Perfetto 轨迹,显示了 GameThread、RenderThread 和 RHIThread 的执行时间
包含 CPU 执行详细信息的 Perfetto 轨迹(点击可放大)。

GPU 开销案例

轨迹表明,GPU 完成本身超过 25 毫秒,这表示属于 GPU 绑定场景。

Perfetto 跟踪记录显示 GPU 完成块等待 GPU 完成
包含 GPU 开销详细信息的 Perfetto 轨迹(点击可放大)。

Simpleperf

如需确定当前 CPU 使用率最高的函数,可以使用 simpleperf。为获得最佳效果,建议您对这些功能进行排序,优先处理使用率最高的功能。

显示 CPU 使用率最高的函数的 Simpleperf 输出
Simpleperf CPU 分析:分析函数调用层次结构和资源利用率(点击可放大)。

Simpleperf 可帮助您检查有关占用最多 CPU 时间的函数的数据。如需优化 CPU 使用率,请先从使用 CPU 最多的函数入手。在此示例中,与 ActorComponentTickFunctions 中的动画关联的 USkeletalMeshComponent 使用的 CPU 最多。

GPU 优化

如果分析表明游戏受 GPU 限制,则必须进一步调查。这需要使用各种工具和技术来进行 GPU 优化和分析。

如需优化 GPU,请使用帧调试器分析每个场景的渲染流水线和绘制调用。此外,您还必须深入了解 GPU 架构和流水线行为,才能确定不必要的操作或需要优化的方面。

以下部分介绍了 GPU 优化方法和工具。

消除不必要的 RenderPass

为了提高渲染性能并减少 GPU 工作负载,请消除不必要的渲染通道。其中包括缺少绘制调用的任何渲染通道,或输出未在最终帧中使用的任何渲染通道。

使用 GPU 调试器(例如 RenderDoc)分析渲染流水线并发现优化机会。

  1. 无绘制调用:检查渲染通道是否包含任何绘制调用。如果没有绘制调用,则移除相应 pass。

  2. 未使用的输出:检查后续处理遍数是否会访问或显示渲染处理遍数的输出,例如颜色或深度。如果不是,请移除通行卡。

  3. 可合并的卡券:确定可合并的卡券:

    • 相同的帧缓冲区或附件
    • 兼容的加载或存储操作
    • 中间没有依赖屏障
RenderDoc 事件浏览器,显示 Vulkan 渲染通道和绘制调用
RenderDoc 中的 RenderPass 和 GPU 命令序列(点击可放大)。

尽可能减少加载或存储操作

加载或存储操作会占用大量内存,因此属于资源密集型操作。最大限度地减少不必要的加载-存储操作。仅当需要 RenderPass 中的附件时才执行这些操作。否则,请将它们替换为 ClearDon't care 操作,以减少开销。

如何进行优化

使用 GPU 调试器(例如 RenderDoc)分析渲染流水线,并确定以下优化机会:

  1. 加载:如果渲染通道附件不使用来自前一个通道或附件的数据,则无需执行加载操作。在这种情况下,使用 Don't careClear 可以减少开销。

  2. 存储:如果渲染通道附件在当前渲染通道之后未使用,则存储操作是不必要的。在这种情况下,请使用 Don't careClear

  3. 替换:确定当前加载或存储设置是否可以替换为 ClearDon't Care,而不会影响最终帧。

RenderDoc 事件浏览器和资源检查器分析图像布局和渲染通道
RenderDoc 渲染流水线分析(点击可放大)。

避免舍弃以启用 Early-Z

Early-Z 可提升移动平台的性能。不过,着色器中的 discard 指令会自动停用 Early-Z。如果 discard 指令不是必需的,请将其移除。

Early-Z 加速

此优化可显著减少 fragment 着色器操作,并提高 GPU 性能。

Early-Z 深度和模板测试

比较启用和停用 Early-Z 时 CPU 和 GPU 性能指标的表格
Early-Z 加速的性能影响(点击可放大)。

如何进行优化

使用 GPU 调试器(例如 RenderDoc)分析渲染流水线,并确定以下优化机会:

  1. 在 fragment 着色器中使用 discarddiscard 关键字可防止 GPU 执行早期深度测试,因为 fragment 的可见性是无法预先知道的。

  2. 修改 gl_FragDepth:动态修改 gl_FragDepth 会更改 fragment 的深度,从而停用 Early-Z 优化,因为在 fragment 处理之前,最终深度是未知的。

  3. 启用了 Alpha 到覆盖率:启用 Alpha 到覆盖率(通常用于 MSAA 渲染)后,片段覆盖率取决于 Alpha 值。这可能会延迟深度测试并停用 Early-Z。

使用和不使用 discard 着色器关键字的每像素 fragment 比较
用于分析的 RenderDoc GPU 调试器(点击可放大)。

优化纹理格式

选择最佳纹理格式可减少内存消耗、提高带宽效率并提升渲染性能。采用过高精度的格式可能会浪费 GPU 资源,而不会带来视觉优势。

如何进行优化

使用 GPU 调试器(例如 RenderDoc)分析渲染流水线,并确定以下优化机会:

  1. 使用 D24S8 而不是 D32S8 作为深度模具缓冲区:与 D32S8 相比,使用 D24S8 作为深度模具缓冲区可将内存消耗量减少 20%,并且在大多数应用中,视觉质量几乎没有明显差异。
  2. 为彩色纹理使用 ASTC 压缩ASTC 压缩可显著减少纹理内存用量(与未压缩格式相比,最多可减少 8 倍),同时保持较高的视觉质量。
  3. 使用半精度浮点格式而非全精度浮点格式:使用 R16FRG16F 可减少内存带宽和存储空间消耗。这些格式非常适合后处理缓冲区。

优化几何体复杂程度

尽量减少几何复杂性有助于提高渲染性能,尤其是在 GPU 功能受限的移动设备上。这包括使用更少的顶点和三角形、合并对象以减少绘制调用次数,以及消除未渲染或不必要的几何图形。网格简化、细节层次 (LOD) 以及视锥体或遮挡剔除等技术可以显著减少 GPU 工作负载并提高帧速率。

如何进行优化

使用性能分析工具和 GPU 调试器(例如 RenderDoc、Android GPU 检查器或其他性能分析器)来确定与几何图形相关的性能瓶颈。

  1. 减少三角形数量:尽量减少多边形的使用,尤其是对于小型或远处的对象。

  2. 使用细节级别 (LOD):系统会根据摄像头的距离自动使用更简单的网格。

  3. 合并小型网格:整合静态对象,以减少绘制调用次数和 CPU 开销。

  4. 视锥体剔除和遮挡剔除:避免渲染视图之外或被其他元素遮挡的对象。

移除不必要的附件

即使渲染通道附件(例如颜色、深度、模板)未使用,也会消耗内存带宽和 GPU 资源。移除不必要或冗余的附件可提高性能并降低功耗,尤其是在移动平台上。

如何进行优化

使用性能分析工具和 GPU 调试器(例如 RenderDocAndroid GPU Inspector 或其他性能分析器)来确定与几何图形相关的性能瓶颈。

  1. 检查实际使用情况:是否有任何绘制调用或着色器正在写入附件或从中读取数据?
  2. 分析帧输出:使用 RenderDoc 或类似的实用程序来确定附件是否对最终图像有贡献。
  3. 考虑使用临时或虚拟附件:对于不需要永久性存储的临时数据,应使用临时附件或“Don't Care”存储操作。

优化着色器精度

在着色器中使用过高的精度(例如,使用 highp 而不是 mediumplowp)会增加 GPU 工作负载、功耗和寄存器压力,尤其是在移动 GPU 上。通过为变量(例如位置、颜色、UV)使用最低的适当精度,您可以在不产生明显视觉影响的情况下提高性能。

比较使用 mediump 与 highp 着色器精度时的 CPU 和 GPU 性能指标的表格
着色器精度对性能的影响(点击可放大)。

如何进行优化

使用 RenderDoc、Android GPU 检查器或其他性能分析器等分析工具和 GPU 调试器来确定与几何体相关的性能瓶颈。

  1. 检查着色器代码:评估着色器变量,并确认仅在必要时(例如用于深度或屏幕空间计算)才使用高精度。对于不需要高精度的颜色、UV 坐标或值,请使用中等或低精度。

  2. 使用 GPU 调试器:RenderDoc 或移动 GPU 性能分析器(例如 AGI、Mali/GPU Inspector)等诊断实用程序可识别与精度问题相关的高寄存器使用率或着色器停滞。

Mali Varying Usage 分析器显示了 16 位插值以及使用 mediump 的着色器代码
性能剖析工具和 GPU 调试器示例(点击可放大)。

启用背面剔除

对于实体对象,通常不需要渲染背对相机的三角形(背面)。

如何进行优化

使用 VK_CULL_MODE_NONE 会对性能产生负面影响,因为它会强制 GPU 渲染正面和背面,从而增加渲染工作负载。

Vulkan 命令日志,显示 vkCmdSetCullMode 设置为 VK_CULL_MODE_NONE
启用背面剔除的调试日志(点击可放大)。

最大限度地减少界面场景中的过度绘制

消除不必要的绘制调用和渲染通道,尤其是在界面场景中,以提升渲染性能并减少 GPU 工作负载。例如,在整个世界渲染完毕后才在屏幕上叠加界面的界面场景中,渲染世界会变得冗余。

如何进行优化

使用 GPU 调试器(例如 RenderDoc)分析渲染流水线,并确定以下优化机会:

  1. 验证是否没有多余的过度绘制。在用户界面上下文中,在整个屏幕可能需要渲染的情况下,请确认之前的渲染传递不会不必要地过度绘制。
  2. 启用深度测试和剔除功能以优化性能。
  3. 考虑从前到后进行渲染。
RenderDoc 事件浏览器和纹理查看器,用于识别不必要的过度绘制渲染通道
消除多余的绘制调用和渲染通道的示例(点击可放大)。