【问题标题】:performance of unsigned vs signed integers无符号与有符号整数的性能
【发布时间】:2011-06-10 09:13:34
【问题描述】:

使用无符号整数而不是有符号整数是否有任何性能增益/损失?

如果是这样,这是否也适用于短期和长期?

【问题讨论】:

  • @JeremyP,我是否可以建议您只为大多数开发人员和应用程序说出真相......
  • @Brett:大多数 CPU 上的有符号和无符号运算之间的差异为零。除非您进行大量算术运算,否则各种尺寸的差异是微不足道的。

标签: c++ c integer int unsigned


【解决方案1】:

传统上int 是目标硬件平台的本机整数格式。任何其他整数类型都可能导致性能损失。

编辑:

现代系统上的情况略有不同:

  • int 实际上可能出于兼容性原因在 64 位系统上是 32 位。我相信这发生在 Windows 系统上。

  • 现代编译器在某些情况下执行较短类型的计算时可能会隐式使用int

【讨论】:

  • 是的,传统上 ;-) 在当前 64 位系统上,int 仍然是 32 位宽,但 64 位类型(longlong long,取决于操作系统)应该是至少一样快。
  • int 在我知道的所有系统(Windows、Linux、Mac OS X,无论处理器是否为 64 位)上始终为 32 位宽。 long 类型不同:在 Windows 上是 32 位,但在 Linux 和 OS X 上是一个字。
  • @Philipp 但int 不必总是 32 位宽。
【解决方案2】:

这将取决于具体的实施。然而,在大多数情况下,不会有任何区别。如果您真的很在意,您必须尝试所有您考虑的变体并衡量性能。

【讨论】:

  • +1 表示“如果你想知道,你需要测量”。几乎每周都需要回答这个问题,这很烦人。
  • @sbi 如果您尝试编写可移植代码,测量将无法回答您的问题。
  • @martinkunev 如果您需要可移植的代码,那么您需要在您想要移植到的所有平台上进行测量——但结果很可能一个平台的优化是另一个平台的悲观化。 IME 主要归结为:要么为平台定制优化,要么不优化。
【解决方案3】:

这在很大程度上取决于特定的处理器。

在大多数处理器上,都有用于有符号和无符号算术的指令,因此使用有符号整数和无符号整数的区别在于编译器使用哪一种。

如果两者中的任何一个更快,则它完全是特定于处理器的,并且很可能差异很小,如果存在的话。

