克服 CUDA 中的复制开销

问题描述

我想使用 CUDA 在 GPU 上并行化图像操作,为图像的每个像素(或像素组)使用一个线程。操作非常简单:每个像素乘以一个值。

但是,如果我理解正确,为了将图像放在 GPU 上并进行并行处理,我必须将其复制到统一内存或其他一些 GPU 可访问的内存中,这基本上是一个 double for 循环就像在 cpu 上处理图像的那个一样。我想知道是否有更有效的方法在 GPU 上复制图像(即 1D 或 2D 阵列),并且没有开销,因此并行化是无用的。

解决方法

但是,如果我理解正确,为了将图像放在 GPU 上并进行并行处理,我必须将其复制到统一内存或其他一些 GPU 可访问的内存中

你理解正确。

我想知道是否有更有效的方法在没有开销的 GPU 上复制图像(即一维或二维数组)

没有。主机系统内存中的数据必须通过 PCIE 总线才能到达 GPU 内存。这受 PCIE 总线带宽的限制(对于 PCIE Gen3 约为 12GB/s),并且还有一些与之相关的“固定开销”,至少在每次传输几微秒的数量级上,因此非常小的传输看起来更糟从性能(字节/秒)的角度来看。

这样并行化就没有用了。

如果您想要执行的唯一操作是获取图像并将每个像素乘以一个值,并且图像由于某种原因尚未在 GPU 上,那么正常人不会为此使用 GPU(可能除了用于学习目的)。在性能开始变得有趣之前,您需要为 GPU 找到更多涉及的工作

操作很简单

这通常不是 GPU 加速带来的性能优势的良好指标。

,

当你说“这基本上是一个双循环,就像在 CPU 上处理图像的循环一样”,我希望你不是指在每一行然后每一列上逐个像素地复制。您可以使用 memcpy 复制整个图像。然而,正如其他人所说,在 CPU 和 GPU 之间移动数据仍然有相当大的开销,除非您在 GPU 上的计算复杂到足以证明开销是合理的。

,

您可以隐藏一些复制延迟。在复制图像输入的补丁时,您可以同时从 GPU 上的先前计算中复制回结果补丁。在重叠的双向副本之上,可以运行第三个补丁的计算。这既可以缩短单个图像处理的总延迟,也可以缩短多个图像处理的总延迟(但这次隐藏了整个图像处理的延迟)。

对于一个很简单的处理,只有读和写可以互相隐藏。简单的计算没有有意义的延迟来隐藏其他任何东西。因此,通过流水线,您可以将性能提高 100%(假设输入 1 个图像,输出 1 个相同大小的图像,并且 pcie/驱动程序在两个方向上的性能相同)。

如果每个像素只是乘以一个值,那么它是令人尴尬的并行,您可以通过使用任意大小的块进行流水线操作来隐藏延迟。例如,

  • 将 N 行像素复制到 vram
  • 计算 N 行并同时复制 N 行新行到 vram
  • 将 N 个结果复制回 ram,(同时)计算 N 个新行,(同时/异步)将最新的 N 行复制到 vram
  • ...
  • 将最后的结果复制回内存

您可以为每个运行中的 N 扫描线使用 1 个流(进行读取+计算+写入)并让驱动程序选择扫描线计算的最佳重叠,或者每个操作类型使用 1 个流(1 个用于所有写入, 1 用于所有读取,1 用于所有计算)并使用事件显式维护重叠行为。

如果您对每个像素进行更多计算,例如等于复制的延迟,那么流水线将为您提供 3 倍的性能(2 个其他操作隐藏在 1 之后)。