nvcc设备代码可以访问内置值warpSize
,该值设置为执行内核的设备的warp大小(即在可预见的将来为32)。通常你不能告诉它除了一个常量 - 但是如果你试图声明一个长度为warpSize的数组,你就会抱怨它是非常量的...(使用CUDA 7.5)
所以,至少为了这个目的,你有动力去做(编辑):
enum : unsigned int { warp_size = 32 };
标题中的某个位置。但是现在 - 我应该选择哪个,何时? :warpSize
或warp_size
?
编辑: warpSize
显然是PTX中的编译时常量。问题仍然存在。
答案 0 :(得分:10)
让我们直截了当地说几点。 warp大小不是编译时常量,不应该被视为一个。它是一个特定于体系结构的运行时立即常量(对于迄今为止的所有体系结构,它的值恰好为32)。曾几何时,旧的Open64编译器确实向PTX发出了一个常量,但是如果我的记忆没有让我失望,那至少在6年前就会发生变化。
该值可用:
warpSize
,其中不是编译时常量(在这种情况下,编译器会发出PTX WARP_SZ
变量)。WARP_SZ
,它是运行时立即常量不要为warp大小声明你自己的常量,这只是要求麻烦。内核数组的正常用例是使用动态分配的共享内存,其大小为warp大小的某个倍数。您可以在运行时从主机API读取warp大小以获取它。如果你有一个静态声明的内核,你需要从warp大小维度,使用模板并在运行时选择正确的实例。后者可能看起来像是不必要的剧院,但对于在实践中几乎从不出现的用例来说,这是正确的做法。选择是你的。
答案 1 :(得分:2)
与talonmies的答案相反,我发现warp_size
常数完全可以接受。使用warpSize
的唯一原因是使代码向前兼容可能具有不同大小的warp的未来硬件。然而,当这样的硬件到来时,内核代码也很可能需要其他改变以保持有效。 CUDA不是与硬件无关的语言 - 相反,它仍然是一种低级编程语言。生产代码使用随时间变化的各种内在函数(例如__umul24
)。
当我们得到不同的扭曲尺寸(例如64)时,很多事情都会发生变化:
warpSize
必须明显调整int __ballot
,虽然int
不需要是32位,但 最常见!迭代操作(如warp级别的缩减)需要调整其迭代次数。我从未见过有人写过:
for (int i = 0; i < log2(warpSize); ++i) ...
在通常是时间关键的代码中会过于复杂。
warpIdx
和laneIdx
计算threadIdx
。目前,我看到的最典型的代码是:
warpIdx = threadIdx.x/32;
laneIdx = threadIdx.x%32;
简化了右移和掩码操作。但是,如果您将32
替换为warpSize
,这突然变得非常昂贵!
同时,在代码中使用warpSize
会阻止优化,因为正式它不是编译时已知的常量。
此外,如果共享内存的数量取决于warpSize
,则强制您使用动态分配的shmem(根据talonmies的答案)。但是,使用它的语法不方便,特别是当你有几个数组时 - 这会强制你自己做指针算法并手动计算所有内存使用量的总和。
使用warp_size
的模板是一个部分解决方案,但在每个函数调用时都需要增加一层语法复杂性:
deviceFunction<warp_size>(params)
这会混淆代码。样板越多,代码的读取和维护就越困难。
我的建议是使用一个控制所有模型特定常量的标题,例如
#if __CUDA_ARCH__ <= 600
//all devices of compute capability <= 6.0
static const int warp_size = 32;
#endif
现在,您的其余CUDA代码可以使用它而不会产生任何语法开销。您决定添加对新架构的支持的那一天,您只需要更改这一段代码。