【讨论】:

    【解决方案4】:

    IIRC,在 x86 上签名/未签名不应该有任何区别。另一方面,short/long 则不同,因为 long 需要移入/移出 RAM 的数据量更大(其他原因可能包括转换操作,例如将 short 扩展为 long)。

    【讨论】:

    • 还请记住,某些编译器可能具有不适用于所有整数类型的优化。例如。如果 for 循环计数器不是有符号整数,至少旧的英特尔编译器无法应用自动向量化。
    • 在指令级无关紧要,但从 C++ 级却很重要
    • @LưuVĩnhPhúc 你是说签名溢出是 UB 吗?如果是这样,我知道的唯一情况是,优化编译器更难推断用作循环计数器/归纳变量的无符号整数(我在你上面的评论中涵盖了这一点)跨度>
    • 不,还有其他各种情况下符号很重要。您阅读其他答案了吗?
    • 我做到了。你是否?他们中的大多数人说没有太大的区别,除非编译时常量除法和循环归纳变量(我在评论中提到)。即使在你的你有点指出,在较新的处理器中差异不是很大(例如检查 Sandy Bridge 表)
    【解决方案5】:

    使用unsigned int 除以 2 的幂更快,因为它可以优化为单个移位指令。使用signed int,它通常需要更多的机器指令,因为除法向零循环,但向右移动向下。示例:

    int foo(int x, unsigned y)
    {
        x /= 8;
        y /= 8;
        return x + y;
    }
    

    这是相关的x 部分(签名除法):

    movl 8(%ebp), %eax
    leal 7(%eax), %edx
    testl %eax, %eax
    cmovs %edx, %eax
    sarl $3, %eax
    

    这里是相关的y 部分(无符号除法):

    movl 12(%ebp), %edx
    shrl $3, %edx
    

    【讨论】:

    • 这只有在除数是编译时已知常数是 2 的幂的情况下才有效,不是吗?
    • @sharptooth,除法,是的。可能还有其他仅对无符号有效的位操作技巧。或签名。我不认为积极的影响只在一个方向。
    • 为什么这个技巧不能用于非常数除数? x86的第一个操作数shrl应该是字面量?
    • @Manu343726 如果除数不是 2 的幂怎么办? (即使是这样,您也必须先计算数字的二进制对数,然后再进行移位。)
    • 在这个规模上,对于现代流水线 CPU 架构,更多指令并不总是意味着 运行时更慢。 IE。在得出影响深远的结论之前,我仍然会进行测量。
    【解决方案6】:

    在 C++(和 C)中,有符号整数溢出是未定义的,而无符号整数溢出被定义为回绕。请注意,例如在 gcc 中,您可以使用 -fwrapv 标志来定义有符号溢出(环绕)。

    未定义的有符号整数溢出允许编译器假设溢出不会发生,这可能会带来优化机会。参见例如this blog post 讨论。

    【讨论】:

      【解决方案7】:

      unsigned 的性能与signed 相同或更好。 一些例子:

      • 除以 2 的幂的常数(另请参阅 FredOverflow 的答案)
      • 除以常数(例如,我的编译器使用 2 条无符号 asm 指令和 6 条有符号指令实现除以 13)
      • 检查数字是否为偶数(我不知道为什么我的 MS Visual Studio 编译器使用 4 条指令来实现 signed 数字;gcc 使用 1 条指令来实现它,就像在 unsigned 案例中一样)

      short 通常会导致与int 相同或更差的性能(假设sizeof(short) < sizeof(int))。当您将算术运算的结果(通常是 int,绝不是 short)分配给存储在处理器寄存器中的 short 类型的变量(也是 int 类型的变量时,性能下降会发生)。从shortint 的所有转换都需要时间并且很烦人。

      注意:一些 DSP 具有 signed short 类型的快速乘法指令;在这种特定情况下,shortint 快。

      至于intlong 的区别,我只能猜测(我不熟悉64 位架构)。当然,如果intlong 的大小相同(在32 位平台上),它们的性能也是一样的。


      几个人指出的一个非常重要的补充:

      对于大多数应用程序来说真正重要的是内存占用和使用的带宽。对于大型数组,您应该使用最小的必要整数(short,甚至可能是signed/unsigned char)。

      这将提供更好的性能,但增益是非线性的(即不是 2 或 4 倍)并且有些不可预测 - 它取决于缓存大小以及应用程序中计算和内存传输之间的关系。

      【讨论】:

      • 我会小心关于与 int 相比 short 的性能的陈述。虽然使用 int 算术“可能”会更快,但应该记住整数算术很少是瓶颈(至少在现代桌面 cpu 上),另一方面内存带宽通常是瓶颈,因此对于大型数据集而言,短实际上可能会提供相当好的性能诠释。此外,对于使用较小数据类型的自动矢量化代码,通常意味着可以同时处理更多数据元素,因此即使是算术性能也可能会提高(尽管考虑到自动矢量化器的当前状态不太可能)。
      • @Grizzly 我同意(我的应用程序实际上计算量很大,所以我对short 的体验与你/其他人的不同)
      • @martinkunev 绝对!这可能是今天使用 short 的唯一原因(非缓存 RAM 实际上是无限的),而且是一个很好的理由。
      • @anatolyg RAM 可能实际上是无限的,但不要忘记 32 位程序的数量仍然远远超过 64 位程序,这意味着无论有多少可用 RAM,你仍然经常限制为 2GB 的可用地址空间。
      • @JoshParnell 我猜你的意思是short内存受限 时比int 快。根据我的经验,它们在 x86 上具有相同的性能,而 short 在 ARM 上的速度较慢。
      【解决方案8】:

      有符号和无符号整数之间的性能差异实际上比接受答案所暗示的更为普遍。无符号整数除以任何常数可以比有符号整数除以常数更快,无论该常数是否是 2 的幂。见http://ridiculousfish.com/blog/posts/labor-of-division-episode-iii.html

      在他的帖子末尾,他包括以下部分:

      一个自然的问题是相同的优化是否可以改善有符号除法?不幸的是,它似乎没有,原因有两个:

      股息的增加必须变成幅度的增加,即如果 n > 0 则增加,如果 n

      不合作的除数的惩罚只有签名除法的一半左右,留下一个更小的改进窗口。

      因此,向下取整算法似乎可以在有符号除法中工作,但性能不如标准向上取整算法。

      【讨论】:

        【解决方案9】:

        无符号整数的优势在于您可以将两者都存储并视为比特流,我的意思是只是一个没有符号的数据,因此通过位移操作,乘法、除法变得更容易(更快)

        【讨论】:

          【解决方案10】:

          简而言之,在事实发生之前不要打扰。但是以后要麻烦。

          如果您想获得性能,您必须使用编译器的性能优化,这可能违反常识。要记住的一件事是,不同的编译器可以以不同的方式编译代码,并且它们本身具有不同类型的优化。如果我们谈论的是g++ 编译器并谈论通过使用-Ofast 或至少一个-O3 标志来最大化它的优化级别,根据我的经验,它可以将long 类型编译成性能更好的代码比任何unsigned 类型,甚至只是int

          这是我自己的经验,我建议您先编写完整的程序,然后再关心这些事情,当您手头有实际代码并且可以通过优化对其进行编译以尝试选择类型时实际上表现最好。这也是关于性能代码优化的一个很好的非常普遍的建议,首先快速编写,尝试使用优化进行编译,调整内容以查看最有效的方法。您还应该尝试使用不同的编译器来编译您的程序,并选择输出性能最高的机器代码。

          优化的多线程线性代数计算程序可以轻松获得>10倍的性能差异精细优化与未优化。所以这很重要。

          在很多情况下,优化器的输出与逻辑相矛盾。例如,我有一个案例,a[x]+=ba[x]=b 之间的差异将程序执行时间几乎改变了 2 倍。不,a[x]=b 不是更快的。

          例如,NVidia stating 用于对他们的 GPU 进行编程:

          注意:正如已经推荐的最佳实践一样,有符号算术 应尽可能优先于无符号算术 SMM 上的最佳吞吐量。 C 语言标准放置了更多 限制无符号数学的溢出行为,限制编译器 优化机会。

          【讨论】:

            【解决方案11】:

            不仅无符号类型除以 2 的幂更快,无符号类型除以任何其他值也更快。如果您查看Agner Fog's Instruction tables,您会发现未签名部门的性能与签名版本相似或更好

            以 AMD K7 为例

            Instruction Operands Ops Latency Reciprocal throughput
            DIV r8/m8 32 24 23
            DIV r16/m16 47 24 23
            DIV r32/m32 79 40 40
            IDIV r8 41 17 17
            IDIV r16 56 25 25
            IDIV r32 88 41 41
            IDIV m8 42 17 17
            IDIV m16 57 25 25
            IDIV m32 89 41 41

            英特尔奔腾也是如此

            Instruction Operands Clock cycles
            DIV r8/m8 17
            DIV r16/m16 25
            DIV r32/m32 41
            IDIV r8/m8 22
            IDIV r16/m16 30
            IDIV r32/m32 46

            当然,这些都是相当古老的。具有更多晶体管的较新架构可能会缩小差距,但基本情况适用:通常您需要更多微操作、更多逻辑、更多延迟来进行有符号除法

            【讨论】:

              【解决方案12】:

              有符号和无符号整数将始终作为单时钟指令运行并具有相同的读写性能,但根据Dr Andrei Alexandrescu,无符号整数优于有符号整数。这样做的原因是您可以在相同的位数中容纳两倍数量的数字,因为您不会浪费符号位,并且您将使用更少的指令检查负数,从而从减少的 ROM 中提高性能。根据我对具有超高性能Script 实现的Kabuki VM 的经验,在使用内存时实际上很少需要有符号数。我花了数年时间对有符号和无符号数字进行指针运算,但我发现在不需要符号位时对有符号没有任何好处。

              当使用位移来执行 2 的幂的乘法和除法时,可能首选有符号的地方,因为您可以使用带符号的 2 的补码整数执行 2 的负幂除法。请参阅more YouTube videos from Andrei 了解更多优化技术。您还可以在我关于 the world's fastest Integer-to-String conversion algorithm 的文章中找到一些有用的信息。

              【讨论】:

                猜你喜欢
                • 2010-09-19
                • 1970-01-01
                • 2012-10-20
                • 1970-01-01
                • 2012-04-19
                • 2013-10-02
                • 2015-02-17
                • 1970-01-01
                • 2023-03-16
                相关资源
                最近更新 更多