使用 local/shared 内存作为全局缓存

Using local/shared memory as a cache for global

我有一个图像处理内核,它使用的标志缓冲区太大而无法放入本地内存。标志以可预测的光栅模式访问(从左上角到右下角)。

我的想法是将flags存放在global memory中,用local memory作为global的缓存。因此,当我沿着光栅模式前进时,我想从全局读取标志到本地,进行一些处理,然后将标志写回全局。但是,我想隐藏所涉及的延迟。

所以,假设我访问我的图像作为一系列位置:a1,a2,a3...... 我想执行以下操作:

  1. 获取 a1 标志
  2. 获取a2 标志
  3. 在获取 a2 标志时,处理 a1 位置并存储回去 到全局内存
  4. 获取a3 标志
  5. 在获取 a3 标志时,处理 a2 位置并存储回去 到全局内存
  6. 等等

我应该如何构建我的代码以确保隐藏延迟? 我需要使用 vload/vstore 来执行此操作吗?还是 GPU 硬件 延迟会自动隐藏吗?

无需手动执行此操作。 GPU 设备已经为您执行此操作。

计算核心分批执行一些工作项(warps),当批处理由于等待全局内存而无法继续时,它会同时启动另一个批处理并将该批处理置于休眠状态。

CUDA Surface 概念可能是适合您的案例的好工具。访问模式针对图像处理进行了优化,它使用纹理缓存,因此无需自己执行缓存。纹理缓存是按块的,因此您可能希望使用 2D 线程分布来让小方块由单个块处理。

隐藏延迟自然是通过调度比硬件可以同时处理的更多的线程和块来完成的。根据设备的 Compute Capability"Maximum number of resident threads per multiprocessor"(自 CC 3.0 起为 2048)与每个 SM 的 CUDA 核心数之间的比率将为您提供良好的提示计算要安排的线程总数(线程 * 块)以最好地隐藏延迟。请注意,最佳实际上取决于代码本身、内核需要的寄存器数量等。

关键是要确保合并读取 - 这是获得峰值内存带宽的唯一方法。然后,保持内核复杂度足够低,以便占用率足够高,以确保所有计算都隐藏在内存访问之后。那么你会以最快的速度运行。

看来您确实具备拥有高效内核的所有要求。 您可以预测数据访问模式,从 gmem 合并读取,并且您确实有一个复杂的处理。

因此硬件将通过在驻留块中选择 warp 来自动隐藏全局加载延迟 "in flight" 以在停滞的 warp 加载值时进行处理,但你必须足够清晰扭曲。我认为您可能会在这里处理两个挑战:

  • 没有足够的threads/blocks(您可能入住率低) 意味着当另一个 warp 正在从全局加载值时,硬件将无法选择准备好处理的 warp(即使您的内核使用了太多资源来启动,也可能发生这种情况,因此处理器只能处理几个一次,或者因为您只发布了一些);
  • 并行处理是不够的,所以即使你有一个很好的访问模式,但你的内核有很多 "sequential" 处理或者是执行依赖,甚至很多同步,所以扭曲会停止直到值被完全处理,并且由于所有飞行中的扭曲都停止了,因此即使要执行大量处理,您的性能也会很差;

很难说出你有哪一个,但一些实验可以帮助你。 Achieved Occupancy, Instruction Statistics, Branch Statistics and Issue Efficiency should help you to pinpoint the kernel limitation. You may even bump in a processo pipeline limitation.

请注意,"local" 内存在片外(作为全局内存),但某些硬件允许将其缓存在 L1 中。看起来你甚至可以使用共享内存来改进你的处理,因为 example.

所以基本上只要你有超过 80% 的可读扭曲,你就不应该有隐藏延迟的问题。