← ALL ARTICLES

帧瓶颈诊断

全库目录221
本文目录16

性能优化先看时间,不先看方案名称。60 FPS 的单帧预算约 16.67 ms,30 FPS 约 33.33 ms。FPS 是帧时间的倒数,平均 FPS 会掩盖偶发尖峰。

帧时间

需要同时记录:

  • 平均帧时间;
  • P95/P99;
  • 最坏帧;
  • CPU 主线程;
  • CPU 渲染线程;
  • GPU帧时间;
  • 内存和温度随时间变化。

一段 5 秒稳定场景和 30 分钟真机运行回答的是不同问题。

CPU 瓶颈

CPU 无法及时准备下一帧。常见来源:

  • 玩法、动画、物理;
  • Culling 和渲染 List;
  • Draw Call/状态提交;
  • 资源加载、反序列化和实例化;
  • GC;
  • 主线程锁和 Job 等待。

症状可能是 GPU 有空闲空泡,CPU Profiler 中某线程持续接近帧预算。

降低分辨率后帧时间几乎不变,常提示CPU或非像素瓶颈,但这不是单独的充分证据。沿主线程到记录线程、再到GPU提交寻找依赖:主线程等动画Job时,先看Job何时开始、哪条前驱让它迟到;若Job已完成而主线程仍未提交,等待对象就不是当前瓶颈。GPU在收到下一批命令前空闲,与收到命令后执行很慢,是两条不同的调查路径。

GPU 瓶颈

GPU 无法在预算内完成命令。可能受:

  • 顶点/Geometry;
  • 像素/Fragment;
  • Compute;
  • 纹理/内存 Bandwidth;
  • 渲染目标带宽;
  • 同步和管线空泡;
  • 射线 Tracing;
  • Queue 重叠争用。

降低分辨率后明显加速,说明成本与像素相关的可能性高,例如重复绘制、后处理、GBuffer 等随渲染分辨率变化的工作,但仍要用 GPU 计数器确认。

工作与等待

CPU与GPU的重叠

CPU 与 GPU 通常并行。帧总时间接近较慢一侧和必要同步,而不是主线程 + 渲染线程 + GPU 的直接总和。

如果 CPU 等待 Present 或 Fence,它的等待时间可能只是 GPU 慢的结果。不能看到 CPU 等待很高就判断 CPU 算法慢。

例如 CPU 有效工作 6 毫秒、GPU 工作 12 毫秒,稳态重叠可以接近每 12 毫秒产出一帧;CPU 时间线上的另外 6 毫秒等待并不是又一份算法工作。若开启 60 Hz 同步,呈现节奏可能限制在约 16.67 毫秒,此时把 GPU 从 12 毫秒优化到 10 毫秒,显示帧率也未必上升。区分吞吐、帧排队和输入延迟,才能解释“优化后 FPS 没变”。

瓶颈隔离的实验协议

判断瓶颈在哪一侧,靠控制变量实验而不是读总数。协议是一次只改一个量,观察帧时间响应:

改动帧时间明显下降帧时间几乎不变
降低分辨率像素/带宽相关CPU、顶点或提交侧
减少三角形数量提示顶点或几何相关可能未触及当前关键路径
缩小纹理尺寸提示纹理访问相关可能仍被其他工作限制
简化像素着色器提示片元计算相关可能被访存或其他通道限制
减少 Draw Call提示提交或状态开销相关提交可能未处于关键路径

每个实验只证明一个方向。分辨率下降变快说明成本与像素相关,但无法区分是重复绘制、带宽还是 ALU——需要再用对应计数器细分。

等待的归属

时间线上的等待段经常被误读。Present 阻塞可能是帧率上限生效(一切正常),也可能是 GPU 积压过多帧(需要减少排队深度)。GPU 时间线中的空泡可能是等 CPU 提交(CPU 侧瓶颈),也可能是等屏障(管线组织问题)。判断依据永远是“空泡两端分别是什么”,而不是空泡出现在哪一侧。

GPU工作负载

Draw Call 成本

