无符号与有符号整数的性能
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了无符号与有符号整数的性能相关的知识,希望对你有一定的参考价值。
通过在有符号整数上使用无符号整数,是否有任何性能增益/损失?
如果是这样,这也是短期和长期的吗?
使用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
IIRC,x86签名/未签名不应该有任何区别。另一方面,短/长是一个不同的故事,因为必须移入/移出RAM的数据量对于多头来说更大(其他原因可能包括强制转换操作,如从短到长)。
无符号整数是有利的,因为你将它们存储和处理为比特流,我的意思是只是一个没有符号的数据,所以乘法,使用位移操作,分割变得更容易(更快)
有符号和无符号整数将始终作为单个时钟指令运行并具有相同的读写性能,但根据Dr Andrei Alexandrescu,unsigned优先于signed。这样做的原因是你可以在相同的位数中输入两倍的数字,因为你没有浪费符号位,你将使用较少的指令检查负数,从而减少ROM的性能提升。根据我使用Kabuki VM的经验,Script具有超高性能的more YouTube videos from Andrei实现,在使用内存时实际上需要一个带符号的数字是很少见的。我花了数年时间使用有符号和无符号数进行指针运算,并且当没有需要符号位时,我发现签名没有任何好处。
如果签名可能是首选的是使用位移来执行乘法和2的幂除法,因为你可以用带符号的2的补码整数执行2除法的负幂。请参阅一些the world's fastest Integer-to-String conversion algorithm了解更多优化技巧。你也可以在我关于qazxswpoi的文章中找到一些好的信息。
在C ++(和C)中,有符号整数溢出是未定义的,而无符号整数溢出被定义为环绕。请注意,例如在gcc中,您可以使用-fwrapv标志来定义已签名的溢出(以换行)。
未定义的有符号整数溢出允许编译器假定不会发生溢出,这可能会引入优化机会。参见例如this blog post进行讨论。
这将取决于具体实施。但在大多数情况下,没有区别。如果您真的在意,您必须尝试所有您考虑的变体并测量性能。
unsigned
导致与signed
相同或更好的表现。一些例子:
- 除以2的幂的常数除法(参见FredOverflow的答案)
- 除以常数(例如,我的编译器使用2个asm指令实现除以13的除法,以及6个用于签名的指令)
- 检查一个数字是否是偶数(我不知道我的MS Visual Studio编译器为什么用
signed
数字的4条指令实现它; gcc用1条指令执行它,就像在unsigned
情况下一样)
short
通常导致与int
相同或更差的表现(假设sizeof(short) < sizeof(int)
)。当您将算术运算的结果(通常是int
,从不short
)分配给short
类型的变量时会发生性能下降,该变量存储在处理器的寄存器(也是int
类型)中。从short
到int
的所有转换需要时间并且令人讨厌。
注意:某些DSP具有signed short
类型的快速乘法指令;在这种特殊情况下,short
比int
更快。
至于int
和long
之间的区别,我只能猜测(我不熟悉64位架构)。当然,如果int
和long
具有相同的大小(在32位平台上),它们的性能也是相同的。
一些非常重要的补充,几个人指出:
对大多数应用程序而言真正重要的是内存占用和带宽利用。您应该使用最小的必要整数(short
,甚至signed/unsigned char
)用于大型数组。
这将提供更好的性能,但增益是非线性的(即不是2或4倍)并且有些不可预测 - 它取决于缓存大小以及应用程序中计算和内存传输之间的关系。
这几乎取决于特定的处理器。
在大多数处理器上,都有有符号和无符号算术的指令,因此使用有符号和无符号整数之间的区别归结为编译器使用的整数。
如果两者中的任何一个更快,那么它完全取决于处理器,如果它存在的话,很可能差别很小。
有符号和无符号整数之间的性能差异实际上比接受答案所暗示的更为普遍。任何常数对无符号整数的划分都可以比有符号整数除以常数更快,无论常数是2的幂。见http://ridiculousfish.com/blog/posts/labor-of-division-episode-iii.html
在他的帖子结束时,他包括以下部分:
一个自然的问题是相同的优化是否可以改善签名划分;不幸的是,它似乎没有,原因有两个:
被除数的增量必须变为幅度的增加,即如果n> 0则增加,如果n <0则递减。这引入了额外的费用。
不合作除数的惩罚只是签名师的一半左右,留下了较小的改进窗口。
因此,似乎可以使得向下舍入算法在有符号的除法中工作,但是将不如标准的向上舍入算法。
总之,在事实之前不要打扰。但是后来要麻烦。
如果您希望获得性能,则必须使用编译器的性能优化,这可能会违反常识。要记住的一件事是,不同的编译器可以以不同的方式编译代码,并且它们本身具有不同类型的优化。如果我们正在讨论g++
编译器并谈论通过使用-Ofast
或至少一个-O3
标志来最大化它的优化级别,根据我的经验,它可以将long
类型编译成代码,其性能甚至比任何unsigned
类型都要好,甚至只是int
。
这是我自己的经验,我建议你先编写完整的程序,然后关心这些事情,当你掌握了实际的代码并且可以通过优化编译它来尝试选择实际执行的类型时最好。这也是关于性能代码优化的一般性建议,首先快速编写,尝试使用优化进行编译,调整内容以查看最佳方法。您还应该尝试使用不同的编译器来编译您的程序并选择输出最高性能机器代码的程序。
优化的多线程线性代数计算程序可轻松实现精细优化与未优化的> 10倍性能差异。所以这很重要。
在很多情况下,优化器输出与逻辑相矛盾。例如,我有一个案例,当a[x]+=b
和a[x]=b
之间的差异改变了程序执行时间差不多2倍。不,a[x]=b
不是更快的。
以下是用于编程GPU的NVidia stating:
注意:正如已经建议的最佳实践一样,只要有可能,签名算法应优先于无符号算术,以获得SMM上的最佳吞吐量。 C语言标准对无符号数学的溢出行为施加了更多限制,限制了编译器优化机会。
对于无符号类型,不仅除以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 ║
╚═════════════╧══════════╧══════════════╝
当然那些都很古老。具有更多晶体管的较新架构可能缩小差距但基本的事情适用:通常您需要更多的宏操作,更多逻辑,更多延迟来执行签名划分
传统上,int
是目标硬件平台的原生整数格式。任何其他整数类型都可能导致性能损失。
编辑:
现代系统的情况略有不同:
- 出于兼容性原因,
int
实际上可能是64位系统上的32位。我相信这种情况发生在Windows系统上。 - 在某些情况下,现代编译器可能会在执行较短类型的计算时隐式使用
int
。
以上是关于无符号与有符号整数的性能的主要内容,如果未能解决你的问题,请参考以下文章
2021-09-11:给你一个32位的有符号整数x,返回将x中的数字部分反转后的结果。反转后整数超过 32 位的有符号整数的范围就返回0,假设环境不允许存储 64 位整数(有符号或无符号)。(代码片段