三目运算符和if_else引发的血案

三目运算符和if_else引发的血案

背景

刚刚入职,在看各种代码。在很多业务逻辑的判读通篇都是用的if-else,思考:程序猿都是爱偷懒的,对于一些简单的逻辑能否该用三目运算符呢,这样整个代码也不会显得十分冗余,在简洁度上面看起来也比较舒服。
既然三目运算符相比与if-else来说,比较简洁,那么他们在性能上又有没差异呢?
结论是:三目运算符的运算速度比if-else的效率高出1~0.5倍左右,当然机器可能也会导致误差和结果波动,百度上有测出2倍多的 ,对着数据表示怀疑- -!!
下文基于三目运算符多效率为什比if-else效率高做了分析

码上告诉你

StopWatch sw = new StopWatch();
sw.start("if-else");
for (int i = 1; i <= size; i++) {
    if (a > b) {
        temp = a;
    } else {
        temp = b;
    }
}
sw.stop();

sw.start("Tri-");
for (int i = 1; i <= size; i++) {
    temp = a > b ? a : b;
}
sw.stop();

System.out.println(sw.prettyPrint());

结果输出

size = 10000;
StopWatch '': running time (millis) = 3
-----------------------------------------
ms     %     Task name
-----------------------------------------
00002  067%  if-else
00001  033%  Tri-

size = 10 0000
StopWatch '': running time (millis) = 29
-----------------------------------------
ms     %     Task name
-----------------------------------------
00016  055%  if-else
00013  045%  Tri-

size = 100 0000
StopWatch '': running time (millis) = 279
-----------------------------------------
ms     %     Task name
-----------------------------------------
00150  054%  if-else
00129  046%  Tri-

ps:这里为了直观显示结果,选用stopwatch来计时;时间统计可以用stopwatch或者System.currentTimeMillis,stopWatch对小数做了过滤,如果自己想测试的话,建议用currentTimeMills这个可以更加直观的看到数值末尾的波动

改用:System.currentTimeMillis 的计算得出的结果
max = If-else_time/ Tri_time
size = 100 0000
[ 9 ] max :1.8148148148148149-------- min : 1.0714285714285714
-----max-----: 1.8148148148148149
-----avg-----: 1.292012617012617
-----min-----: 1.0714285714285714

现象看本质

问题剖析

解决问题的思路就是根据这个:java–>class字节码–>汇编

三目运算符和if_else引发的血案_第1张图片

字节码层面上剖析问题

首先上查看if-else 和 Tri(三目运算符)生成的字节码文件
linweibodeMacBook-Pro:compare linweibo$ javap -c TriAndIf

[a = 100; b = 500] -----肯定会走 else
void funTri();
    Code:
       0: bipush        100
       2: istore_2
       3: sipush        500
       6: istore_3
       7: iload_2
       8: iload_3
       9: if_icmple     16
      12: iload_2
      13: goto          17
      16: iload_3
      17: istore_1
      18: return


  void funIF();
    Code:
       0: bipush        100
       2: istore_2
       3: sipush        500
       6: istore_3
       7: iload_2
       8: iload_3
       9: if_icmple     17
      12: iload_2
      13: istore_1             !!!!!
      14: goto          19
      17: iload_3
      18: istore_1
      19: return

============分隔符===============================
[a = 100 ; b = 50]----短路判断走不走,else  
void funTri();
    Code:
       0: bipush        100
       2: istore_2
       3: bipush        50
       5: istore_3
       6: iload_2
       7: iload_3
       8: if_icmple     15
      11: iload_2
      12: goto          16
      15: iload_3
      16: istore_1
      17: return

  void funIF();
    Code:
       0: bipush        100
       2: istore_2
       3: bipush        50
       5: istore_3
       6: iload_2
       7: iload_3
       8: if_icmple     16
      11: iload_2
      12: istore_1               !!!!!
      13: goto          18
      16: iload_3
      17: istore_1
      18: return

截取一段字节码做分析

void funTri();
    Code:
       0: bipush        100.  
            //当int取值-128~127时,JVM采用bipush指令将常量压入栈中
       2: istore_2
       3: sipush        500
               //当int取值-32768~32767时,JVM采用sipush指令将常量压入栈中
       6: istore_3
            //把引用保存到局部变量表中的索引3的位置,然后引用弹出栈
       7: iload_2
            //把局部变量表中的索引1处的值压入操作栈
       8: iload_3
       9: if_icmple     16
           //比较 iload_2 和 iload_3的值,如果 iload_2 <= iload_3 则跳转到第 16 行代码
      12: iload_2
      13: goto          17
      16: iload_3
      17: istore_1
      18: return

