在CUDA C Best Practices Guide中,有一小部分关于使用有符号和无符号整数。
在C语言标准中,无符号整数溢出语义被很好地定义,而有符号整数溢出导致未定义的结果。因此,编译器可以使用带符号算法比使用无符号算法更积极地进行优化。循环计数器特别注意这一点:因为循环计数器通常具有始终为正的值,所以将计数器声明为无符号可能很有吸引力。但是,为了稍微提高性能,应将它们声明为已签名。
例如,请考虑以下代码:
for (i = 0; i < n; i++) { out[i] = in[offset + stride*i]; }这里,子表达式
stride*i
可以溢出32位整数,因此如果i被声明为无符号,溢出语义会阻止编译器使用一些可能已经应用的优化,例如强度降低。如果我声明为signed,其中溢出语义未定义,则编译器有更多的余地来使用这些优化。
前两句特别让我感到困惑。如果无符号值的语义被很好地定义并且有符号值可以产生未定义的结果,那么编译器如何为后者生成更好的代码呢?
答案 0 :(得分:11)
文本显示了这个例子:
for (i = 0; i < n; i++) {
out[i] = in[offset + stride*i];
}
它还提到了“力量减少”。允许编译器将此替换为以下“伪优化-C”代码:
tmp = offset;
for (i = 0; i < n; i++) {
out[i] = in[tmp];
tmp += stride;
}
现在,想象一下只支持浮点数(和整数作为子集)的处理器。 tmp
类型为“非常大”。
现在,C标准表示涉及无符号操作数的计算永远不会溢出,而是以最大值+ 1的模数减少。这意味着在无符号i
的情况下,编译器必须这样做:< / p>
tmp = offset;
for (i = 0; i < n; i++) {
out[i] = in[tmp];
tmp += stride;
if (tmp > UINT_MAX)
{
tmp -= UINT_MAX + 1;
}
}
但是在有符号整数的情况下,编译器可以做任何想做的事情。它不需要检查溢出 - 如果它确实溢出那么它是开发人员的问题(它可能导致异常,或产生错误的值)。所以代码可以更快。
答案 1 :(得分:2)
因为C的定义限制了编译器编写者在无符号整数的情况下可以做什么。当有符号整数溢出时,会有更多的余地去搞乱。编译器编写者有更多的移动空间,可以这么说。
这就是我读它的方式。
答案 2 :(得分:1)
signed
和unsigned
的语义之间的差异与不支持C定义的所有字大小的处理器的性能相关。例如,假设您有一个仅支持的CPU 32位操作并具有32位寄存器,您编写的C函数同时使用int
(32位)和char
(8位*):
int test(char a) {
char b = a * 100;
return b;
}
由于CPU只能将char
存储在32位寄存器中,并且只能对32位值执行算术运算,因此它将使用32位寄存器来保持b和32位乘法运算。
因为C标准声明有符号整数溢出会导致未定义的结果,所以编译器可以为上面的函数创建代码,当a
高于2时,该代码返回的值高于127。 / p>
但是,如果使用无符号值:
unsigned int test(unsigned char a) {
unsigned char b = a * 100;
return b;
}
C标准定义了无符号运算的溢出语义,因此,编译器必须添加一个屏蔽操作,以确保即使a
高于2,该函数也不会返回高于255的值。 / p>
* C规范允许char
宽于8位,但这会破坏许多程序,因此我们假设在本例中使用char
的8位值的编译器。