Vulkan 计算着色器:将缓冲区传输到 GPU 或从 GPU 传输缓冲区的最有效方法?检索缓冲区似乎很慢

问题描述 投票:0回答:1

这里是初学者。刚刚完成官方 Vulkan 教程,现在正在尝试使用 Vulkan 进行一些 GPGPU 工作,并且正在努力在我的 GTX 1070 上获得不错的性能。

假设我想针对一个数据块运行计算着色器并将结果写入同一个块中。

为了将数据传输到 GPU,我首先创建一个主机可见的暂存缓冲区和一个设备本地缓冲区,然后将暂存缓冲区的内存映射到主机的地址空间,将数据复制到其中,取消映射暂存缓冲区,然后复制暂存缓冲区到设备缓冲区。

  VkBuffer stagingBuffer;
  VkDeviceMemory stagingBufferMemory;
  createBuffer(size, VK_BUFFER_USAGE_TRANSFER_SRC_BIT,
    VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
    stagingBuffer, stagingBufferMemory);

  void* bufferData = nullptr;
  vkMapMemory(m_device, stagingBufferMemory, 0, size, 0, &bufferData);
  memcpy(bufferData, data, size);
  vkUnmapMemory(m_device, stagingBufferMemory);

  VkBufferUsageFlags usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT
                           | VK_BUFFER_USAGE_TRANSFER_SRC_BIT
                           | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT;
  createBuffer(size, usage, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, m_buffer, m_bufferMemory);

  copyBuffer(stagingBuffer, m_buffer, size);

  vkDestroyBuffer(m_device, stagingBuffer, nullptr);
  vkFreeMemory(m_device, stagingBufferMemory, nullptr);

然后我运行着色器。为了检索结果,我像以前一样但相反:创建一个主机可见的暂存缓冲区,将设备本地缓冲区复制到其中,将暂存缓冲区内存映射到主机的地址空间,然后将其 memcpy 到我需要的位置。

  VkBuffer stagingBuffer;
  VkDeviceMemory stagingBufferMemory;
  createBuffer(m_bufferSize, VK_BUFFER_USAGE_TRANSFER_DST_BIT,
    VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
    stagingBuffer, stagingBufferMemory);

  copyBuffer(m_buffer, stagingBuffer, m_bufferSize);

  void* bufferData = nullptr;
  vkMapMemory(m_device, stagingBufferMemory, 0, m_bufferSize, 0, &bufferData);
  memcpy(data, bufferData, m_bufferSize);
  vkUnmapMemory(m_device, stagingBufferMemory);

  vkDestroyBuffer(m_device, stagingBuffer, nullptr);
  vkFreeMemory(m_device, stagingBufferMemory, nullptr);

有更快的方法吗?目前,运行时间(通过标准库的高分辨率时钟在主机端测量)主要是通过将缓冲区拉回主机来控制的 - 如此之多以至于它比我在 CPU 上完成所有操作要慢得多。

以下是一些示例图。这是针对一个大缓冲区运行的,这使得差异非常极端:

Running CPU benchmark...
Running time: 20.875 milliseconds

Running GPU benchmark...
Submit time = 41.817
Execution time = 1.066
Retrieval time = 223.479
Running time: 266.391 milliseconds

总结一下目前的整体流程:

  • 创建主机可见的暂存缓冲区和设备本地缓冲区
  • 将暂存缓冲区映射到主机的地址空间
  • 将数据复制到暂存缓冲区
  • 从主机地址空间取消暂存缓冲区的映射
  • 使用 vkCmdCopyBuffer 将暂存缓冲区复制到设备本地缓冲区
  • 删除暂存缓冲区及其 VkDeviceMemory
  • 通过记录和提交绑定相关管道和描述符集的命令缓冲区来执行着色器。
  • 再次创建一个暂存缓冲区(我尝试重用旧的缓冲区而不是创建一个新的缓冲区,这只会产生很小的差异)
  • 将设备本地缓冲区复制到暂存缓冲区
  • 将暂存缓冲区映射到主机的地址空间
  • 将数据复制出暂存缓冲区
  • 取消映射暂存缓冲区
  • 删除暂存缓冲区及其 VkDeviceMemory

可以采取什么措施来加快速度?

谢谢

shader vulkan gpgpu compute-shader
1个回答
0
投票

你的代码显示出很多粗俗和糟糕的做法。

您的代码将分配和释放设备内存视为 CPU 堆。它不是。您应该分配设备内存一次并保留它以供重复使用。如果您需要更大的分配,那么您可能需要进行一个分配(或者您之前应该分配更大的缓冲区),但总的来说,您应该永远只为单个操作分配内存。

此外,永远不要取消内存映射,除非您要删除它。保持主机可见内存映射没有任何缺点,并且映射它不是一个自由操作。

但最重要的问题是您的代码暗示它与 Vulkan 设备的关系。我没有看到任何命令缓冲区的直接使用,所以我必须假设像

copyBuffer
这样的函数将创建一个 CB 并向其写入传输操作。但我也没有看到任何同步操作或直接队列使用,这意味着此类函数必须also将 CB 提交到队列并等待队列操作完成。

永远不要这样做。

队列提交操作便宜。这个事实非常重要,以至于 Vulkan 规范实际上在文档中花费了时间来

vkQueueSubmit
来说明这一点。 这实际上是提交命令缓冲区部分中的 第一件事

提交可能是一项高开销操作,应用程序应尝试将批处理工作集中到尽可能少的对

vkQueueSubmit
vkQueueSubmit2
的调用中。

所以一定要这样做。如果您设计的 Vulkan 接口 API 无法实现这一点,那么您在设计层面就犯了错误,需要重新调整。

此外,永远不要等待队列空闲(或更糟糕的是设备空闲)。在 GPU 处理各种操作时去做其他事情,并在完成后返回(通过测试提交命令时使用的栅栏来验证这一点)。除非您没有其他操作要做,否则不要观望。

© www.soinside.com 2019 - 2024. All rights reserved.