CUDA,使用2D和3D数组。

3
有关在CUDA上分配、复制、索引2D和3D数组等问题,网上有很多讨论。但是得到了很多相互矛盾的答案,因此我正在尝试编译以前的问题,看看是否能提出正确的问题。
第一个链接:https://devtalk.nvidia.com/default/topic/392370/how-to-cudamalloc-two-dimensional-array-/
问题:分配一个二维指针数组
用户解决方案:使用mallocPitch
“正确”的低效解决方案:对于每行使用malloc和memcpy的for循环(荒谬的开销)
“更正确”的解决方案:将它压缩成一维数组,“专业意见”,一条评论说没有人会在GPU上使用2D指针结构来考虑性能。
第二个链接:https://devtalk.nvidia.com/default/topic/413905/passing-a-multidimensional-array-to-kernel-how-to-allocate-space-in-host-and-pass-to-device-/
问题:在主机上分配空间并将其传递到设备上
子链接:https://devtalk.nvidia.com/default/topic/398305/cuda-programming-and-performance/dynamically-allocate-array-of-structs/
子链接解决方案:在GPU上编写基于指针的结构是一种不好的体验,而且效率非常低下,将其压缩为一维数组。
第三个链接:https://dev59.com/1XNA5IYBdhLWcg3wNrAy
问题:分配和传输2D数组
用户解决方案:使用mallocPitch
其他解决方案:将其压平
第四个链接:https://dev59.com/pW445IYBdhLWcg3wH2rH
问题:分配和遍历2D数组
提交的解决方案:不显示分配
其他解决方案:将其压缩
还有很多其他的来源,大多数都在说同样的事情,但在多个实例中,我看到了关于GPU上指针结构的警告。
许多人声称分配指针数组的正确方法是对于每行调用malloc和memcpy,但是函数mallocPitch和memcpy2D存在。这些功能是否不够高效?为什么不是默认答案?
另一种“正确”的处理二维数组的方法是将它们压缩成一个数组。我应该把这视为生活的事实并习惯吗?但我对我的代码非常挑剔,觉得这样很不优雅。
我正在考虑的另一种解决方案是使用一个矩阵类来完成,该类使用一个一维指针数组,但我找不到实现双括号操作符的方法。
另外根据这个链接:Copy an object to device? 和子链接的答案:cudaMemcpy segmentation fault ,情况有些棘手。
我想要在CUDA中使用的类都有2/3d数组,将它们转换为CUDA的1d数组会有很多开销吗?
总之,我问了很多问题,总结一下,我应该习惯压缩数组作为生活的事实,还是可以使用2d分配和复制函数而不像在调用for循环中调用alloc和cpy函数那样产生大量开销?
1个回答

11

由于您的问题汇编了其他问题的列表,我将通过编制其他答案的列表来回答。

cudaMallocPitch/cudaMemcpy2D:

首先,cuda运行时API函数,如cudaMallocPitchcudaMemcpy2D实际上并不涉及双指针分配或二维(双下标)数组。这很容易通过查看文档并注意函数原型中参数的类型来确认。 srcdst参数是单指针参数。它们不能是双下标或双重引用。有关其他示例用法,请参见此处,其中包括许多问题。此处是一个完整的示例用法。另一个涵盖与cudaMallocPitch/cudaMemcpy2d使用相关的各种概念的示例是此处。相反,正确的思考方式是它们与pitched分配一起工作。此外,当使用一组malloc(或new或类似操作)在循环中创建基础分配时,您不能使用cudaMemcpy2D传输数据。这种主机数据分配构造特别不适合在设备上处理数据。

通用的,动态分配的二维数组情况:

如果您希望了解如何在CUDA内核中使用动态分配的二维数组(意味着您可以使用双下标访问,例如data[x][y]),那么cuda标签信息页面 包含了这个问题的“规范”问题,它在这里。 talonmies给出的答案包括适当的机制和适当的注意事项:

  • 有额外的,非平凡的复杂性
  • 访问通常比1D访问效率低,因为数据访问需要解引用2个指针,而不是1个。

(请注意,分配一个对象数组,其中对象嵌入指向动态分配的指针,与二维数组概念基本相同,而您在问题中链接的示例 是一个合理的演示)

此外,这里还有一种构建通用动态分配的二维数组的推进方法。

扁平化:

如果您认为必须使用通用的二维方法,请继续,这并不是不可能的(尽管有时人们会遇到困难!)。然而,由于增加了复杂性和降低了效率,因此在此规范的“建议”是“扁平化”存储方法,并使用“模拟”的二维访问。 这里是许多问题/答案讨论“扁平化”的示例之一。

通用的、动态分配的三维情况:

当我们将其扩展到三维(或更高维)时,一般情况变得过于复杂难以处理,在我看来。 这种额外的复杂性应强烈激励我们寻求替代方案。 三次下标的一般情况在数据实际检索之前涉及3个指针访问,因此效率更低。 这里是一个完整的示例(第2个代码示例)。

特殊情况:数组宽度在编译时已知:

请注意,当数组维度(对于2D数组而言,是宽度;对于3D数组而言,是其中的2个维度)在编译时已知时,应将其视为特殊情况。在这种情况下,通过适当的辅助类型定义,我们可以“指示”编译器如何计算索引,并且在这种情况下,我们可以使用双重下标访问,其复杂度比一般情况要小得多,并且由于指针追踪,没有效率损失。只需要解除引用一个指针即可检索数据(无论数组维数如何,如果对于n维数组,有n-1个维度在编译时已知)。已经提到的答案here(第一个代码示例)给出了3D情况下的完整示例,答案here给出了这种特殊情况下的2D示例。

双下标主机代码,单下标设备代码:

最后,另一种方法允许我们在主机代码中轻松混合2D(双下标)访问,同时在设备代码中仅使用1D(单下标,可能带有“模拟2D”访问)。一个示例可以在这里找到。通过将底层分配组织为连续分配,然后构建指针“树”,我们可以在主机上启用双下标访问,并仍然轻松地将平坦分配传递给设备。虽然示例没有显示出来,但是可以扩展此方法以基于平坦分配和手动创建的指针“树”在设备上创建双下标访问系统,但是这将具有与上述给出的2D通用动态分配方法大致相同的问题:它将涉及双指针(双解引用)访问,因此效率较低,并且在构建指针“树”时存在一些复杂性,供设备代码使用(例如,这可能需要额外的cudaMemcpy操作)。

从上述方法中,您需要选择适合您胃口和需求的方法。并没有一个单一的建议适用于每种可能的情况。 这个答案更详细地解释了两种实现/方法之间的差异。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接