技术博客

从 RD 到 RDD 的抽象开销实测

第 49 / 149 篇 · 2025-07-09 · godot · 渲染管线性能优化源码分析

4.3 的拆分让每次 texture_update() 都多走一跳:RD 做账簿和校验,RDD(RenderingDeviceDriver)才把参数翻译成 Vk* 调用。反对拆层的人担心"多一层虚函数,热路径慢一截"。这个担心值得验证而不是争吵——我写了个最小实验,量级结论先放这:在渲染负载里这笔开销低于 1%,可忽略;但 RD 自己的账簿更新比 RDD 跳转贵一个量级,那才是要盯的地方。

实验设计

两个对照组,同一台机器(Apple Silicon,4.3-stable 自编译,validation 层关闭):

  1. 空操作循环:一个 compute pass,uniform set 里绑 8 张 storage texture,dispatch 尺寸 1×1×1,循环 10 万次,只测 compute_list_begin → dispatch ×8 → end → submit 的 CPU 耗时。
  2. 真实抓帧:同一 demo 开 2000 个精灵,用 Time Profiler 采 60 秒,看 RenderingDevice::* 和 RenderingDeviceDriverVulkan::* 在总帧 CPU 里的占比。

空操作循环的意义是把常数项逼出来:dispatch 1×1×1 时 GPU 工作趋近于零,测到的几乎全是 CPU 侧 API 开销。结果(中位数,跑 5 次取中间):

路径 单次循环耗时 每次 dispatch 摊销
RD 完整调用(begin/dispatch/end/submit) ≈ 41 µs / 8 dispatch ≈ 5.1 µs
直接调 RDD driver(绕过 RD 账簿) ≈ 33 µs / 8 dispatch ≈ 4.1 µs

两组差 ≈ 1 µs/dispatch,拆开来|RD 自己的账簿更新占大头(纹理 usage_scope 的读写、列表状态机检查),RDD 的虚函数跳转连零头都算不上——一次非内联虚调用在现代 CPU 上是纳秒级,10 万次才累计 1ms 量级。

真实抓帧组印证了这个量级:60 秒采样里,RenderingDevice::compute_list_* 系列函数加总约占帧 CPU 的 0.7%,RenderingDeviceDriverVulkan::command_buffer_* 系列约 0.2%,而 vkCmd* 之下的驱动内部( MoltenVK 翻译层)占了 6%——翻译层的成本是抽象层的几十倍,盯 RD 这层不如盯驱动。

为什么账簿比跳转贵

这个不对称结果值得想一下。虚函数跳转的成本模型是固定的:一次间接寻址 + 一次不可能预测的跳转,~5-20 个时钟周期。账簿的成本模型随资源数增长:每次 compute_list_bind_uniform_set 要把 set 里每个资源(texture/buffer RID)查表解引用、读写 usage_scope 字段。绑 8 张纹理就是 8 次 RID_Owner 查表 + 8 次字段更新,缓存不命中再翻几倍。RD 为屏障自动化付的就是这笔钱——它在录制期预支,换来列表结束时一次到位的屏障推导。

也就是说,RD 层真实的"税"不是拆层带来的,是自动屏障功能本身带来的,4.3 之前这个税就在。拆层只是把本来就存在的成本挪了个更清晰的记账科目。

成本模型图

一次 dispatch 的 CPU 开销分解(估算占比) 驱动与 MoltenVK 翻译层 ≈ 70% RD 账簿更新 RDD 虚跳转 宽度示意:翻译层与驱动内部 ≫ RD 账簿 ≫ RDD 跳转 优化收益排序:减 dispatch 次数 > 减资源绑定数 > 任何对抽象层本身的操心

这个数字怎么用

1% 的结论不意味着 RD 调用可以挥霍,意味着优化优先级不在这里。如果 profiler 显示帧 CPU 紧,按收益排序排查:减少 dispatch 次数(合批、MultiMesh)、减少每帧资源绑定数(uniform set 复用,见 UniformSet 缓存那篇)、最后才轮到质疑抽象层。反过来,如果场景里 RD 系列函数占比反常地高(比如超过 5%),那几乎一定是调用模式有问题——最典型的两个:每帧重建 uniform set(缓存失效)、在小循环里 submit()(提交洪峰把 staging 回退推到全停档)。

还有一个数字要顺带提:空操作循环里 submit() 本身占了 41 µs 里的约 18 µs——一次 submit 是固定开销,和命令多少关系不大。这条解释了为什么 RD 要求攒整帧再提交而不是逐 pass 提交:5 个 pass 分 5 次 submit,光提交就白付 5 份固定成本。

实测环境:macOS + Apple Silicon,Godot 4.3-stable 自编译(target=release_debug,validation 关闭),Time Profiler 采样。绝对数值随平台浮动,量级结论(驱动 ≫ RD 账簿 ≫ 虚跳转)在 Vulkan 直跑 Linux/Windows 上同样成立。