9

CUDA C 最佳实践指南中有一小节关于使用有符号和无符号整数。

在 C 语言标准中,无符号整数溢出语义是明确定义的,而有符号整数溢出会导致未定义的结果。因此,编译器使用有符号算术可以比使用无符号算术更积极地优化。这对于循环计数器特别值得注意:由于循环计数器的值通常为正数,因此将计数器声明为无符号可能很诱人。然而,为了更好的性能,它们应该被声明为有符号的。

例如,考虑以下代码:

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

在这里,子表达式stride*i可能会溢出一个 32 位整数,因此如果 i 被声明为无符号,溢出语义会阻止编译器使用一些原本可能已经应用的优化,例如强度降低。相反,如果 i 被声明为有符号,其中溢出语义未定义,编译器就有更多的余地来使用这些优化。

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

4

3 回答 3

13

文本显示了这个例子:

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编译器必须这样做:

tmp = offset;
for (i = 0; i < n; i++) {  
     out[i] = in[tmp];
     tmp += stride;
     if (tmp > UINT_MAX)
     {
         tmp -= UINT_MAX + 1;
     }
}

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

于 2012-12-31T21:27:05.213 回答
2

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

我就是这样读的。

于 2012-12-31T20:56:55.420 回答
1

和之间的语义差异与signedunsigned支持 C 定义的所有字长的处理器的性能相关。例如,假设您的 CPU 仅支持 32 位操作并具有 32 位寄存器,而您编写一个同时使用int(32 位)和char(8 位*)的 C 函数:

int test(char a) {
  char b = a * 100;
  return b;
}

由于 CPU 只能存储char在 32 位的寄存器中,并且只能对 32 位的值进行算术运算,所以它会使用一个 32 位的寄存器来保存 b,以及一个 32 位的乘法运算。

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

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

unsigned int test(unsigned char a) {
  unsigned char b = a * 100;
  return b;
}

C 标准为无符号操作定义了溢出语义,因此,编译器必须添加一个屏蔽操作以确保该函数不会返回高于 255 的值,即使a高于 2。


* C 规范允许超过 8 位,但这会破坏许多程序,因此我们假设在此示例char中使用 8 位值的编译器。char

于 2013-01-01T00:02:08.437 回答