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

来源:互联网 发布:jquery.loading.js 编辑:程序博客网 时间:2024/06/04 14:52

三目运算符和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-else00001  033%  Tri-size = 10 0000StopWatch '': running time (millis) = 29-----------------------------------------ms     %     Task name-----------------------------------------00016  055%  if-else00013  045%  Tri-size = 100 0000StopWatch '': running time (millis) = 279-----------------------------------------ms     %     Task name-----------------------------------------00150  054%  if-else00129  046%  Tri-

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

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

现象看本质

问题剖析

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

这里写图片描述

字节码层面上剖析问题

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

[a = 100; b = 500] -----肯定会走 elsevoid 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 条字节码处执行。

这里写图片描述
这里需要引进一个概念:java虚拟机字节码执行引擎

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

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

这里写图片描述


  • 回到主题中,三目运算符效率比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],edx39:               else00401087   jmp         main+7Fh (0040108f)40:                   temp=b;00401089   mov         eax,dword ptr [ebp-14h]0040108C   mov         dword ptr [ebp-18h],eax51:               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],eax00401101   jmp         main+0F9h (00401109)00401103   mov         ecx,dword ptr [ebp-14h]00401106   mov         dword ptr [ebp-24h],ecx00401109   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 ,只有单步调试才不会出现。

原创粉丝点击