无符号与有符号整数的性能


76

通过使用无符号整数而不是有符号整数会带来任何性能提升/损失吗?

如果是这样,这是否会持续很长时间?


8
您需要关心的不够。
— JeremyP,2011年

17
@JeremyP,我可能会建议你说出了真相只有为广大开发者和应用程序....
— 布雷特

1
@Brett:大多数CPU上有符号和无符号算术之间的差为零。除非您进行了大量的算术运算,否则各种大小的差异很小。
— JeremyP

Answers:


106

用的2的次方除法更快unsigned int,因为可以将其优化为单个移位指令。使用时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

11
这仅在除数是一个补全时已知常数为2的幂的情况下才有效,不是吗?
— sharptooth 2011年

1
@sharptooth,用于除法,是的。可能还有其他位操作技巧,这些技巧仅对未签名有效。或签名。我认为积极的影响并不只是一个方向。
— AProgrammer 2011年

为什么非常数除数不能解决这个问题?x86的第一个操作数shrl应该是文字吗?
— Manu343726 2013年

@ Manu343726如果除数不是2的幂?(即使是这样,您也必须在移位之前首先计算数字的二进制对数。)
— fredoverflow

1
在这种规模上,更多指令并不总是意味着现代流水线CPU体系结构的运行时间会变慢。也就是说,在得出深远的结论之前,我仍然会做一个测量。
— ulidtko

49

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

未定义的有符号整数溢出允许编译器假定不会发生溢出,这可能会带来优化机会。请参阅此博客文章进行讨论。


19

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

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

short通常会导致比int(假定sizeof(short) < sizeof(int))相同或更差的性能。当您将算术运算的结果(通常是intnever short)分配给类型的变量时,会降低性能short,该变量存储在处理器的寄存器中(也为type int)。从short进行的所有转换都int需要时间并且很烦人。

注意:有些DSP具有针对该signed short类型的快速乘法指令。在这种情况下short,速度比快int。

至于int和之间的区别long,我只能猜测(我不熟悉64位体系结构)。当然,如果int和long具有相同的大小(在32位平台上),它们的性能也相同。


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

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

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


8
关于short与int相比的性能,我会谨慎对待。使用int进行算术“可能”更快时,应该记住整数算术很少成为瓶颈(至少在现代台式机CPU上是这样),而另一方面,内存带宽通常是瓶颈,因此对于大型数据集,short可能实际上提供了更好的性能,诠释 此外,对于使用较小数据类型的自动矢量化代码,通常意味着可以一次处理更多的数据元素,因此甚至算术性能也可能会提高(尽管考虑到自动矢量化器的当前状态,这种可能性不大)。
— 灰熊”

1
@Grizzly我同意(我的应用程序实际上是计算量很大的,所以我的使用体验short不同于您/其他任何人)
— anatolyg

2
@martinkunev绝对!这可能是short今天使用的唯一原因(非缓存RAM实际上是无限的),这是一个很好的理由。
— anatolyg 2014年

1
@anatolyg RAM可能实际上是无限的,但不要忘记32位程序仍然比64位程序大很多,这意味着无论有多少RAM可用,您仍然经常限于2GB的可用地址-空间。
— bcrist 2014年

1
@JoshParnell我想你的意思short是比内存绑定int时快。以我的经验,它们在x86上具有相同的性能,而在ARM上则较慢。short
— anatolyg

17

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


20
+1表示“如果您想知道,就需要衡量”。非常烦人的是,几乎每周都要回答一次。
— 2011年

9

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

在大多数处理器上,都有用于有符号和无符号算术的指令,因此使用有符号和无符号整数之间的差异归结为编译器使用的整数。

如果两者中的任何一个速度更快,则完全取决于处理器,如果有的话,差异可能很小。


7

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

在他的文章结尾,他包括以下部分:

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

红利的增加必须成为幅度的增加,即,如果n> 0,则增加,如果n <0,则减少。这会带来额外的费用。

不合作的除数的惩罚只有有符号除法的一半,从而有较小的改进余地。

