网上有很多关于CUDA上的2D和3D数组的分配、复制、索引等问题。我得到了很多相互矛盾的答案,所以我试图整理过去的问题,看看我是否可以问正确的问题。
第一个链接:https://devtalk.nvidia.com/default/topic/392370/how-to-cudamalloc-two-dimensional-array-/
问题:分配2d指针数组
用户解决方案:使用mallocPitch
“正确”低效解决方案:在for循环中对每一行使用malloc和memcpy(荒谬的开销)
“更正确”的解决方案:压缩成一个1d数组“专业意见”,一个评论说,没有人的眼睛在性能上使用2d指针结构的gpu。
第二个链接: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数组中。
链接:Allocate 2D Array on Device Memory in CUDA
问题:分配和传输2d数组
用户解决方案:使用mallocPitch
其他解决方案:把它压平
第四环节:How to use 2D Arrays in CUDA?
问题:分配和遍历2d数组
提交的解决方案:不显示分配
其他解决方案:压扁它
有很多其他来源大多说同样的事情,但在多个示例中,我看到关于GPU上的指针结构的警告。
许多人声称分配指针数组的正确方法是为每行调用malloc和memcpy,但函数mallocPitch和memcpy 2D存在。这些函数是否效率较低?为什么这不是默认答案?
对于二维数组的另一个“正确”答案是将它们压缩到一个数组中。我应该习惯于这一事实吗?我对我的代码非常挑剔,这对我来说是不优雅的。
我考虑的另一个解决方案是max一个使用1d指针数组的矩阵类,但我找不到实现双括号运算符的方法。
根据这个链接:Copy an object to device?
子链路回答:cudaMemcpy分段错误
这有点不确定。
我想使用CUDA的类都有2/3d数组,将它们转换为CUDA的1d数组不会有很多开销吗?
我知道我问了很多,但总而言之,我应该习惯于将数组压扁作为生活的事实,还是可以使用2d分配和复制函数而不会产生不良开销,就像在for循环中调用alloc和cpy的解决方案一样?
1条答案
按热度按时间neskvpey1#
既然你的问题包含了一系列其他问题,那么我就列出一系列其他答案来回答你。
cudaMallocPitch/cudaMemcpy2D:
首先,cuda运行时API函数(如
cudaMallocPitch
和cudaMemcpy2D
)实际上不涉及双指针分配或2D(双下标)数组。这很容易通过查看文档并注意函数原型中的参数类型来确认。src
和dst
参数是单指针参数。它们不能被双下标,或双重解引用。对于其他示例用法,这里是关于此的许多问题之一。这里是一个完整的示例用法。另一个涵盖与cudaMallocPitch
/cudaMemcpy2d
用法相关的各种概念的示例在这里。相反,正确的思考方式是它们与 pitched 分配一起工作。此外,如果已使用一组malloc
创建基础分配,则不能使用cudaMemcpy2D
传输数据(或new
,这种类型的主机数据分配构造特别不适合于处理设备上的数据。常规,动态分配的2D用例:
如果你想学习如何在CUDA内核中使用动态分配的2D数组(这意味着你可以使用双下标访问,例如
data[x][y]
),那么cuda
tag info page包含了这个“规范”问题,它是here。Talonmies给出的答案包括适当的机制,以及适当的警告:(note分配一个对象数组,其中对象有一个指向动态分配的嵌入指针,本质上与2D数组概念相同,你在问题中链接的the example是一个合理的演示)
此外,here是用于构建通用动态分配2D阵列的推力方法。
展平:
如果你认为你必须使用一般的2D方法,那么就去吧,这不是不可能的(尽管有时候这个过程是people struggle!)然而,由于增加的复杂性和降低的效率,这里的规范“建议”是“扁平化”你的存储方法,并使用“模拟”2D访问。这里是讨论“扁平化”的许多问题/答案的例子之一。
一般动态分配3D用例:
当我们将其扩展到3(或更高!)维时,一般情况变得过于复杂,难以处理,IMO。额外的复杂性应该强烈地激励我们寻求替代方案。三下标的一般情况在实际检索数据之前涉及3个指针访问,因此效率更低。下面是一个完整的示例(第二个代码示例)。
特殊情况:编译时已知的数组宽度:
请注意,当数组维度为(s)(width,在2D数组的情况下,或3D数组的3个维度中的2个)在编译时是已知的。在这种情况下,通过适当的辅助类型定义,我们可以“指示”编译器应该如何计算索引,在这种情况下,我们可以使用双下标访问,其复杂性比一般情况低得多,* 和 * 没有由于指针追逐而导致的效率损失。只需要解引用一个指针来检索数据(不考虑数组维度,如果对于n维数组在编译时已知n-1维)。这里已经提到的答案中的第一个代码示例(第一个代码示例)给出了3D情况下的完全工作示例,并且这里的答案给出了这种特殊情况的2D示例。
双下标主机代码,单下标设备代码:
最后,另一种方法选项允许我们轻松地混合2D仅使用1D时 * 主机代码 * 中的(双下标)访问(单下标,可能具有“模拟2D”访问)。这里有一个工作示例。通过将底层分配组织为连续分配,然后构建指针“树”,我们可以在主机上启用双下标访问,尽管该示例没有示出,但是可以扩展该方法以基于平面分配和手动创建的指针“树”在设备上创建双下标访问系统,然而,这将具有与上面给出的2D通用动态分配方法大致相同的问题:它将涉及双指针(双解引用)访问,因此效率较低,并且存在与构建指针“树”相关联的一些复杂性,以用于设备代码(例如,可能需要附加的
cudaMemcpy
操作)。从上述方法中,你需要选择一个适合你的胃口和需要。没有一个单一的建议,适合每一个可能的情况。
这个答案进入了对两类实现/方法之间的差异的更细粒度的解释。