通过使用无符号整数而不是有符号整数会带来任何性能提升/损失吗?
如果是这样,这是否会持续很长时间?
Answers:
用的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
shrl
应该是文字吗?
unsigned
导致相同或更好的性能signed
。一些例子:
signed
数字指令来实现它; gcc却用1条指令来实现unsigned
)short
通常会导致比int
(假定sizeof(short) < sizeof(int)
)相同或更差的性能。当您将算术运算的结果(通常是int
never short
)分配给类型的变量时,会降低性能short
,该变量存储在处理器的寄存器中(也为type int
)。从short
进行的所有转换都int
需要时间并且很烦人。
注意:有些DSP具有针对该signed short
类型的快速乘法指令。在这种情况下short
,速度比快int
。
至于int
和之间的区别long
,我只能猜测(我不熟悉64位体系结构)。当然,如果int
和long
具有相同的大小(在32位平台上),它们的性能也相同。
一些人指出了一个非常重要的补充:
对于大多数应用程序真正重要的是内存占用量和已利用的带宽。对于大型数组short
,您应该使用最小的必要整数(,甚至是signed/unsigned char
)。
这将提供更好的性能,但是增益是非线性的(即不是2或4的倍数)并且有些不可预测-它取决于缓存大小以及应用程序中计算与内存传输之间的关系。
short
不同于您/其他任何人)
short
今天使用的唯一原因(非缓存RAM实际上是无限的),这是一个很好的理由。
short
是比内存绑定int
时快。以我的经验,它们在x86上具有相同的性能,而在ARM上则较慢。short
这在很大程度上取决于特定的处理器。
在大多数处理器上,都有用于有符号和无符号算术的指令,因此使用有符号和无符号整数之间的差异归结为编译器使用的整数。
如果两者中的任何一个速度更快,则完全取决于处理器,如果有的话,差异可能很小。
实际上,有符号整数和无符号整数之间的性能差异比接受答案所建议的更为普遍。无论常量是否为2的幂,都可以使无符号整数除以任何常数都快于有符号整数除以常数。参见http://ridiculousfish.com/blog/posts/labor-of-division-episode-iii.html
在他的文章结尾,他包括以下部分:
一个自然的问题是,相同的优化是否可以改善有符号除法?不幸的是,由于以下两个原因,它似乎没有出现:
红利的增加必须成为幅度的增加,即,如果n> 0,则增加,如果n <0,则减少。这会带来额外的费用。
不合作的除数的惩罚只有有符号除法的一半,从而有较小的改进余地。
因此,似乎可以使舍入算法在带符号除法中起作用,但性能不如标准舍入算法。
对于无符号类型,不仅被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 ║
╚═════════════╧══════════╧══════════════╝
当然,这些都是很古老的。晶体管更多的新架构可能会缩小差距,但基本原理仍然适用:通常,您需要更多的宏操作,更多的逻辑,更多的延迟来执行有符号除法
简而言之,不要在事实之前就打扰。但是,请稍后再做。
如果要获得性能,则必须使用编译器的性能优化,这可能会违反常识。要记住的一件事是,不同的编译器可以不同地编译代码,并且它们本身具有不同种类的优化。如果我们在谈论g++
编译器,并在谈论通过使用-Ofast
或至少使用一个-O3
标志来最大化其优化级别,以我的经验,它可以将long
类型编译为比任何unsigned
类型甚至甚至更好的性能的代码int
。
这是根据我的经验,我建议您首先编写完整的程序,然后再处理此类事情,只有当您掌握了实际的代码并且可以通过优化对其进行编译,以尝试选择实际执行的类型时最好。这也是关于代码优化以提高性能的很好的一般性建议,请先快速编写代码,然后尝试进行优化编译,并进行调整以查看最有效的方法。而且,您还应该尝试使用不同的编译器来编译程序,然后选择输出性能最高的机器代码的程序。
经过优化的多线程线性代数计算程序可以轻松实现> 10倍的性能差异,分别是经过优化和未经优化的性能差异。所以这很重要。
在很多情况下,优化器输出与逻辑矛盾。例如,我有一个案例,两者之间的差异a[x]+=b
与a[x]=b
更改后的程序执行时间差不多是2倍。不,a[x]=b
不是更快。
例如,NVidia声明了对GPU进行编程的方法:
注意:正如已经建议的最佳实践一样,应尽可能采用有符号算法而不是无符号算法,以实现SMM的最佳吞吐量。C语言标准对无符号数学的溢出行为施加了更多限制,从而限制了编译器优化的机会。
IIRC在x86上签名/未签名应该没有任何区别。另一方面,短/长则是另一回事,因为必须将数据移入RAM或从RAM移出的数据量会长一些(其他原因可能包括强制转换操作,如将长短扩展为长)。
有符号和无符号整数都将始终作为单个时钟指令运行,并且具有相同的读写性能,但是根据Andrei Alexandrescu博士的说法,无符号优先于有符号。原因是您可以在相同位数中容纳两倍数量的数字,因为您不会浪费符号位,并且将使用更少的指令来检查负数,从而从减少的ROM中获得更高的性能。以我对Kabuki VM的经验,该功能具有超高性能脚本实现,很少需要在处理内存时实际需要一个带符号的数字。我花了很多年的时间对有符号和无符号数字进行指针算术运算,但是当不需要任何符号位时,我发现带符号没有任何好处。
带符号的2的补码整数可以执行负除以2的次幂,这是使用位移位执行2的乘数和除法时首选的符号。请观看更多来自Andrei的YouTube视频,了解更多优化技术。您还可以在我的文章中找到有关世界上最快的Integer-to-String转换算法的一些不错的信息。
传统上int
是目标硬件平台的本机整数格式。任何其他整数类型都可能导致性能损失。
编辑:
在现代系统上情况略有不同:
int
实际上,出于兼容性原因,在64位系统上可能是32位。我相信这会在Windows系统上发生。
int
在某些情况下,现代编译器可能在为较短类型执行计算时隐式使用。
int
)仍为32位宽,但是64位类型(long
或long long
,取决于OS)应至少与之一样快。
int
在我所知道的所有系统(Windows,Linux,Mac OS X,无论处理器是否为64位)上,它总是32位宽。这long
是不同的类型:Windows上为32位,Linux和OS X上为一个字
int
不必总是32位宽。
无符号整数的优势在于,您可以将它们都存储并视为位流,我的意思是只是一个没有符号的数据,因此乘法和除法通过移位操作变得更容易(更快)