首页 / 资讯中心 / 文章详情

为什么降分辨率能区分 CPU 和 GPU 瓶颈

为什么降分辨率能区分 CPU 和 GPU 瓶颈 ★ FEATURED ARTICLE
核心逻辑就两条:① CPU 和 GPU 是并行工作的,帧时间取两者里慢的那个 帧时间 max(CPU 耗时, GPU 耗时) ← 不是相加 ② 分辨率只影响 GPU 的像素工作量 对 CPU 的工作量几乎没有任何影响所以降分辨率相当于单独给 GPU 减负,CPU 一动不动。减负之后帧率变没变,就暴露了到底是谁在拖后腿。一、先理解并行这件事很多人以为一帧是这样跑的:❌ 错误的理解(串行) CPU 干 10ms → GPU 干 8ms → 下一帧 帧时间 18ms实际是流水线:✅ 真实情况(并行,GPU 比 CPU 晚一帧) 时间 ────────────────────────────────────────► CPU [ 第1帧 ][ 第2帧 ][ 第3帧 ][ 第4帧 ] GPU [ 第1帧 ][ 第2帧 ][ 第3帧 ]CPU 准备好第 1 帧的指令就交给 GPU,然后立刻开始准备第 2 帧。两个在同时干活。结果是:输出速度由慢的那一方决定。这就像两个人接力装配:A 组装零件(CPU) 10ms 一个 B 喷漆(GPU) 5ms 一个 → 产出速度 10ms 一个,B 有一半时间在等 ← A 是瓶颈 如果 B 变成 20ms 一个 → 产出速度 20ms 一个,A 在等 ← B 是瓶颈二、两种瓶颈的样子GPU bound(GPU 是瓶颈)CPU [忙 6ms][═════ 等 10ms ═════][忙 6ms][═══ 等 ═══] GPU [════════ 忙 16ms ════════][════════ 忙 16ms ════] ↑ 帧时间 16ms,CPU 有 10ms 在闲着CPU bound(CPU 是瓶颈)CPU [═══════ 忙 16ms ═══════][═══════ 忙 16ms ═══════] GPU [忙 6ms][══ 等 10ms ══][忙 6ms][══ 等 ══] ↑ 帧时间 16ms,GPU 有 10ms 在闲着两种情况下帧率一模一样,但原因完全相反。你看帧率看不出区别,所以需要一个手段把它们分开。三、降分辨率做了什么它减少了什么GPU 的工作大致分两块:顶点处理(Vertex) 处理每个顶点 → 工作量 顶点数,和分辨率无关 片元处理(Fragment) 处理每个像素 → 工作量 像素数,和分辨率直接成正比1080p → 540p 像素数: 1920×1080 207 万 960×540 52 万 像素工作量直接变成 1/4 连带帧缓冲的读写带宽也变成 1/4它没有减少什么Draw Call 数量 不变 ← 还是那么多物体要提交 物体剔除计算 不变 脚本逻辑 / Update 不变 物理模拟 不变 动画计算 不变 UI 重建 不变 GC 不变 顶点处理 不变 ← 顶点还是那么多上面这一列全是 CPU 的活(顶点是 GPU 的,但也不受分辨率影响)。所以降分辨率这个操作非常干净:它只动一个变量。四、两种结果怎么读情况 A:帧率明显上升 → GPU bound降分辨率前 降分辨率后 CPU [忙 6ms][═ 等 10 ═] CPU [忙 6ms][等 ═] GPU [═══ 忙 16ms ═══] GPU [忙 7ms] 帧时间 16ms 帧时间 7ms ≈ 62 FPS ≈ 140 FPS GPU 的担子被卸掉了,帧率立刻跟着涨 → 说明之前就是 GPU 在拖情况 B:帧率几乎不变 → CPU bound降分辨率前 降分辨率后 CPU [═══ 忙 16ms ═══] CPU [═══ 忙 16ms ═══] GPU [忙 6ms][等 10ms] GPU [忙 2ms][等 14ms] 帧时间 16ms 帧时间 16ms ≈ 62 FPS ≈ 62 FPS ← 没变 GPU 本来就在闲着,你让它更闲也没意义 CPU 该花的 16ms 一分没少 → 说明瓶颈在 CPU一句话总结:给一个本来就在摸鱼的部件减负,总产量不会有任何变化。五、这个测试的真正含义(重要)严格说,降分辨率测的不是CPU vs GPU,而是:帧率上升 → 瓶颈在「片元处理 带宽」 帧率不变 → 瓶颈在其他地方(CPU,或者 GPU 的顶点侧)所以存在一种误判场景:场景里有几百万个顶点,但 Shader 很简单 降分辨率 → 片元变少了,但顶点数一个没少 → GPU 还是忙 → 帧率不变 → 你以为是 CPU bound ❌ → 实际是 GPU 的顶点瓶颈什么时候容易撞上这种情况:· 大量高面数模型没做 LOD · 骨骼动画角色太多 · 植被/草海没用 Instancing · 阴影 Pass 把整个场景又画了一遍(顶点翻倍) · 曲面细分 / 几何着色所以这个测试只是第一道筛子,不是终审。帧率不变时,还要进一步确认。六、配一个交叉验证:看 Profiler 的等待项最直接的证据是 CPU 里的等待标记。Unity Profiler → CPU Usage → Hierarchy 看到这些,说明 CPU 在等 GPU → GPU bound Gfx.WaitForPresentOnGfxThread Gfx.WaitForRenderThread Gfx.PresentFrame Semaphore.WaitForSignal 看不到等待项,时间全被这些吃掉 → CPU bound Scripts / PlayerLoop Camera.Render(提交阶段) Physics.Simulate Canvas.BuildBatch GC.Collect⚠️ 一个常见误读 Gfx.WaitForPresentOnGfxThread 占了 10ms → 不是「渲染很慢」 → 是「CPU 提前干完活,在等 GPU」 → 这是 GPU bound 的标志,不是 CPU 问题 很多人看到这个耗时高,去优化渲染提交代码,方向反了两个手段结论一致,基本就可以定性了:降分辨率Profiler 有等待项结论帧率涨有GPU bound,片元/带宽 ✅ 明确帧率不变没有CPU bound ✅ 明确帧率不变有GPU bound 但卡在顶点侧 ⚠️帧率涨没有少见,可能 VSync 干扰七、测的时候必须注意的几件事① 一定要解锁帧率这是最常见的测试失误。❌ targetFrameRate 60,VSync 开着 原本 60 帧 → 降分辨率 → 还是 60 帧 → 你得出CPU bound的结论 → 实际只是撞到了帧率上限,什么都没测出来 ✅ 测试时解锁Application.targetFrameRate-1;QualitySettings.vSyncCount0;⚠️ 移动端注意:即使解锁,系统也可能把你压在屏幕刷新率上 所以要确保基线帧率明显低于刷新率(比如 40 帧) 才有观察空间② 降的要是渲染分辨率,不是窗口大小✅ URP: Render Scale 0.5 (Universal Render Pipeline Asset → Quality → Render Scale) ✅ 代码: Screen.SetResolution(w/2, h/2, true) ⚠️ 注意 URP 的 Render Scale 不影响 UI(UI 按原分辨率画) 所以如果你的瓶颈是 UI 的 Overdraw,这个测试会失灵③ 幅度要够大0.9x → 像素少 19%,噪声里看不出来 0.5x → 像素少 75%,差异明显 ✅ 0.25x → 更极端,确认用建议直接测 0.5x 和 1.0x 两档对比。④ 排除发热干扰❌ 先测 1.0x 跑 10 分钟,然后测 0.5x → 这时候手机已经热了,在降频 → 0.5x 的数据被污染 ✅ 每次测试前让设备冷却 ✅ 或者交替测:1.0 → 0.5 → 1.0 → 0.5,看是否可重复⑤ 场景要固定用固定机位、固定时间点的回放或静止场景 不要边操作边测 —— 视角一变负载就变,数据没法比八、扩展:一套完整的单变量诊断法降分辨率只是这套方法里的一项。思路都一样:每次只卸掉一种负担,看帧率反应。测试动作只减少了帧率涨 瓶颈在降渲染分辨率片元数、帧缓冲带宽片元处理 / 带宽把所有材质换成最简 UnlitShader 指令数Shader 复杂度把所有贴图尺寸减半纹理采样带宽纹理带宽关掉实时阴影一整个额外 Pass阴影关掉后处理全屏 Pass 和 RT 切换后处理 / 带宽隐藏一半物体Draw Call 顶点 片元不确定(动了太多变量)用 LOD 强制最低级顶点数顶点处理禁用所有脚本的 UpdateCPU 逻辑脚本逻辑关掉物理(Time.fixedDeltaTime 调大)物理模拟物理关掉 UI CanvasUI 重建 UI 填充UGUI使用要点:一次只改一项,改完立刻改回来 隐藏一半物体那一行是个反例 —— 它同时减少了 Draw Call、 顶点和像素,涨了也不知道是哪个的功劳,只能当粗筛一个快捷的 Shader 替换测试#ifUNITY_EDITORusingUnityEditor;usingUnityEngine;publicclassShaderCostTest:MonoBehaviour{[SerializeField]privateShadersimplest;// 指向 Unlit/ColorprivateMaterial[]_backup;privateRenderer[]_renderers;[ContextMenu(切换到最简 Shader)]voidToggle(){_renderers??FindObjectsOfTypeRenderer();if(_backupnull){_backupnewMaterial[_renderers.Length];varflatnewMaterial(simplest);for(inti0;i_renderers.Length;i){_backup[i]_renderers[i].sharedMaterial;_renderers[i].sharedMaterialflat;}Debug.Log(已替换为最简 Shader);}else{for(inti0;i_renderers.Length;i)_renderers[i].sharedMaterial_backup[i];_backupnull;Debug.Log(已恢复);}}}#endif如果换成 Unlit 后帧率大涨,说明瓶颈是 Shader 太复杂(逐像素光照、多层采样、复杂数学),不是面数或 Draw Call。九、归纳1. CPU 和 GPU 并行工作,帧时间 max(两者),不是相加 2. 瓶颈方满载,另一方在等 —— 给闲着的那方减负,帧率不会变 3. 降分辨率只减少 GPU 的像素工作量,CPU 工作量不变 → 这是一个干净的单变量实验 4. 帧率涨 GPU(片元/带宽)瓶颈 帧率不变 CPU 瓶颈,或 GPU 的顶点侧瓶颈 5. 配合 Profiler 的 Gfx.WaitFor... 交叉验证 有等待项 CPU 在等 GPU GPU bound 6. 测试前必须:解锁帧率、幅度够大(0.5x)、场景固定、设备冷却 7. 同一套思路可以扩展成一整张诊断表,每次只卸一种负担
阅读完成 · 觉得有帮助?
咨询建站