释放双眼,带上耳机,听听看~!
今天,简单讲讲android里使用try–catch语句是否会影响性能。
我在app的代码里有一些for循环里面有try – catch语句,担心循环里一直执行try – catch语句会影响效率,所以在网上查询了资料,后来发现并不影响性能。这里记录一下。
1、JAVA性能调优-将try/catch块移出循环
据说把try/catch块放入循环体内,会极大的影响性能。因为使用了try/catch模块的使用,会让JAVA虚拟机做很多额外的工作。就好比对每个人说,“嗨,哥们,路上可能有蛇。于是听到的人只好手拿木棍,小心翼翼的往前走”。
把try/catch块放到循环外面,就好比对一大群人说,“嗨,兄弟们,路上可能有蛇。于是听到的人安排部分人员拿木棍往前走,其他人基本不受影响”
这个理论蛮不错的,测试下对性能的实际影响
2、将try/catch块在循环条件进行比对的源代码
package com.java.test;
import java.util.ArrayList;
import java.util.List;
/**
* 使用预热模式 JVM参数:-XX:PrintCompilation
* 目标:测试在循环中使用try/catch对性能的影响
*
*/
public class EasyTryCatchTest {
ListaList = new ArrayList();
public staticvoid main(String[] args) throws Exception {
intwarmUpCycles = 10000000; // 预热次数
inttestCycles = 50000000; // 正式执行次数
EasyTryCatchTestse = new EasyTryCatchTest();
System.out.println("...预热循环开始 ...");
longcatchTime = se.runCatchLoop(warmUpCycles);
longnotcatchTime = se.runCatchNotLoop(warmUpCycles);
System.out.println("...预热结束");
System.out.println("...预热阶段,try/catch在循环中耗时: " + catchTime);
System.out.println("...预热阶段,try/catch不在循环中耗时: " + notcatchTime);
System.out.println("...进入正式循环 ...");
catchTime =se.runCatchLoop(testCycles);
notcatchTime= se.runCatchNotLoop(testCycles);
System.out.println("...正式运行阶段,try/catch在循环中耗时: " + catchTime);
System.out.println("...正式运行阶段,try/catch不在循环中耗时: " + notcatchTime);
}
publicEasyTryCatchTest(){
aList.add("0");
}
// try / catch 在具体的循环体(内圈 循环 )中
private longrunCatchLoop(int iterations) {
// 开始计时
longstartTime = System.nanoTime();
for (intloop = 0; loop < iterations; loop++) {
try {
Stringtemp = aList.get(0);
} catch(Exception e) {}
}
// 计时完成
longelapsedTime = System.nanoTime();
return(elapsedTime - startTime);
}
// try / catch 在不在具体的循环体(内圈 循环 )中
public longrunCatchNotLoop(int iterations) {
// 开始计时
longstartTime = System.nanoTime();
try {
for (intloop = 0; loop < iterations; loop++) {
Stringtemp = aList.get(0);
}
} catch(Exception e) {}
// 计时完成
longelapsedTime = System.nanoTime();
return(elapsedTime - startTime);
}
}
3、运行结果
…预热循环开始 …
…预热结束
…预热阶段,try/catch在循环中耗时: 76507316
…预热阶段,try/catch不在循环中耗时: 76292613
…进入正式循环 …
…正式运行阶段,try/catch在循环中耗时: 389151690
…正式运行阶段,try/catch不在循环中耗时: 389874615
4、结论
从测试结果来看,可能我们的JDK(1.6)会自动优化字节码的缘故,因此try/catch是否在循环中,对整体性能的影响几乎微乎其微,389151690 389874615 差距非常的小。
以上是没有发生异常的情况,如果发生异常,那么也就无法比较了。
这里再举一个例子:
讨论的问题
当时讨论的是这样的问题:
比较下面两种try catch写法,哪一种性能更好。
for (int i = 0; i < 1000000; i++) {
try {
Math.sin(j);
} catch (Exception e) {
e.printStackTrace();
}
}
try {
for (int i = 0; i < 1000000; i++) {
Math.sin(j);
}
} catch (Exception e) {
e.printStackTrace();
}
结论
在没有发生异常时,两者性能上没有差异。如果发生异常,两者的处理逻辑不一样,已经不具有比较的意义了。
分析
要知道这两者的区别,最好的办法就是查看编译后生成的Java字节码。看一下try catch到底做了什么。
下面是我的测试代码
import org.openjdk.jmh.annotations.Benchmark;
/**
* Created by kevin on 16-7-10.
*/
public class ForTryAndTryFor {
public static void main(String[] args) {
tryFor();
forTry();
}
public static void tryFor() {
int j = 3;
try {
for (int i = 0; i < 1000; i++) {
Math.sin(j);
}
} catch (Exception e) {
e.printStackTrace();
}
}
public static void forTry() {
int j = 3;
for (int i = 0; i < 1000; i++) {
try {
Math.sin(j);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
使用javap -c fileName.class输出对应的字节码
Compiled from "ForTryAndTryFor.java"
public class com.kevin.java.performancetTest.ForTryAndTryFor {
public com.kevin.java.performancetTest.ForTryAndTryFor();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."":()V
4: return
public static void main(java.lang.String[]);
Code:
0: invokestatic #2 // Method tryFor:()V
3: invokestatic #3 // Method forTry:()V
6: return
public static void tryFor();
Code:
0: iconst_3
1: istore_0
2: iconst_0
3: istore_1
4: iload_1
5: sipush 1000
8: if_icmpge 23
11: iload_0
12: i2d
13: invokestatic #4 // Method java/lang/Math.sin:(D)D
16: pop2
17: iinc 1, 1
20: goto 4
23: goto 31
26: astore_1
27: aload_1
28: invokevirtual #6 // Method java/lang/Exception.printStackTrace:()V
31: return
Exception table:
from to target type
2 23 26 Class java/lang/Exception
public static void forTry();
Code:
0: iconst_3
1: istore_0
2: iconst_0
3: istore_1
4: iload_1
5: sipush 1000
8: if_icmpge 31
11: iload_0
12: i2d
13: invokestatic #4 // Method java/lang/Math.sin:(D)D
16: pop2
17: goto 25
20: astore_2
21: aload_2
22: invokevirtual #6 // Method java/lang/Exception.printStackTrace:()V
25: iinc 1, 1
28: goto 4
31: return
Exception table:
from to target type
11 17 20 Class java/lang/Exception
}
指令含义不是本文的重点,所以这里就不介绍具体的含义,感兴趣可以到Oracle官网查看相应指令的含义The Java Virtual
Machine Instruction Set
好了让我们来关注一下try catch 到底做了什么。我们就拿forTry方法来说吧,从输出看,字节码分两部分,code(指令)和exception table(异常表)两部分。当将java源码编译成相应的字节码的时候,如果方法内有try catch异常处理,就会产生与该方法相关联的异常表,也就是Exception table:部分。异常表记录的是try
起点和终点,catch方法体所在的位置,以及声明捕获的异常种类。通过这些信息,当程序出现异常时,java虚拟机就会查找方法对应的异常表,如果发现有声明的异常与抛出的异常类型匹配就会跳转到catch处执行相应的逻辑,如果没有匹配成功,就会回到上层调用方法中继续查找,如此反复,一直到异常被处理为止,或者停止进程。具体介绍可以看这篇文章How
the Java virtual machine handles exceptions
所以,try 在反映到字节码上的就是产生一张异常表,只有发生异常时才会被使用。由此得到出开始的结论。
这里再对结论扩充:
try catch与未使用try catch代码区别在于,前者禁止try语句块中的代码进行优化,例如重排序,try catch里面的代码是不会被编译器优化重排的。对于上面两个函数而言,只是异常表中try起点和终点位置不一样。至于刚刚说到的指令重排的问题,由于for循环条件部分符合happens- before原则,因此两者的for循环都不会发生重排。当然只是针对这里而言,在实际编程中,还是提倡try
catch范围尽量小,这样才可以充分发挥java编译器的优化能力。
至于网上得出的for-try比try-catch要快是由于cpu执行代码时可能有环境的影响因素,因为手机可能同时执行其他线程,所以测试的时间不准确。具体来说:
原因至少有下面这些:System.currentTimeMillis()测量的只是逝去的时间,并没有反映出cpu执行该函数真正消耗的时间。
这导致线程未被分配cpu资源时,等待cpu的时间也会被计算进去
JIT优化导致结果出现偏差。
像这种多次循环非常容易触发JIT的优化机制,关于JIT,这里简短的介绍一下
在Java编程语言和环境中,即时编译器(JIT compiler,just-in-time compiler)是一个把Java的字节码(包括需要被解释的指令的程序)转换成可以直接发送给处理器的指令的程序。当你写好一个Java程序后,源语言的语句将由Java编译器编译成字节码,而不是编译成与某个特定的处理器硬件平台对应的指令代码(比如,Intel的Pentium微处理器或IBM的System/390处理器)。字节码是可以发送给任何平台并且能在那个平台上运行的独立于平台的代码。
简单来说,JIT会将某些符合条件(比如,频繁的循环)的字节码被编译成目标的机器指令直接执行,从而加快执行速度。可以通过配置-XX:+PrintCompilation参数,观察JIT。当JIT执行优化时,会在终端输出相应的优化信息。
类加载时间也被统计进来了。
类首次被使用时,会触发类加载,产生了时间消耗。
由上面的分析不难看出为什么绝大多数时候tryFor会比forTry快了,JIT编译耗时和类加载时间会被统计到第一个执行的函数forTry里面。要验证这个也非常简单,把两个函数的调用顺序互换,然后再进行测试。
当然,还有一点不能忽略的是System.currentTimeMillis()并不是统计cpu真正执行时间,所以可能测试的结果会有出入。可以使用JProfiler配合Intellij IDEA进行测试,下面会提到。由于这些因素影响着测试结果,从而使得测试结果扑朔迷离
所以总结一下,在没有异常时,try-catch不影响性能,当发生异常时,才会有多余的消耗。
android try catch并不影响性能就讲完了。
就这么简单。