CUDA,使用2D和3D阵列

CUDA, Using 2D and 3D Arrays

本文关键字:3D 阵列 2D 使用 CUDA      更新时间:2023-10-16

关于CUDA上的二维和三维数组的分配、复制、索引等,网上有很多问题。我得到了很多相互矛盾的答案,所以我试图整理过去的问题,看看我是否能问对。

第一个链接:https://devtalk.nvidia.com/default/topic/392370/how-to-cudamalloc-two-dimensional-array-/

问题:分配指针的2d数组

用户解决方案:使用mallocPitch

"正确"的低效解决方案:在for循环中为每一行使用malloc和memcpy(荒谬的开销)

"更正确"的解决方案:将其压缩为1d数组"专业意见",一条评论称,没有一个关注性能的人在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上对基于指针的结构进行编码是一种糟糕的体验,而且效率很低,将其压缩为1d数组。

第三个环节:在CUDA 中的设备内存上分配2D阵列

问题:分配和传输二维阵列

用户解决方案:使用mallocPitch

其他解决方案:压平

第四个环节:如何在CUDA中使用2D阵列?

问题:分配和遍历二维阵列

提交的解决方案:不显示分配

其他解决方案:压缩

有很多其他来源大多都在说同样的话,但在很多情况下,我看到了关于GPU上指针结构的警告。

许多人声称,分配指针数组的正确方法是为每一行调用malloc和memcpy,但函数mallocPitch和memcpy2D仍然存在。这些功能是否在某种程度上效率较低?为什么这不是默认答案?

2d数组的另一个"正确"答案是将它们压缩成一个数组。我应该习惯这是生活中的一个事实吗?我对我的代码很挑剔,感觉很不雅。

我正在考虑的另一个解决方案是最大化一个使用1d指针数组的矩阵类,但我找不到实现双括号运算符的方法。

还根据此链接:将对象复制到设备?

以及子链接的答案:cudaMemcpy分段故障

这有点不确定。

我想使用CUDA的类都有2/3d数组,将这些数组转换为CUDA的1d数组不会有很多开销吗?

我知道我问了很多问题,但总的来说,我应该习惯于将压缩数组作为生活中的一个事实吗?或者我可以使用2d分配和复制函数而不会像在for循环中调用alloc和cpy的解决方案中那样产生糟糕的开销吗?

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

cudaMallocPitch/cudaMemcpy2D:

首先,像cudaMallocPitchcudaMemcpy2D这样的cuda运行时API函数实际上并不涉及双帧分配或2D(双下标)数组。这很容易通过查看文档和注意函数原型中的参数类型来确认。CCD_ 3和CCD_ 4参数是单指针参数。它们不能被双重下标或双重取消引用。对于其他示例用法,这里是关于此问题的众多问题之一。这里是一个完整的使用示例。这里是涵盖与cudaMallocPitch/cudaMemcpy2d使用相关联的各种概念的另一个示例。相反,思考这些问题的正确方法是,它们与倾斜分配一起工作。此外,当使用循环中的一组malloc(或new或类似操作)创建基础分配时,不能使用cudaMemcpy2D传输数据。这种主机数据分配结构特别不适合处理设备上的数据。

常规、动态分配的2D案例:

如果你想学习如何在CUDA内核中使用动态分配的2D数组(这意味着你可以使用双下标访问,例如data[x][y]),那么cuda标签信息页面包含";规范的";这个问题就在这里。talonmies给出的答案包括适当的机制,以及适当的注意事项:

  • 还有额外的、非平凡的复杂性
  • 该访问通常将不如1D访问有效,因为数据访问需要解引用2个指针,而不是1个

(注意,分配一个对象数组,其中对象有一个嵌入的指向动态分配的指针,本质上与2D数组概念相同,您在问题中链接的例子就是一个合理的证明)

此外,这里是一种用于构建通用动态分配2D阵列的推力方法。

压扁:

如果你认为你必须使用通用的2D方法,那么继续吧,这不是不可能的(尽管有时人们会在这个过程中挣扎!)然而,由于增加了复杂性和降低了效率;建议";这是为了";"压平";您的存储方法,并使用";模拟的";2D访问。这里是讨论";扁平化";。

常规、动态分配的3D案例:

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

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

请注意,当数组维度(对于2D数组,为宽度,对于3D数组,为3个维度中的2个)在编译时已知时,应将其视为<em]特例>。在这种情况下,通过适当的辅助类型定义,我们可以";指示";编译器应该如何计算索引,在这种情况下,我们可以使用双下标访问,其复杂性比一般情况低得多,不会因为指针追逐而损失效率。只需取消引用一个指针即可检索数据(无论数组维度如何,如果n维数组在编译时已知n-1个维度)。这里已经提到的答案中的第一个代码示例(第一个代码实例)给出了3D情况下的完整示例,而这里的答案给出了这种特殊情况的2D示例。

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

最后,另一种方法选项允许我们在主机代码中轻松混合2D(双下标)访问,而在

设备代码从以上方法中,你需要选择一种符合你胃口和需求的方法。没有一项建议适合所有可能的情况。

这个答案对两类实现/方法之间的差异进行了更细粒度的解释。