CUDA C最佳实践:无符号优化与有符号优化

CUDA C最佳实践:无符号优化与有符号优化,c,cuda,C,Cuda,在中,有一个关于使用有符号和无符号整数的小节 在C语言标准中,无符号整数溢出语义定义良好,而有符号整数溢出会导致未定义的结果。因此,编译器可以使用有符号算术比使用无符号算术更积极地进行优化。对于循环计数器,这一点尤其值得注意:因为循环计数器的值通常都是正数,所以可能很容易将计数器声明为无符号。但是,为了获得更好的性能,应该将它们声明为已签名 例如,考虑下面的代码: for (i = 0; i < n; i++) { out[i] = in[offset + strid

在中,有一个关于使用有符号和无符号整数的小节

在C语言标准中,无符号整数溢出语义定义良好,而有符号整数溢出会导致未定义的结果。因此,编译器可以使用有符号算术比使用无符号算术更积极地进行优化。对于循环计数器,这一点尤其值得注意:因为循环计数器的值通常都是正数,所以可能很容易将计数器声明为无符号。但是,为了获得更好的性能,应该将它们声明为已签名

例如,考虑下面的代码:

for (i = 0; i < n; i++) { out[i] = in[offset + stride*i]; } 对于(i=0;istride*i可能会使32位整数溢出,因此如果将i声明为无符号,溢出语义将阻止编译器使用一些可能已应用的优化,例如强度降低。相反,如果i被声明为signed,其中溢出语义未定义,那么编译器有更多的余地来使用这些优化


前两句话让我特别困惑。如果无符号值的语义定义良好,并且有符号值可以生成未定义的结果,那么编译器如何才能为后者生成更好的代码?

这是因为C的定义限制了编译器编写器在无符号整数的情况下可以做什么。当有符号整数溢出时,有更多的回旋余地。可以说,编译器编写者有更多的移动空间


我就是这样读的。

文本显示了以下示例:

for (i = 0; i < n; i++) {  
     out[i] = in[offset + stride*i];  
}

但是在有符号整数的情况下,编译器可以做它想做的任何事情。它不需要检查溢出-如果它确实溢出,那么这是开发人员的问题(它可能导致异常或产生错误的值)。因此,代码可以更快。

有符号的和无符号的之间的语义差异与不支持C定义的所有字大小的处理器的性能有关。例如,假设您有一个只支持32位操作且具有32位寄存器的CPU,您编写了一个C函数,它同时使用
int
(32位)和
char
(8位*):

由于CPU只能在32位寄存器中存储
char
,并且只能对32位值执行算术运算,因此它将使用32位寄存器来保存b,并使用32位乘法运算

由于C标准规定有符号整数溢出会导致未定义的结果,因此编译器可以为上述函数创建代码,当
a
大于2时,该函数返回的值大于127

但是,如果使用无符号值:

unsigned int test(unsigned char a) {
  unsigned char b = a * 100;
  return b;
}
C标准定义了无符号操作的溢出语义,因此,编译器必须添加一个屏蔽操作,以确保即使
a
大于2,函数也不会返回大于255的值



*C规范允许
char
的宽度大于8位,但这会中断许多程序,因此在本例中,我们假设编译器对
char
使用8位值。

编译器必须在执行加法之前检查溢出,否则,它将导致未定义的错误behavior@K-巴洛,不管怎样,这个场景是虚构的。。。我的假设是
tmp
是一个“非常大的数字”(例如长双精度),因此它可以在加法后检查它。但这只是一个奇怪的例子,当然这取决于实际的处理器。为什么不让无符号整数缠绕并责怪程序员,就像对待有符号整数一样?@BarryBrown the unsigned将根据标准缠绕。有符号整数甚至不能保证“环绕”——允许程序在有符号溢出时崩溃。不同之处在于,该标准规定了在一种情况下应该做什么,而将另一种情况视为“未定义的行为”。
tmp = offset;
for (i = 0; i < n; i++) {  
     out[i] = in[tmp];
     tmp += stride;
     if (tmp > UINT_MAX)
     {
         tmp -= UINT_MAX + 1;
     }
}
int test(char a) {
  char b = a * 100;
  return b;
}
unsigned int test(unsigned char a) {
  unsigned char b = a * 100;
  return b;
}