Draw Call 主要增加 CPU 提交、状态准备和驱动/API 开销。GPU 端还会受小批次、状态切换和管线利用率影响。

优化 Draw 时要区分:

  • Draw 数;
  • SetPass/PSO Switch;
  • 每 Draw 三角形和实例数;
  • CPU 渲染线程时间;
  • GPU 是否因小批次或状态切换空闲。

每次Draw都要从引擎数据整理出绘制参数、绑定状态并记录命令,小批次让这部分固定成本分摊到更少图元。假设N次绘制各有平均固定记录耗时c,另有CPU侧变换、排序、打包和上传准备的总耗时G,则CPU近似成本可写为N*c+G,c与G均以毫秒计。合批只有在不显著增加G时,才可能通过降低N改善关键路径。材质/PSO不兼容会拆开批次,过度合并包围体又可能减少剔除收益。将记录时间与GPU覆盖同时检查,才能解释Draw减少而整帧变慢。

顶点成本

顶点成本不只由三角形数量决定:

  • 顶点拆分;
  • 索引缓存;
  • 蒙皮骨骼数;
  • 顶点纹理拉取;
  • Tessellation;
  • 多 Pass 重复绘制;
  • 阴影 Cascade。

索引缓冲让相邻三角形复用顶点,实际顶点着色次数受顶点拆分和变换缓存影响,不一定等于三角形数乘3。一个物体再进入阴影、深度和主颜色通道时,各通道可能重复执行顶点工作;减少颜色通道的片元着色不会取消这些顶点调用。先按通道统计处理的顶点和着色器复杂度,再判断蒙皮或顶点纹理读取是否主导。

三角形缩小后还会改变下一阶段:它在光栅化后只覆盖很少样本,顶点工作仍需完成,并可能出现Quad有效利用下降。

像素和重复绘制

像素成本约受下面因素共同影响:

text
屏幕覆盖 × Overdraw 层数 × Shader 成本 × 样本数

透明、粒子、全屏Pass和复杂材质是常见来源。这个乘积用于分解负载,不意味着所有成本都严格相乘:MSAA增加逐样本覆盖、深度与颜色存储,但普通像素着色结果可共享到多个覆盖样本,逐样本着色才增加对应的Shader调用。Early-Z在深度已有且执行条件允许时拒绝被挡片元,透明混合仍需保留有贡献的多层着色。分别查看片元调用、覆盖样本与附件读写,才知道重复工作落在哪一阶段。

Quad 利用率

使用屏幕导数的片元工作常按2×2像素块(Quad)组织,以便在相邻lane之间求差。小三角形覆盖很少样本时,辅助 lane 会参与求导却不写颜色,降低有效利用率。具体付出多少执行成本取决于硬件、着色器和调用合并,不能固定理解成每个三角形必付四倍成本。

因此三角形缩小到像素尺度后,有效覆盖下降不一定带来同等的着色成本下降。远密集网格的“微三角形”是常见的隐藏热点,这也是 Nanite 一类方案把三角形尺寸控制在像素级附近的动机之一。

填充率和带宽

填充率关注单位时间处理/写入样本的能力。带宽关注从显存读取和写入多少字节。

高分辨率 GBuffer、HDR、MSAA、多个全屏 Pass 会增加渲染目标带宽。纹理采样也会增加读取,但缓存命中和压缩会改变实际流量。

带宽的算术

带宽调查可以先计算逻辑流量,再用计数器与对照实验判断是否真的受外存带宽限制。以 1080p 延迟渲染为例:4 张 RGBA8 的 GBuffer 每张约 8.3 MB,几何阶段写一遍、光照阶段读一遍,按 60 帧每秒运行时,仅 GBuffer 的每秒逻辑流量就是:

4×8.3 MB×2×60 fps≈4 GB/s4\times8.3\text{ MB}\times2\times60\text{ fps}\approx4\text{ GB/s}

再叠加颜色、深度、阴影与后处理的读写。这是未考虑缓存、压缩与 tile 内复用的逻辑流量。与设备实测外部带宽对照时,还要确认哪些附件真的写回或读出外存。它适合比较候选预算,不能单凭这一个估算决定方案可行性。移动端共享内存带宽、功耗与片上附件预算常较紧,压缩GBuffer数量与位宽可以减少这些资源压力;具体可用吞吐仍按设备实测。

