只要有人在 StackOverflow 或 nVidia 论坛上询问有关 CUDA 中的多维数组的问题,答案或多或少如下所示:
请将多维数组展平为一维。
这是一个示例解决方案:
//Implementation of a CUDA program using a 1D array. ... ... ...
我对此感到困惑
在 CUDA 中传递和操作多维数组是不可能的,还是出于性能原因没有这样做?
C++ 中的多维数组定义属于两类之一。
第一类是当编译器知道或可以在编译时发现数组宽度。在这种情况下,对于多订阅访问,例如
array[x][y]
,编译器知道array
的宽度(即对应于第二个下标的维度中的元素数),并且“在引擎盖下将生成这样的索引:
*(a+x*awidth+y)
所有这些项的含义与它们在 C++ 中的含义完全相同:
a
是数组的“名称”,在执行此类指针运算时会衰减为指针,x
是第一个下标,y
是第二个下标,awidth
是编译器在编译时为 a
发现的宽度。根据 C++ 定义,a
是一个 array 而不是其他任何东西。
在这种情况下,当允许“衰减”到指针时,
a
将指向一个位置,该位置不包含指针,但包含与数组a
相同类型的元素。
在这个类别中,只生成一次对内存的访问,它检索有问题的
a
的元素。我会称之为“有效”案例,尽管这是主观的,所以我不想争论它。
另一种类型的多下标数组可以使用“指针追逐”来构造。在这种情况下,
a
不是一个array,它是一个双指针:
T **a;
在这种情况下,
a
指向一个位置,该位置包含指向数组a
元素类型的指针(更典型的是,a
指向“行指针”数组的第一个元素),因此,我将 a
称为“双指针”,并且正式地,a
没有命名 array。但是,在典型用法中,我们可以使用相同的语法引用a
的元素:a[x][y]
。 “幕后”的编译器不会生成我们之前介绍的指针算法,而是生成两个连续的指针取消引用操作:
a
(已经是一个指针)以检索另一个指针。a
在上面我们看到需要two内存操作。第一个检索指针,第二个检索有问题的元素。
我称之为“低效”方法。第一种方法正式使用“数组”,第二种方法不是,但在典型的双下标用法中,它们在语法上看起来是相同的。
所有这些讨论都适用于 C++ 编译器行为,并不是 CUDA 独有或特定的。
如果你小心翼翼地让 CUDA 编译器可以在编译时发现你的数组宽度(并非总是可能),那么它在指针算法、指针解引用和涉及的内存操作数方面的行为是相同的,因为我对上面第一个案例的描述。否则,如果您使用第二种方法声明您的“数组”,每次访问将获得多个内存操作(当然,编译器也可能缓存第一个解引用操作的结果,这可能会提供一些好处,但这不是保证的行为在所有情况下,并且只在某些重用的情况下提供好处,而不是在一般情况下。)
这个答案 提供了对 CUDA 中 2D 和 3D 示例的调查,以及一些权衡的讨论。
对于正在或必须使用第二种类型的人来说,展平数组总是可行的,使用展平的替代方案将导致一种访问方法,其行为大致与上面的第一种方法相同:只需要一次访问,使用典型的指针算术。