同步 CPU 和 GPU

此示例的源代码可以在 Khronos Vulkan 示例 github 存储库 中找到。

概述

此示例比较了两种用于在 CPU 和 GPU 之间同步的方法,WaitIdleFences,演示了哪种方法是避免停顿的最佳选择。

WaitIdle 或 Fences

同步 CPU 和 GPU 的最简单方法是使用 vkQueueWaitIdlevkDeviceWaitIdle,这些命令会等待设备或队列完成执行所有分派给它的工作。请注意,当使用单个 VkQueue 时,vkQueueWaitIdle 在功能上等同于 vkDeviceWaitIdle。虽然此方法可靠,但它比 CPU 和 GPU 之间同步实际需要的要粗糙得多。这会导致 GPU 内出现气泡,阻止它保持完整的管线,从而阻止它并行化来自不同帧的顶点和片元工作,从而导致更高的帧时间和更低的效率。

WaitIdle 的替代方法是使用 Vulkan Fence 对象,这些对象旨在允许 GPU 在完成单个帧的工作负载时通知 CPU,从而允许 CPU 安全地重用该帧的资源。此方法避免了在等待 GPU 完成执行时停顿,因为 CPU 可以继续提交后续帧,而无需等待 GPU,这反过来避免了 GPU 管线耗尽工作。

Wait Idle 示例

此示例提供了两个单选按钮,允许您在 WaitIdleFence 之间切换。

当选择 WaitIdle 时,该示例在开始每个帧之前调用 vkDeviceWaitIdle,这会强制 GPU 完成执行所有分派给它的工作,从而耗尽管线内的所有工作。因此,GPU 在创建下一帧的命令缓冲区时处于空闲状态,直到它被分派,这会增加帧时间。

当选择 Fence 时,该示例在创建期间为每个帧分配一个 Fence,然后调用 vkWaitForFences 并使用下一个要计算的帧的 Fence。此方法允许 CPU 在 GPU 执行先前帧的工作负载时继续向 GPU 分派工作。

以下是在具有 Mali G76 GPU 的手机上运行的示例的屏幕截图

Wait Idle Sample

当使用 WaitIdle 时,平均帧时间为 72 毫秒,但在启用 Fences 时,帧时间会减少 22% 至 56 毫秒。

当使用 Streamline Performance Analyzer 时,性能提升也很明显。

下图显示了当使用 WaitIdle 时,单个帧中的 Job Manager 周期和停顿。

Wait Idle Graph

而下图显示了使用 Fences 时,单个帧的 Job Manager 周期。

Fences Graph

此输出清楚地表明,WaitIdle 强制 GPU 耗尽所有工作,这导致 GPU 空闲,从而导致更高的帧时间。

最佳实践总结

这样做

  • 使用 Fences 异步读取数据回 CPU;不要同步阻塞并导致管线排空。

不要

  • 不必要地在 CPU 或 GPU 上等待 GPU 数据。

  • 除非绝对必要进行粗粒度同步,否则不要使用 vkQueueWaitIdle()vkDeviceWaitIdle()

影响

  • 影响可能非常小,也可能非常显著,具体取决于排队的各个工作负载的相对大小和顺序。

调试

  • Arm Mobile Studio 可用于可视化 Arm CPU 和 Mali GPU 在两个 GPU 队列上的活动,并可以快速显示 GPU 队列本地调度中的气泡(指示阶段依赖问题)或 CPU 和 GPU 之间的全局气泡(指示正在使用阻塞的 CPU 调用)。