缓存

GPU 常有纹理缓存、L1/L2 和专用缓存。性能取决于访问局部性,不只是资源总大小。

  • 连续实例数据容易合并读取;
  • 随机访问大缓冲可能频繁 Miss;
  • Mipmap 改善远处纹理采样足迹;
  • 同一 Wave 访问相邻地址通常更友好。

占用率和延迟隐藏

GPU 让多个 Wave 同时驻留,以便一个 Wave 等待纹理时执行另一个。每线程寄存器、线程组共享内存和组尺寸会限制驻留数量。

占用率不是目标分数。算法带宽已满时,提高占用率不一定加速;寄存器过少还可能 Spill 到内存。

占用率可用简化资源账本推演。假设某执行单元可分配65536个寄存器,每Wave有32线程,每线程64个寄存器,仅寄存器上限便是32个Wave;改为每线程128个,便降到16个。这里忽略分配粒度、共享内存和硬件Wave上限,仅说明资源如何限制驻留,实际数字必须查询目标硬件。若纹理延迟需要更多就绪Wave才能覆盖,驻留减少可能暴露等待;若带宽早已饱和,增加Wave只增加排队。

缓存判断也需要跟踪地址。让相邻lane读取相邻记录通常集中在少量缓存行,随机索引则可能触及许多行。缩小纹理后变快既可能减少流量,也可能让工作集进入缓存;结合外部读取、命中率及相同访问次数对照,才能区分两种解释。缓存与Wave细节由 GPU执行与访存 展开。

同步与管线空泡

同步把某次访问的开始限制在前驱完成之后。看到图形队列在8到11毫秒空闲,先找它等待的复制完成值:若复制队列直到11毫秒才上传完纹理,图形队列空泡来自这条数据依赖。可以将上传提前、降低本帧必需资源量或先使用已驻留回退资源;直接删掉等待会让Shader读取未完成的数据。若复制早已在7毫秒完成而图形仍等到11毫秒,则再检查CPU提交、队列信号顺序与工具显示的等待对象。

同一队列的资源屏障也可能降低重叠。原本只需等待生产纹理A的Compute,却把所有此前工作都划入宽泛等待范围,就可能让无关绘制也停住。依据真实读写缩小阶段/访问范围,优化后保持输出一致,并在时间线确认消费开始更早。跨队列和访问可见性规则由GPU同步与屏障展开;正确依赖保留,调查目标是前驱为什么晚、等待范围为什么大。

CPU立即读回也会截断重叠:本帧提交后马上等待完成并读取,下一帧准备被推迟。把读回移到独立槽位、结果附原始帧号,过几帧检查其完成值再消费,可以解除即时依赖;必须当帧参与决策的数据则仍保留等待成本。相关实现见GPU异步回读。

显存驻留与预算

资源的逻辑分配量、实际驻留量和操作系统预算分别记录。纹理、网格、动画、渲染目标、加速结构与临时资源的峰值可能共同超过当前预算;驱逐后再次访问又要页入或重新流送,尖峰由此出现。磁盘包体小不代表解压后的资源或多份临时附件也小。

例如资源A本已驻留,新增高分辨率附件使工作集超过预算,系统驱逐A;下一段绘制再用A时,时间线上可能出现驻留等待或传输。先把尖峰与资源驻留事件、传输和预算曲线对齐,再通过降低Mip驻留或减少附件峰值做对照。若分配量下降而尖峰未变,应继续检查上传依赖和其他共享内存使用者,不能仅凭“接近显存总量”认定已经发生驱逐。

稳定运行采用资源预算和有明确回退层级的流送,资源回收等待最后一次GPU访问完成;预算降低时逐级减少非必需资源,避免所有资源同帧卸载又重新请求。具体驻留和流送由Texture Streaming与GPU资源生命周期说明,本页用预算→驱逐/流送→等待→帧尖峰连接调查证据。

相关主题

参考资料