使用 Vulkan® 设备内存

最初发布时间:
Timothy Lottes's avatar
Timothy Lottes

本教程旨在指导如何在 AMD 驱动的 Vulkan 中最好地利用各种内存堆和内存类型,并提供一些高级技巧。

  • GPU 批量数据 将 GPU 端分配放在 DEVICE_LOCAL 中,但不要 HOST_VISIBLE。确保首先分配优先级最高的资源,如渲染目标和更常访问的资源。一旦 DEVICE_LOCAL 填满且分配失败,如果需要,可以通过 HOST_VISIBLE(带 HOST_COHERENT 但不带 HOST_CACHED)将低优先级分配回退到 CPU 端内存。在进行游戏内重新分配(例如,由于显示分辨率更改)时,请务必在尝试进行任何新分配之前完全释放所有涉及的分配。这可以最大程度地减少分配无法适应 GPU 端堆的可能性。
  • CPU 到 GPU 数据流 对于相对较小的总分配大小(256 MB 以下),DEVICE_LOCAL with HOST_VISIBLE 是 CPU 上传到 GPU 的理想内存类型:CPU 可以直接写入 GPU 内存,然后 GPU 可以访问而无需跨 PCIe 总线读取。这对于上传常量数据等非常有用。
  • GPU 到 CPU 数据流 使用 HOST_VISIBLE with HOST_COHERENT and HOST_CACHED。这是唯一支持 CPU 缓存读取的内存类型。非常适合录制屏幕截图、反馈分层 Z 缓冲区遮挡测试等场景。

内存池化分配

Axel Gneiting(id Software 的 DOOM® Vulkan 实现负责人)的一条很棒的提醒是,要确保将一组资源(如纹理和缓冲区)池化到一个单一内存分配中。例如,在 Windows® 7 上,Vulkan 内存分配会映射到 WDDM 分配(在 GPUView 中看到的相同列表),并且 WDDM 分配会产生相对较高的成本,因为命令缓冲区会流经基于 WDDM 的驱动程序堆栈。每个 DEVICE_LOCAL 分配 256 MB 是一个不错的起点,填充 4 GB 只需要 16 次分配。

隐藏分页

当应用程序开始过度订阅 GPU 端内存时,DEVICE_LOCAL 内存分配将失败。另外,在应用程序执行过程中,系统中另一个应用程序可能会增加其 GPU 端内存使用量,从而导致 GPU 端内存的动态过度订阅。这种情况可能导致操作系统(例如 Windows® 7)在 GPU 上对每个应用程序的执行进行时间切片时,自动将 GPU 端分配迁移到/从 CPU 端。这可能导致明显的“卡顿”。目前没有直接查询操作系统是否在 Vulkan 中迁移分配的方法。一个可能的变通方法是让应用程序通过查看时间戳来检测卡顿,然后主动尝试减少 DEVICE_LOCAL 内存消耗。例如,应用程序可以手动移动资源以完全清空 DEVICE_LOCAL 分配,然后将其释放。

针对低内存 GPU

在内存充足的情况下,使用 DEVICE_LOCAL+HOST_VISIBLE 进行 CPU 写入可以避免安排额外的复制。但是,在内存受限的情况下,使用 DEVICE_LOCAL+HOST_VISIBLE 作为 DEVICE_LOCAL 堆的扩展,并将其用于纹理和缓冲区等 GPU 资源会更好。CPU 写入可以切换到 HOST_VISIBLE+COHERENT。性能的首要任务是保持高带宽访问资源在 GPU 端内存中。

内存堆和内存类型 – 技术细节

驱动程序设备内存堆和内存类型可以使用 Vulkan Hardware Database 进行检查。对于 Windows AMD 驱动程序,下面分解了所有内存类型的特征和最佳使用模型。Vulkan 规范不保证堆和内存类型的编号,因此请务必直接从属性标志进行工作。另请注意,Vulkan 中报告的内存大小代表在应用程序和驱动程序之间共享的最大数量。

  • 堆 0
    • VK_MEMORY_HEAP_DEVICE_LOCAL_BIT
    • 代表 GPU 设备上的内存,无法映射到主机系统内存。
    • 对于缓冲区和图像的集合,每次 vkAllocateMemory() 分配使用 256 MB 是一个不错的起点。
    • 建议为可能需要在运行时调整大小(释放和重新分配)的大型分配使用单独的分配。
    • 内存类型 0
      • VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT
      • GPU 读取/写入/原子操作全速。
      • 无法使用 vkMapMemory() 映射到主机系统地址空间。
      • 用于标准的 GPU 端数据。
  • 堆 1
    • VK_MEMORY_HEAP_DEVICE_LOCAL_BIT
    • 代表 GPU 设备上的内存,可以映射到主机系统内存。
    • 在 Windows 上限制为 256 MB。
      • 最好为每个 vkAllocateMemory() 分配最多分配 64 MB。
      • 如有必要,回退到较小的分配。
    • 内存类型 1
      • VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT
      • VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT
      • VK_MEMORY_PROPERTY_HOST_COHERENT_BIT
      • GPU 读取/写入/原子操作全速。
      • 可以使用 vkMapMemory() 映射到主机系统地址空间。
      • CPU 写入是写组合的,直接写入 GPU 内存。
        • 最好写入完整对齐的缓存行大小的块。
      • CPU 读取是未缓存的。
        • 最好为 GPU 写入和 CPU 读取场景改用内存类型 3。
      • 用于动态缓冲区数据,以避免额外的从主机到设备的复制。
      • 用于在堆 0 空间不足之前作为回退,然后才求助于堆 2。
  • 堆 2
    • 代表主机系统上的内存,可供 GPU 访问。
    • 建议使用与堆 0 相似的分配大小策略。
    • 可以使用 vkMapMemory()
    • GPU 读取纹理和缓冲区的缓存位于 GPU L2。
      • GPU L2 缺失会通过 PCIe 总线读取到主机系统内存。
      • L2 缺失时延迟更高,吞吐量更低。
    • 在 Tonga 及之后的 GPU(如 FuryX)中,GPU 读取索引缓冲区的缓存位于 GPU L2。
    • 内存类型 2
      • VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT
      • VK_MEMORY_PROPERTY_HOST_COHERENT_BIT
      • CPU 写入是写组合的。
      • CPU 读取是未缓存的。
      • 用于向 GPU 设备上传的暂存。
      • 当 GPU 设备在堆 0 和堆 1 中内存不足时可用作回退。
    • 内存类型 3
      • VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT
      • VK_MEMORY_PROPERTY_HOST_COHERENT_BIT
      • VK_MEMORY_PROPERTY_HOST_CACHED_BIT
      • CPU 读取和写入通过 CPU 缓存层次结构进行。
      • GPU 读取会窥探 CPU 缓存。
      • 用于从 GPU 设备下载的暂存。

选择正确的内存堆和内存类型是优化的关键任务。例如,Radeon™ Fury X 这样的 GPU 具有 512 GB/s 的 DEVICE_LOCAL 带宽(读写任意比例的总和),但 PCIe 总线最多支持 16 GB/s 的读取和 16 GB/s 的写入,双向总共为 32 GB/s。

Timothy Lottes's avatar

Timothy Lottes

Timothy Lottes 是 AMD 图形性能研发团队的成员。提供第三方网站链接仅为方便用户,除非明确说明,AMD 对此类链接网站的内容概不负责,且不暗示任何认可。
© . This site is unofficial and not affiliated with AMD.