这里是初学者。刚刚完成官方 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
总结一下目前的整体流程:
可以采取什么措施来加快速度?
谢谢
你的代码显示出很多粗俗和糟糕的做法。
您的代码将分配和释放设备内存视为 CPU 堆。它不是。您应该分配设备内存一次并保留它以供重复使用。如果您需要更大的分配,那么您可能需要进行一个分配(或者您之前应该分配更大的缓冲区),但总的来说,您应该永远只为单个操作分配内存。
此外,永远不要取消内存映射,除非您要删除它。保持主机可见内存映射没有任何缺点,并且映射它不是一个自由操作。
但最重要的问题是您的代码暗示它与 Vulkan 设备的关系。我没有看到任何命令缓冲区的直接使用,所以我必须假设像
copyBuffer
这样的函数将创建一个 CB 并向其写入传输操作。但我也没有看到任何同步操作或直接队列使用,这意味着此类函数必须also将 CB 提交到队列并等待队列操作完成。
永远不要这样做。
队列提交操作不便宜。这个事实非常重要,以至于 Vulkan 规范实际上在文档中花费了时间来
vkQueueSubmit
来说明这一点。 这实际上是提交命令缓冲区部分中的 第一件事:
提交可能是一项高开销操作,应用程序应尝试将批处理工作集中到尽可能少的对
或vkQueueSubmit
的调用中。vkQueueSubmit2
所以一定要这样做。如果您设计的 Vulkan 接口 API 无法实现这一点,那么您在设计层面就犯了错误,需要重新调整。
此外,永远不要等待队列空闲(或更糟糕的是设备空闲)。在 GPU 处理各种操作时去做其他事情,并在完成后返回(通过测试提交命令时使用的栅栏来验证这一点)。除非您没有其他操作要做,否则不要观望。