因此,似乎可以使舍入算法在带符号除法中起作用,但性能不如标准舍入算法。


4

对于无符号类型,不仅被2的幂除以更快,对于无符号类型,除以任何其他值也更快。如果查看Agner Fog的指令表,您会发现无符号除法的性能与带符号的除法相似或更好。

例如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           ║
╚═════════════╧══════════╧══════════════╝

当然,这些都是很古老的。晶体管更多的新架构可能会缩小差距,但基本原理仍然适用:通常,您需要更多的宏操作,更多的逻辑,更多的延迟来执行有符号除法


3

简而言之,不要在事实之前就打扰。但是,请稍后再做。

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

这是根据我的经验,我建议您首先编写完整的程序,然后再处理此类事情,只有当您掌握了实际的代码并且可以通过优化对其进行编译,以尝试选择实际执行的类型时最好。这也是关于代码优化以提高性能的很好的一般性建议,请先快速编写代码,然后尝试进行优化编译,并进行调整以查看最有效的方法。而且,您还应该尝试使用不同的编译器来编译程序,然后选择输出性能最高的机器代码的程序。

经过优化的多线程线性代数计算程序可以轻松实现> 10倍的性能差异,分别是经过优化和未经优化的性能差异。所以这很重要。

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

例如,NVidia声明了对GPU进行编程的方法:

注意:正如已经建议的最佳实践一样,应尽可能采用有符号算法而不是无符号算法,以实现SMM的最佳吞吐量。C语言标准对无符号数学的溢出行为施加了更多限制,从而限制了编译器优化的机会。


1

IIRC在x86上签名/未签名应该没有任何区别。另一方面,短/长则是另一回事,因为必须将数据移入RAM或从RAM移出的数据量会长一些(其他原因可能包括强制转换操作,如将长短扩展为长)。


1
还请记住,某些编译器可能具有并非适用于所有整数类型的优化。例如,如果for循环计数器不是带符号的int,则至少是旧的Intel编译器无法应用自动矢量化。
— CAFxX 2011年

在指令级别上并不重要,但是在C ++级别上很重要
— phuclv

@LưuVĩnhPhúc您是在谈论UB的签名溢出吗?如果是这样,我唯一了解的情况是难以优化编译器来推理将无符号整数用作循环计数器/归纳变量的情况(这已在您上方的评论中涵盖了)
— CAFxX

不,在其他情况下,重要性也很重要。您是否阅读了其他答案?
— phuclv

是的 你是否?他们中的大多数人说,除非编译时常数除法和循环归纳变量(我在我的评论中提到),否则没有很大的区别。即使在您自己中,您也要指出,在较新的处理器中,差异不是很大(例如,检查Sandy Bridge表)
— CAFxX

1

有符号和无符号整数都将始终作为单个时钟指令运行,并且具有相同的读写性能,但是根据Andrei Alexandrescu博士的说法,无符号优先于有符号。原因是您可以在相同位数中容纳两倍数量的数字,因为您不会浪费符号位,并且将使用更少的指令来检查负数,从而从减少的ROM中获得更高的性能。以我对Kabuki VM的经验,该功能具有超高性能脚本实现,很少需要在处理内存时实际需要一个带符号的数字。我花了很多年的时间对有符号和无符号数字进行指针算术运算,但是当不需要任何符号位时,我发现带符号没有任何好处。

带符号的2的补码整数可以执行负除以2的次幂,这是使用位移位执行2的乘数和除法时首选的符号。请观看更多来自Andrei的YouTube视频,了解更多优化技术。您还可以在我的文章中找到有关世界上最快的Integer-to-String转换算法的一些不错的信息。


0

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

编辑:

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

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

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


是的,传统上在当前64位系统上;; int)仍为32位宽,但是64位类型(long或long long,取决于OS)应至少与之一样快。
— 菲利普

1
int在我所知道的所有系统(Windows,Linux,Mac OS X,无论处理器是否为64位)上,它总是32位宽。这long是不同的类型:Windows上为32位,Linux和OS X上为一个字
— Philipp

@Philipp,但int不必总是32位宽。
— vapor0114

0

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.