在CUDA内核中使用char变量是否会受到惩罚?

时间:2014-11-18 11:35:26

标签: c++ c performance types cuda

我似乎记得得到提示,我应该尽量避免在CUDA内核中使用char,因为SM喜欢32位整数。使用它们会有一些速度惩罚吗?例如,执行速度较慢

int a[4];
int b = a[0] + a[1] + a[2] + a[3];
a[1] = a[3];
a2[0] = a[0]

大于

char a[4];
char b = a[0] + a[1] + a[2] + a[3];
a[1] = a[3];
a2[0] = a[0]

在内核代码中?

备注:

  • 我对使用char值进行算术,执行比较以及读取和写入内存的惩罚感兴趣。

2 个答案:

答案 0 :(得分:9)

预先快速说明:在C / C ++中,char的签名是实现定义的。使用char执行8位整数运算时,建议根据计算要求使用signed charunsigned char

在CUDA中使用char类型产生的负面性能影响可能是。我不建议使用char类型,除非内存大小限制(包括共享内存大小限制)或计算的性质特别需要它。

CUDA是一种遵循基本C ++语言规范的C ++派生语言。 C ++(和C)指定在进入计算之前,必须将比int更窄的类型的表达式数据扩展为int。除非底层硬件的整数指令带有内置转换,否则这意味着需要额外的转换指令,这将增加动态指令数量并可能降低性能。

请注意,允许编译器在“as-if”规则下偏离抽象C ++执行模型:只要生成的代码表现得好像它遵循抽象模型,即它的语义相同,就允许消除这些转换操作。我最近的实验表明,CUDA 6.5编译器正在积极地应用这种优化,因此能够彻底消除大多数转换操作,或者将它们合并到其他指令中。

然而,这并非总是可行的。一个简单的人为例子是以下内核,当使用I2I.S32.S8T = char进行实例化时,它包含一个额外的转换指令T = int。我通过在可执行文件上运行cuobjdump --dump-sass来转储机器代码来验证这一点。

template <class T>
__global__ void kernel (T *out, const T *in)
{
    int tid = threadIdx.x;
    if (threadIdx.x < 128) {
        T foo = 5 * in[tid] + 7 * in[tid+1];
        out [tid] = foo * foo;
    }
}

除了增加指令数量外,由于内存带宽较低,使用char类型也会对​​性能产生负面影响。 GPU的存储器子系统的设计使得总可实现的全局存储器带宽通常随着访问的宽度而增加。对此的一种可能解释是跟踪内存访问的内部队列的有限深度,但可能还有其他因素在起作用。

由于用例的性质(例如图像处理)自然会出现char类型,人们可能希望研究使用32位复合类型,例如uchar4。在加载和存储操作期间使用更宽的类型允许改善的存储器带宽。 CUDA有SIMD intrinsics来处理打包的char数据,使用它们可以有利地减少动态指令数量。请注意,SIMD内在函数仅在Kepler GPU上由硬件完全支持,在Fermi CPU上完全仿真,并在Maxwell GPU上进行部分仿真。我已经看到轶事证据表明,与分别处理每个字节相比,即使是模拟版本仍然可以提供性能优势。我建议在任何特定用例的上下文中验证。

CUDA Best Practices Guide的第11.1.3节中,对此问题也有一个非常简短的参考:

  

编译器必须偶尔插入转换指令,引入附加指令   执行周期。这就是......

     
      
  • char short 上运行的功能,其操作数通常需要转换为 int
  •   
  • ...
  •   

答案 1 :(得分:3)

<强>算术

在一般意义上不可能说它是否会更快/更慢/不变,尽管我通常不会期望有太大差异。你说chars的算术将是32位,但是否需要进行类型转换将取决于问题。在问题的示例中,我希望在32位寄存器中看到编译器存储ab,并且在我的实验中围绕这个问题(注意,没有完整的复制案例很难保证这一点)我没有看到SASS的差异。对于在寄存器中完成所有操作的代码区域,我不会期望性能受到影响。

然而,由于char变量被移动两次并且从内存中移动,因此存在影响。由于char必须在使用前转换为32位寄存器,因此会产生额外的指令。这可能是相当大的影响,也可能不是。

现在,也有一些边缘情况可能会有所不同。编译器可能能够将多个char打包到寄存器中并使用算术(寄存器保存与算术成本)提取它们。您甚至可以使用联合强制执行此行为。保存是否值得指示将根据具体情况而有所不同。我无法想到任何其他会导致重大投射开销的人。

<强>内存

相当明显的是,如果您可以将变量存储在1个字节而不是4个字节中,那么您将需要节省4倍的内存和带宽。有些事情需要考虑:

  1. 共享内存。当前共享库大小为4字节或8字节。除非您使用每个线程至少4/8字节的事务进行读取,否则无法实现峰值共享内存带宽。还有较小的交易需要考虑银行冲突。读取一个字节大小的1字节将避免这些库冲突,但会增加所需的内存并浪费带宽。
  2. 全球记忆。当您能够进行大型交易时,内存系统最有效。 128位事务往往比64位快,这往往比32位快。因此,打包(并对齐)您的数据是个好主意,这样您就可以通过一条指令将多个数据移动到一个线程中。
  3. 结论

    我不知道有任何重要原因,如果可能的话,不使用char代替int进行算术,其中一切都在寄存器中,尽管您在阅读/写作时会支付转换费用记忆。将数组存储为char而不是int应该,如果您小心,请同时节省带宽和空间。