iload_2 和 iload_3 两个指令将两个入参压入操作数栈中;if_icmple会比较栈顶的两个值的大小;如果 a 小都等于 b 值的话,会跳转到 第 16 条字节码处执行。

三目运算符和if_else引发的血案_第2张图片
这里需要引进一个概念:java虚拟机字节码执行引擎

jvm字节码执行引擎时jvm最核心的组成部分之一。它做的事情很简单:输入的是字节码文件,处理过程是字节码解析等效过程,输出的是执行结果。
在这里java的跨平台性就得到了很好的解释:java代码通过编译器编译之后便会生成一个字节码文件,字节码事一种二进制的class文件,它的内容是jvm的指令,而不像c/c++经由编译器直接生成机器码(汇编)。在java中不用担心生成的字节码文件的兼容性,因为所有的jvm都遵守java虚拟机规范,也就是说所有的jvm环境都是一样的,这样一来字节码文件可以在不同平台下的jvm上运行,这也就是我们常说的java的跨平台性。

再啰嗦一下,有得必有失,虽然保证的跨平台性,代价就是java比c/c++性能低的原因之一就是:语言翻译过程中多了一个解析成字节码的环节

三目运算符和if_else引发的血案_第3张图片


  • 回到主题中,三目运算符效率比if_else速度快:在jvm解析class文件这一环节中三目运算符的字节码比if_else少了一个操作指令,这是因为else里头也可以存储

在if_else或者三目运算符外部嵌套高密度for;或者在更加真实的业务场景中可能不需要外加for,里头可能是调用方法或者对象,还有可能递归,那么很有可能持有对象引用太深导致stack区内存泄漏

汇编层面上剖析问题

  • 在VC++6.0中c/C++的if_else 和 三目运算符的 汇编代码
37:               if(a>b)
00401079   mov         ecx,dword ptr [ebp-10h]
0040107C   cmp         ecx,dword ptr [ebp-14h]
0040107F   jle         main+79h (00401089)
38:                   temp=a;
00401081   mov         edx,dword ptr [ebp-10h]
00401084   mov         dword ptr [ebp-18h],edx
39:               else
00401087   jmp         main+7Fh (0040108f)
40:                   temp=b;
00401089   mov         eax,dword ptr [ebp-14h]
0040108C   mov         dword ptr [ebp-18h],eax


51:               temp=a>b?a:b;
004010F3   mov         edx,dword ptr [ebp-10h]
004010F6   cmp         edx,dword ptr [ebp-14h]
004010F9   jle         main+0F3h (00401103)
004010FB   mov         eax,dword ptr [ebp-10h]
004010FE   mov         dword ptr [ebp-24h],eax
00401101   jmp         main+0F9h (00401109)
00401103   mov         ecx,dword ptr [ebp-14h]
00401106   mov         dword ptr [ebp-24h],ecx
00401109   mov         edx,dword ptr [ebp-24h]
0040110C   mov         dword ptr [ebp-18h],edx

If-else无论在何种情况下(在if中或者else中),都是通过先将需要赋的变量值传给寄存器然后再通过寄存器赋值给temp变量 。即
mov edx,b; mov temp,edx ;
然而,对于三目运算,它其中一步却增加多了一个临时变量。
mov ecx,b;mov NEWTEMP,ecx;
mov edx,NEWTEMP;mov temp;edx
因为三目运算是先运算,再赋值!
例如 :
temp = a > b ? a : b ;
a > b ? a : b 是运算, temp = (a > b ? a : b )是赋值。

if语句是直接赋值,不存在运算,所以快一点;
这也就可以解释为什么有时候,if_else 会比 三目执行的快
但是呢,编译器已经帮我们做了优化,所以在汇编成面上,三木运算符和if_else 在效率低差异化上是比较小的。
针对java语言,java看的是字节码不是汇编

总结

  • 三木运算符的效率比if_else效率高的原因

1.在java转成字节码指令时,if_else比三目运算符多了一条指令istore指令
2.因为解析字节码是在jvm的字节码执行引擎中所以性能主要是在这里被损耗

  • 建议
  1. 对于简单的业务逻辑尽量替代为三目运算符,就算不为性能,起码看起来也比较爽代码
  2. 对于一些核心的业务逻辑,可以进行抽取,利用状态设计模式

ps:时间比较仓猝考虑的不太全面和漏洞,感兴趣的一起剥坑,测试代码中还遇到一个问题,就是在运行多组测试数据时后面的数据会出现Infiity ,只有单步调试才不会出现。

你可能感兴趣的:(JavaWeb)