克服 CUDA 中的复制开销
Overcoming the copy overhead in CUDA
我想使用 CUDA 在 GPU 上并行化图像操作,为图像的每个像素(或像素组)使用一个线程。操作很简单:每个像素乘以一个值。
但是,如果我理解正确的话,为了将图像放在 GPU 上并并行处理,我必须将它复制到统一内存或其他一些 GPU 可访问的内存,这基本上是一个双for 循环就像处理 CPU 上的图像的循环一样。我想知道是否有更有效的方法来在 GPU 上复制图像(即一维或二维数组),而不会产生并行化无用的开销。
However, if I understand it correctly, in order to put the image on the GPU and have it processed in parallel, I have to copy it to unified memory or some other GPU-accessible memory
你没看错。
I am wondering whether there is a more efficient way to copy an image (i.e. a 1D or 2D array) on the GPU that does not have an overhead
没有。主机系统内存中的数据必须通过 PCIE 总线才能到达 GPU 内存。这受限于 PCIE 总线带宽(对于 PCIE Gen3 约为 12GB/s),并且还有一些与之相关的“固定开销”,至少每次传输几微秒的数量级,因此非常小的传输似乎更糟从性能 (bytes/s) 的角度出发。
such that the parallelization is useless.
如果您想要执行的唯一操作是获取图像并将每个像素乘以一个值,并且由于某种原因该图像尚未在 GPU 上,那么头脑正常的人不会为此使用 GPU(除了可能出于学习目的)。在性能开始变得有趣之前,您需要为 GPU 找到更多复杂的工作
The operation is quite simple
这通常不是 GPU 加速带来的性能优势的良好指标。
当你说“这基本上是一个双 for 循环,就像在 CPU 上处理图像的循环一样”,我希望你不是指在每一行上逐个像素地复制,然后再逐个复制柱子。您可以使用 memcpy 复制整个图像。然而,正如其他人所说,在 CPU 和 GPU 之间移动数据仍然存在相当大的开销,除非您在 GPU 上的计算足够复杂以证明开销是合理的。
您可以隐藏一些复制延迟。在复制图像输入补丁时,您可以同时从 GPU 上的先前计算中复制回结果补丁。在重叠的双向副本之上,第三个补丁的计算可以是运行。这可以缩短单个图像处理或多个图像处理的总延迟(但这次隐藏了整个图像处理的延迟)。
一个很简单的处理,只读和写可以相互隐藏。简单的计算没有有意义的延迟来隐藏其他任何东西。因此,通过 pipelininig,您可以将性能提高 100%(假设输入 1 张图像,输出 1 张大小相等的图像,并且 pcie/driver 对两个方向执行相同的操作)。
如果每个像素只是乘以一个值,那么它是令人尴尬的并行,您可以通过流水线处理任意大小的块来隐藏延迟。例如,
- 复制N行像素到vram
- 计算 N 行并同时将 N 行新行复制到 vram
- 将 N 个结果复制回 ram,(同时)计算 N 个新行,(concurrently/asynchronously)将最新的 N 个行复制到 vram
- ...
- 将最后的结果复制回 ram
您可以为每个运行中的 N 扫描线使用 1 个流(以执行读取+计算+写入)并让驱动程序选择扫描线计算的最佳重叠,或者为每个操作类型使用 1 个流(1 个用于所有写入,1 个用于所有读取,1 个用于所有计算)并使用事件显式维护重叠行为。
如果你对每个像素进行更多的计算,比如等于复制的延迟,那么流水线将为你提供 3 倍的性能(隐藏在 1 后面的 2 个其他操作)。
我想使用 CUDA 在 GPU 上并行化图像操作,为图像的每个像素(或像素组)使用一个线程。操作很简单:每个像素乘以一个值。
但是,如果我理解正确的话,为了将图像放在 GPU 上并并行处理,我必须将它复制到统一内存或其他一些 GPU 可访问的内存,这基本上是一个双for 循环就像处理 CPU 上的图像的循环一样。我想知道是否有更有效的方法来在 GPU 上复制图像(即一维或二维数组),而不会产生并行化无用的开销。
However, if I understand it correctly, in order to put the image on the GPU and have it processed in parallel, I have to copy it to unified memory or some other GPU-accessible memory
你没看错。
I am wondering whether there is a more efficient way to copy an image (i.e. a 1D or 2D array) on the GPU that does not have an overhead
没有。主机系统内存中的数据必须通过 PCIE 总线才能到达 GPU 内存。这受限于 PCIE 总线带宽(对于 PCIE Gen3 约为 12GB/s),并且还有一些与之相关的“固定开销”,至少每次传输几微秒的数量级,因此非常小的传输似乎更糟从性能 (bytes/s) 的角度出发。
such that the parallelization is useless.
如果您想要执行的唯一操作是获取图像并将每个像素乘以一个值,并且由于某种原因该图像尚未在 GPU 上,那么头脑正常的人不会为此使用 GPU(除了可能出于学习目的)。在性能开始变得有趣之前,您需要为 GPU 找到更多复杂的工作
The operation is quite simple
这通常不是 GPU 加速带来的性能优势的良好指标。
当你说“这基本上是一个双 for 循环,就像在 CPU 上处理图像的循环一样”,我希望你不是指在每一行上逐个像素地复制,然后再逐个复制柱子。您可以使用 memcpy 复制整个图像。然而,正如其他人所说,在 CPU 和 GPU 之间移动数据仍然存在相当大的开销,除非您在 GPU 上的计算足够复杂以证明开销是合理的。
您可以隐藏一些复制延迟。在复制图像输入补丁时,您可以同时从 GPU 上的先前计算中复制回结果补丁。在重叠的双向副本之上,第三个补丁的计算可以是运行。这可以缩短单个图像处理或多个图像处理的总延迟(但这次隐藏了整个图像处理的延迟)。
一个很简单的处理,只读和写可以相互隐藏。简单的计算没有有意义的延迟来隐藏其他任何东西。因此,通过 pipelininig,您可以将性能提高 100%(假设输入 1 张图像,输出 1 张大小相等的图像,并且 pcie/driver 对两个方向执行相同的操作)。
如果每个像素只是乘以一个值,那么它是令人尴尬的并行,您可以通过流水线处理任意大小的块来隐藏延迟。例如,
- 复制N行像素到vram
- 计算 N 行并同时将 N 行新行复制到 vram
- 将 N 个结果复制回 ram,(同时)计算 N 个新行,(concurrently/asynchronously)将最新的 N 个行复制到 vram
- ...
- 将最后的结果复制回 ram
您可以为每个运行中的 N 扫描线使用 1 个流(以执行读取+计算+写入)并让驱动程序选择扫描线计算的最佳重叠,或者为每个操作类型使用 1 个流(1 个用于所有写入,1 个用于所有读取,1 个用于所有计算)并使用事件显式维护重叠行为。
如果你对每个像素进行更多的计算,比如等于复制的延迟,那么流水线将为你提供 3 倍的性能(隐藏在 1 后面的 2 个其他操作)。