我应该何时使用CUDA的内置warpSize,而不是我自己的常量?

时间:2016-03-16 21:11:35

标签: c++ cuda constants gpu-warp

nvcc设备代码可以访问内置值warpSize,该值设置为执行内核的设备的warp大小(即在可预见的将来为32)。通常你不能告诉它除了一个常量 - 但是如果你试图声明一个长度为warpSize的数组,你就会抱怨它是非常量的...(使用CUDA 7.5)

所以,至少为了这个目的,你有动力去做(编辑):

enum : unsigned int { warp_size  = 32 };

标题中的某个位置。但是现在 - 我应该选择哪个,何时? :warpSizewarp_size

编辑: warpSize显然是PTX中的编译时常量。问题仍然存在。

2 个答案:

答案 0 :(得分:10)

让我们直截了当地说几点。 warp大小不是编译时常量,不应该被视为一个。它是一个特定于体系结构的运行时立即常量(对于迄今为止的所有体系结构,它的值恰好为32)。曾几何时,旧的Open64编译器确实向PTX发出了一个常量,但是如果我的记忆没有让我失望,那至少在6年​​前就会发生变化。

该值可用:

  1. 在CUDA C中通过warpSize,其中不是编译时常量(在这种情况下,编译器会发出PTX WARP_SZ变量)。
  2. 在PTX汇编程序中通过WARP_SZ,它是运行时立即常量
  3. 从运行时API作为设备property
  4. 不要为warp大小声明你自己的常量,这只是要求麻烦。内核数组的正常用例是使用动态分配的共享内存,其大小为warp大小的某个倍数。您可以在运行时从主机API读取warp大小以获取它。如果你有一个静态声明的内核,你需要从warp大小维度,使用模板并在运行时选择正确的实例。后者可能看起来像是不必要的剧院,但对于在实践中几乎从不出现的用例来说,这是正确的做法。选择是你的。

答案 1 :(得分:2)

与talonmies的答案相反,我发现warp_size常数完全可以接受。使用warpSize的唯一原因是使代码向前兼容可能具有不同大小的warp的未来硬件。然而,当这样的硬件到来时,内核代码也很可能需要其他改变以保持有效。 CUDA不是与硬件无关的语言 - 相反,它仍然是一种低级编程语言。生产代码使用随时间变化的各种内在函数(例如__umul24)。

当我们得到不同的扭曲尺寸(例如64)时,很多事情都会发生变化:

  • warpSize必须明显调整
  • 许多warp级内在函数需要调整其签名,或者生成新版本,例如: int __ballot,虽然int不需要是32位,但 最常见!
  • 迭代操作(如warp级别的缩减)需要调整其迭代次数。我从未见过有人写过:

    for (int i = 0; i < log2(warpSize); ++i) ...
    

    在通常是时间关键的代码中会过于复杂。

  • 需要调整{li>

    warpIdxlaneIdx计算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代码可以使用它而不会产生任何语法开销。您决定添加对新架构的支持的那一天,您只需要更改这一段代码。