三目运算符和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的字节码执行引擎中所以性能主要是在这里被损耗
- 建议
- 对于简单的业务逻辑尽量替代为三目运算符,就算不为性能,起码看起来也比较爽代码
- 对于一些核心的业务逻辑,可以进行抽取,利用状态设计模式
ps:时间比较仓猝考虑的不太全面和漏洞,感兴趣的一起剥坑,测试代码中还遇到一个问题,就是在运行多组测试数据时后面的数据会出现Infiity ,只有单步调试才不会出现。
- 三目运算符和if_else引发的血案
- Verilog 运算符优先级引发的血案
- 一个运算符优先级问题引发的血案
- 换行符引发的血案
- ExecutorService引发的血案(三)ThreadPoolExecutor
- 由一个iOS单利写法引发的C语言运算符优先级血案。
- 一个换行符引发的“血案”
- ActiveX引发的“血案”
- size_t引发的血案
- 一个 * 引发的血案
- gets引发的血案
- Print 引发的“血案”
- lease引发的血案
- 一个“-”引发的血案
- MD5引发的血案
- 一个"/"引发的血案
- wrap_content引发的血案
- merge_all引发的血案
- Android Api demo系列(23) (Graphics>Pictures)
- Python 文件数据批量录入数据库(MongoDB)
- LINTCODE——前序遍历和中序遍历树构造二叉树
- Java之——Map与JavaBean互相转化
- java方法调用原理——虚拟机中方法调用
- 三目运算符和if_else引发的血案
- Calico 的网络结构是什么?- 每天5分钟玩转 Docker 容器技术(68)
- 【前端笔试】JavaScript实现字符串全排列
- 嵌入式系统基础及知识及接口技术总结
- java web项目代码不报错但是项目左上角有个红叉
- Android多媒体--学习笔记1
- 2017年9月16日,周结(二十四),最近一段时间的总结
- 欢迎使用CSDN-markdown编辑器
- bzoj1012: [JSOI2008]最大数maxnumber(线段树)