x> -1 vs x> = 0,是否有性能差异
我听说有一次老师放弃了这个,从那以后一直困扰着我。 假设我们想检查整数x
是否大于或等于0.有两种方法可以检查:
if (x > -1){ //do stuff }
和
if (x >= 0){ //do stuff }
按照这个老师>
会稍微快一点,那么>=
。 在这种情况下,它是Java,但据他说,这也适用于C,C ++和其他语言。 这个陈述有没有道理?
在现实世界中没有任何区别。
让我们来看看各种编译器为不同目标生成的代码。
- 我假设一个有符号的int操作(这似乎是OP的意图)
- 我已经通过调查限制了C和编译器,我已经随手(不可否认的是一个很小的样本 – GCC,MSVC和IAR)
- 启用基本优化(
-O2
用于GCC,/Ox
用于MSVC,-Oh
用于IAR) -
使用以下模块:
void my_puts(char const* s); void cmp_gt(int x) { if (x > -1) { my_puts("non-negative"); } else { my_puts("negative"); } } void cmp_gte(int x) { if (x >= 0) { my_puts("non-negative"); } else { my_puts("negative"); } }
以下是他们每个人为比较操作而生产的东西:
针对ARM的MSVC 11:
// if (x > -1) {... 00000 |cmp_gt| PROC 00000 f1b0 3fff cmp r0,#0xFFFFFFFF 00004 dd05 ble |$LN2@cmp_gt| // if (x >= 0) {... 00024 |cmp_gte| PROC 00024 2800 cmp r0,#0 00026 db05 blt |$LN2@cmp_gte|
定位到x64的MSVC 11:
// if (x > -1) {... cmp_gt PROC 00000 83 f9 ff cmp ecx, -1 00003 48 8d 0d 00 00 // speculative load of argument to my_puts() 00 00 lea rcx, OFFSET FLAT:$SG1359 0000a 7f 07 jg SHORT $LN5@cmp_gt // if (x >= 0) {... cmp_gte PROC 00000 85 c9 test ecx, ecx 00002 48 8d 0d 00 00 // speculative load of argument to my_puts() 00 00 lea rcx, OFFSET FLAT:$SG1367 00009 79 07 jns SHORT $LN5@cmp_gte
针对x86的MSVC 11:
// if (x > -1) {... _cmp_gt PROC 00000 83 7c 24 04 ff cmp DWORD PTR _x$[esp-4], -1 00005 7e 0d jle SHORT $LN2@cmp_gt // if (x >= 0) {... _cmp_gte PROC 00000 83 7c 24 04 00 cmp DWORD PTR _x$[esp-4], 0 00005 7c 0d jl SHORT $LN2@cmp_gte
GCC 4.6.1定位到x64
// if (x > -1) {... cmp_gt: .seh_endprologue test ecx, ecx js .L2 // if (x >= 0) {... cmp_gte: .seh_endprologue test ecx, ecx js .L5
针对x86的GCC 4.6.1:
// if (x > -1) {... _cmp_gt: mov eax, DWORD PTR [esp+4] test eax, eax js L2 // if (x >= 0) {... _cmp_gte: mov edx, DWORD PTR [esp+4] test edx, edx js L5
针对ARM的GCC 4.4.1:
// if (x > -1) {... cmp_gt: .fnstart .LFB0: cmp r0, #0 blt .L8 // if (x >= 0) {... cmp_gte: .fnstart .LFB1: cmp r0, #0 blt .L2
针对ARM Cortex-M3的IAR 5.20:
// if (x > -1) {... cmp_gt: 80B5 PUSH {R7,LR} .... LDR.N R1,??DataTable1 ;; `?<Constant "non-negative">` 0028 CMP R0,#+0 01D4 BMI.N ??cmp_gt_0 // if (x >= 0) {... cmp_gte: 80B5 PUSH {R7,LR} .... LDR.N R1,??DataTable1 ;; `?<Constant "non-negative">` 0028 CMP R0,#+0 01D4 BMI.N ??cmp_gte_0
如果你仍然和我在一起,下面是评估(x > -1)
和(x >= 0)
之间任何音符的区别,
- 针对
(x > -1)
MSVC目标ARM使用cmp r0,#0xFFFFFFFF
,对于(x >= 0)
cmp r0,#0
。 第一条指令的操作码长两个字节。 我想这可能会带来一些额外的时间,所以我们将这称为(x >= 0)
- 针对x86的MSVC使用
cmp ecx, -1
(x > -1)
对test ecx, ecx
(x >= 0)
。 第一条指令的操作码长一个字节。 我想这可能会带来一些额外的时间,所以我们将这称为(x >= 0)
请注意,GCC和IAR为这两种比较生成了相同的机器码(可能的例外是使用哪个寄存器)。 所以根据这个调查,似乎(x >= 0)
有一个“更快”的机会。 但是,最小的操作码字节编码可能有什么好处(我强调可能 )将被其他因素完全掩盖。
如果您发现Java或C#的jitted输出有任何不同,我会感到惊讶。 即使是像8位AVR这样的非常小的目标,我也怀疑你会发现任何不同的音符。
总之,不要担心这个微观优化。 我认为我写在这里已经花费了更多的时间,而不会花费在我所有的执行它们的CPU中积累的这些expression式的任何差异。 如果你有能力测量性能的差异,请把你的努力应用到更重要的事情上,比如研究亚primefaces粒子的行为。
它非常依赖于底层架构,但是任何区别都是微不足道的。
如果有的话,我期望(x >= 0)
稍微快一些,因为与某些指令集(如ARM)上的0
进行比较是免费的。
当然,任何明智的编译器都会select最好的实现,而不pipe源代码中哪个变体。
你的老师一直在读一些真正的旧书。 过去,一些架构缺乏greater than or equal
指令来评估>
所需的机器周期less于>=
,但是现在这些平台很less见。 我build议去可读性,并使用>= 0
。
这里更大的担忧是不成熟的优化 。 许多人认为编写可读代码比编写高效代码更重要[ 1,2 ]。 一旦devise被证实可行,我将把这些优化作为低级图书馆的最后一个阶段。
您不应该经常考虑以代码的可读性为代价对代码进行微小的优化,因为这会使代码难以阅读和维护。 如果需要进行这些优化,请将其抽象为较低级的函数,这样您仍然可以使用更容易阅读的代码。
作为一个疯狂的例子,考虑一下将程序集合到一个愿意放弃额外效率的人,并将Java用于devise,易用性和可维护性。
作为一个方面说明,如果你使用C语言,也许编写一个使用稍微高效的代码的macros是一个更可行的解决scheme,因为它比分散的操作更有效率,可读性和可维护性。
当然,效率和可读性的权衡取决于您的应用程序。 如果这个循环每秒运行10000次,那么这是一个可能的瓶颈,你可能想花时间优化它,但是如果这是一个单独的声明,偶尔会被调用,这可能是不值得的。
是的,有区别,你应该看字节码。
对于
if (x >= 0) { }
字节码是
ILOAD 1 IFLT L1
对于
if (x > -1) { }
字节码是
ILOAD 1 ICONST_M1 IF_ICMPLE L3
版本1更快,因为它使用特殊的零操作数操作
iflt : jump if less than zero
但是可以看到,只有在仅解释模式java -Xint ...
才运行JVM,例如这个Test
int n = 0; for (;;) { long t0 = System.currentTimeMillis(); int j = 0; for (int i = 100000000; i >= n; i--) { j++; } System.out.println(System.currentTimeMillis() - t0); }
在n = 0时显示690毫秒,在n = 1时显示为760毫秒(我使用1而不是-1,因为它更容易演示,这个想法保持不变)
事实上,我相信第二个版本应该稍微快一点,因为它需要一个单一的检查(假设你比较为零,如上所示)。 然而,这样的优化从来没有真正显示,因为大多数编译器会优化这种调用
“> =”是单个操作,就像“>”一样。 与OR不是2个独立的操作。
但> = 0可能更快,因为计算机只需要检查一位(负号)。
按照这个老师>会稍微快一点,那么> =。 在这种情况下,它是Java,但据他说,这也适用于C,C ++和其他语言。 这个陈述有没有道理?
你的老师是根本错误的。 不仅是为什么机会比0更快,而且因为你的编译器/解释器很好地完成了这种局部优化,你可以把所有试图帮助的东西都搞乱。 肯定不是一件好东西教。
你可以阅读: 这个或这个
对不起,在这个关于表演的谈话中插话。
在我离题之前,让我们注意到JVM有特殊的指令来处理不仅是零,而且还有一个到三个的常量。 由此可见,体系结构处理零的能力很可能远远超过编译器优化,而且还有字节码到机器码翻译等等。
我记得从我的x86汇编语言的日子里,有大于( ja
)和大于或等于( jae
)的集中的指令。 你会做这些之一:
; x >= 0 mov ax, [x] mov bx, 0 cmp ax, bx jae above ; x > -1 mov ax, [x] mov bx, -1 cmp ax, bx ja above
这些替代方法需要花费相同的时间,因为这些指令是相同或相似的,并且它们消耗可预测数量的时钟周期。 例如,看这个 。 ja
和jae
确实可以检查不同数量的算术寄存器,但是这种检查主要是由指令需要一个可预测的时间。 这反过来需要保持CPU体系结构的可pipe理性。
但我确实来这里离题了。
在我之前的答案往往是相关的,也表明就性能而言,无论你select哪种方法,你都会处于相同的地位。
这就让你根据其他标准select。 这是我想要logging的地方。 在testing指数时,首选x >= lowerBound
, x > lowerBound - 1
。 这个论点肯定是有意思的,但归结为可读性,因为在这里所有的东西都是平等的。
从概念上来说,你正在testing一个下界, x >= lowerBound
是从你的代码的读者中引出最适合的认知的规范testing。 x + 10 > lowerBound + 9
, x - lowerBound >= 0
和x > -1
都是迂回的方法来testing下界。
再次,抱歉插入,但我觉得这是超出了学术界的重要。 我总是用这些术语来思考,让编译器担心它认为可以摆脱常量和操作符严格性的分钟优化。
首先它高度依赖于硬件平台。 对于现代PC和ARM SoC的差异主要依靠编译器优化。 但对于没有FPU的CPU,有符号math将是灾难。
例如简单的8位CPU,如Intel 8008,8048,8051,Zilog Z80,Motorola 6800甚至现代的RISC PIC或Atmel微控制器都可以通过ALU和8位寄存器进行math运算,基本上只有标志位和z(零值指示符)标志位。 所有严肃的math都是通过图书馆和expression来完成的
BYTE x; if (x >= 0)
肯定会赢,使用JZ或JNZ的asm指令,而不需要昂贵的库调用。