【问题标题】:CUDA C best practices: unsigned vs signed optimizationCUDA C 最佳实践:无符号与有符号优化
【发布时间】:2013-01-01 08:02:08
【问题描述】:

CUDA C Best Practices Guide 中有一小部分关于使用有符号和无符号整数。

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

例如,考虑以下代码:

 for (i = 0; i 
  
  

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

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

【问题讨论】:

    标签: c cuda


    【解决方案1】:

    文字显示了这个例子:

    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;
         }
    }
    

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

    【讨论】:

    • 编译器必须执行加法之前检查溢出,否则会导致未定义的行为
    • @K-ballo 这个场景无论如何都是虚构的......我的假设是 tmp 是一个“非常大的数字”(例如长双精度数),所以它可以在添加后检查它。但这只是一个奇怪的例子,当然这取决于实际的处理器。
    • 为什么不让无符号整数环绕并责怪程序员,就像对待有符号整数一样?
    • @BarryBrown 未签名将根据标准环绕。甚至不能保证有符号整数“环绕”这一点 - 允许程序在有符号溢出时崩溃。不同之处在于标准说明了在一种情况下要做什么,而将另一种情况视为“未定义的行为”。
    【解决方案2】:

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

    我就是这么读的。

    【讨论】:

      【解决方案3】:

      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 标准定义了无符号操作的溢出语义,因此,编译器必须添加屏蔽操作以确保即使a 高于 2,函数也不会返回高于 255 的值。


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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2011-03-31
        • 1970-01-01
        • 2023-03-28
        • 2011-03-14
        • 2011-11-25
        • 2012-10-20
        • 2021-06-24
        • 2020-11-07
        相关资源
        最近更新 更多