Which part of throwing an Exception is expensive?
在Java中,当不存在错误时,使用抛出/ catch作为逻辑的一部分通常是一个坏主意(部分),因为抛出和捕获异常是昂贵的,并且在一个循环中进行多次操作通常要比不涉及抛出异常的其他控制结构慢得多。
我的问题是,在抛出/捕获本身,或者在创建异常对象时(因为它获得了很多运行时信息,包括执行堆栈),会产生成本吗?
换句话说,如果我这样做的话
但不要扔它,这是扔的大部分成本,还是扔+接球处理的成本很高?
我不是在问将代码放入try/catch块是否会增加执行代码的成本,而是在问捕获异常是代价高昂的部分,还是创建(调用构造函数)异常是代价高昂的部分。
另一种问这个问题的方法是,如果我创建了一个异常实例,然后一次又一次地抛出并捕获它,那么这是否比每次抛出新的异常都要快得多呢?
创建一个异常对象并不比创建其他常规对象更昂贵。主要成本隐藏在本机
关于高异常成本的神话来自这样一个事实,即大多数
那么抛出一个异常呢?实际上,这取决于在何处捕获抛出的异常。
如果它被捕获在同一个方法中(或者更准确地说,在同一个上下文中,因为上下文可能由于内联而包含多个方法),那么
但是,如果一个
我可以通过适当的基准来确认上述声明,但幸运的是,我不需要这样做,因为Hotspot的绩效工程师Alexey Shipilev:LIL’Exception的出色表现已经完全涵盖了所有方面。
大多数
但是,有一个带有禁用堆栈跟踪标志的受保护构造函数。扩展
如果通过常规方法创建任何类型的单个异常,则可以多次重新抛出它,而无需填充堆栈跟踪的开销。但是,它的堆栈跟踪将反映它的构造位置,而不是在特定实例中抛出的位置。
Java的当前版本尝试优化堆栈跟踪创建。调用本机代码来填充堆栈跟踪,该跟踪以较轻的本机结构记录跟踪。只有当EDCOX1、12、11、EDCOX1、13或其他需要跟踪的方法被调用时,相应的JavaEDCX1×O.0对象才被懒惰地创建。
如果消除了堆栈跟踪的生成,则另一个主要成本是在抛出和捕获之间展开堆栈。捕获异常之前遇到的中间帧越少,速度越快。
设计您的程序,使异常只在真正的异常情况下抛出,这样的优化是很难证明的。
这里有一篇关于例外情况的好文章。
http://shipilev.net/blog/2014/excellant-performance/
结果表明,叠痕构造和叠放是成本较高的零件。下面的代码利用了
以下是单独创建对象的时间。我在这里添加了
1 2 3 |
下面显示了在某个特定深度上投掷100万次后返回的时间。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | |Depth| WriteStack(ms)| !WriteStack(ms)| Diff(%)| | 16| 1428| 243| 588 (%)| | 15| 1763| 393| 449 (%)| | 14| 1746| 390| 448 (%)| | 13| 1703| 384| 443 (%)| | 12| 1697| 391| 434 (%)| | 11| 1707| 410| 416 (%)| | 10| 1226| 197| 622 (%)| | 9| 1242| 206| 603 (%)| | 8| 1251| 207| 604 (%)| | 7| 1213| 208| 583 (%)| | 6| 1164| 206| 565 (%)| | 5| 1134| 205| 553 (%)| | 4| 1106| 203| 545 (%)| | 3| 1043| 192| 543 (%)| |
以下几乎可以肯定是一个粗俗的过度简化…
如果我们取16的深度,在上面写入堆栈,那么对象创建大约需要40%的时间,实际的堆栈跟踪占据了绝大多数时间。实例化JavaException对象的93%是由于正在进行堆栈跟踪。这意味着在这种情况下,展开堆栈将占用其他50%的时间。
当我们关闭堆栈跟踪对象创建帐户时分数IE 20%和堆栈展开现在占80%的时间。
在这两种情况下,堆栈展开需要大量的总时间。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 | public class JavaException extends Exception { JavaException(String reason, int mode) { super(reason, null, false, false); } JavaException(String reason) { super(reason); } public static void main(String[] args) { int iterations = 1000000; long create_time_with = 0; long create_time_without = 0; long create_string = 0; for (int i = 0; i < iterations; i++) { long start = System.nanoTime(); JavaException jex = new JavaException("testing"); long stop = System.nanoTime(); create_time_with += stop - start; start = System.nanoTime(); JavaException jex2 = new JavaException("testing", 1); stop = System.nanoTime(); create_time_without += stop - start; start = System.nanoTime(); String str = new String("testing"); stop = System.nanoTime(); create_string += stop - start; } double interval_with = ((double)create_time_with)/1000000; double interval_without = ((double)create_time_without)/1000000; double interval_string = ((double)create_string)/1000000; System.out.printf("Time to create %d String objects: %.2f (ms) ", iterations, interval_string); System.out.printf("Time to create %d JavaException objects with stack: %.2f (ms) ", iterations, interval_with); System.out.printf("Time to create %d JavaException objects without stack: %.2f (ms) ", iterations, interval_without); JavaException jex = new JavaException("testing"); int depth = 14; int i = depth; double[] with_stack = new double[20]; double[] without_stack = new double[20]; for(; i > 0 ; --i) { without_stack[i] = jex.timerLoop(i, iterations, 0)/1000000; with_stack[i] = jex.timerLoop(i, iterations, 1)/1000000; } i = depth; System.out.printf("|Depth| WriteStack(ms)| !WriteStack(ms)| Diff(%%)| "); for(; i > 0 ; --i) { double ratio = (with_stack[i] / (double) without_stack[i]) * 100; System.out.printf("|%5d| %14.0f| %15.0f| %2.0f (%%)| ", i + 2, with_stack[i] , without_stack[i], ratio); //System.out.printf("%d\t%.2f (ms) ", i, ratio); } } private int thrower(int i, int mode) throws JavaException { ExArg.time_start[i] = System.nanoTime(); if(mode == 0) { throw new JavaException("without stack", 1); } throw new JavaException("with stack"); } private int catcher1(int i, int mode) throws JavaException{ return this.stack_of_calls(i, mode); } private long timerLoop(int depth, int iterations, int mode) { for (int i = 0; i < iterations; i++) { try { this.catcher1(depth, mode); } catch (JavaException e) { ExArg.time_accum[depth] += (System.nanoTime() - ExArg.time_start[depth]); } } //long stop = System.nanoTime(); return ExArg.time_accum[depth]; } private int bad_method14(int i, int mode) throws JavaException { if(i > 0) { this.thrower(i, mode); } return i; } private int bad_method13(int i, int mode) throws JavaException { if(i == 13) { this.thrower(i, mode); } return bad_method14(i,mode); } private int bad_method12(int i, int mode) throws JavaException{ if(i == 12) { this.thrower(i, mode); } return bad_method13(i,mode); } private int bad_method11(int i, int mode) throws JavaException{ if(i == 11) { this.thrower(i, mode); } return bad_method12(i,mode); } private int bad_method10(int i, int mode) throws JavaException{ if(i == 10) { this.thrower(i, mode); } return bad_method11(i,mode); } private int bad_method9(int i, int mode) throws JavaException{ if(i == 9) { this.thrower(i, mode); } return bad_method10(i,mode); } private int bad_method8(int i, int mode) throws JavaException{ if(i == 8) { this.thrower(i, mode); } return bad_method9(i,mode); } private int bad_method7(int i, int mode) throws JavaException{ if(i == 7) { this.thrower(i, mode); } return bad_method8(i,mode); } private int bad_method6(int i, int mode) throws JavaException{ if(i == 6) { this.thrower(i, mode); } return bad_method7(i,mode); } private int bad_method5(int i, int mode) throws JavaException{ if(i == 5) { this.thrower(i, mode); } return bad_method6(i,mode); } private int bad_method4(int i, int mode) throws JavaException{ if(i == 4) { this.thrower(i, mode); } return bad_method5(i,mode); } protected int bad_method3(int i, int mode) throws JavaException{ if(i == 3) { this.thrower(i, mode); } return bad_method4(i,mode); } private int bad_method2(int i, int mode) throws JavaException{ if(i == 2) { this.thrower(i, mode); } return bad_method3(i,mode); } private int bad_method1(int i, int mode) throws JavaException{ if(i == 1) { this.thrower(i, mode); } return bad_method2(i,mode); } private int stack_of_calls(int i, int mode) throws JavaException{ if(i == 0) { this.thrower(i, mode); } return bad_method1(i,mode); } } class ExArg { public static long[] time_start; public static long[] time_accum; static { time_start = new long[20]; time_accum = new long[20]; }; } |
本例中的堆栈帧与您通常会找到的相比非常小。
您可以使用javap查看字节码
1 | javap -c -v -constants JavaException.class |
这是方法4…
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | protected int bad_method3(int, int) throws JavaException; flags: ACC_PROTECTED Code: stack=3, locals=3, args_size=3 0: iload_1 1: iconst_3 2: if_icmpne 12 5: aload_0 6: iload_1 7: iload_2 8: invokespecial #6 // Method thrower:(II)I 11: pop 12: aload_0 13: iload_1 14: iload_2 15: invokespecial #17 // Method bad_method4:(II)I 18: ireturn LineNumberTable: line 63: 0 line 64: 12 StackMapTable: number_of_entries = 1 frame_type = 12 /* same */ Exceptions: throws JavaException |
使用
我创建了以下基准来演示对性能的影响。我将
1 2 3 4 5 | class NoStackException extends Exception{ public NoStackException() { super("",null,false,false); } } |
基准代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 | public class ExceptionBenchmark { private static final int NUM_TRIES = 100000; public static void main(String[] args) { long throwCatchTime = 0, newExceptionTime = 0, newObjectTime = 0, noStackExceptionTime = 0; for (int i = 0; i < 30; i++) { throwCatchTime += throwCatchLoop(); newExceptionTime += newExceptionLoop(); newObjectTime += newObjectLoop(); noStackExceptionTime += newNoStackExceptionLoop(); } System.out.println("throwCatchTime =" + throwCatchTime / 30); System.out.println("newExceptionTime =" + newExceptionTime / 30); System.out.println("newStringTime =" + newObjectTime / 30); System.out.println("noStackExceptionTime =" + noStackExceptionTime / 30); } private static long throwCatchLoop() { Exception ex = new Exception(); //Instantiated here long start = System.currentTimeMillis(); for (int i = 0; i < NUM_TRIES; i++) { try { throw ex; //repeatedly thrown } catch (Exception e) { // do nothing } } long stop = System.currentTimeMillis(); return stop - start; } private static long newExceptionLoop() { long start = System.currentTimeMillis(); for (int i = 0; i < NUM_TRIES; i++) { Exception e = new Exception(); } long stop = System.currentTimeMillis(); return stop - start; } private static long newObjectLoop() { long start = System.currentTimeMillis(); for (int i = 0; i < NUM_TRIES; i++) { Object o = new Object(); } long stop = System.currentTimeMillis(); return stop - start; } private static long newNoStackExceptionLoop() { long start = System.currentTimeMillis(); for (int i = 0; i < NUM_TRIES; i++) { NoStackException e = new NoStackException(); } long stop = System.currentTimeMillis(); return stop - start; } } |
输出:
1 2 3 4 | throwCatchTime = 19 newExceptionTime = 77 newObjectTime = 3 noStackExceptionTime = 15 |
这意味着创建一个
这部分问题…
Another way of asking this is, if I made one instance of Exception and
threw and caught it over and over, would that be significantly faster
than creating a new Exception every time I throw?
似乎在问是否创建了一个异常并将其缓存到某个地方可以提高性能。是的。这与关闭正在对象创建中写入的堆栈相同,因为它已经完成了。
这些是我的时间安排,请在这之后阅读警告…
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | |Depth| WriteStack(ms)| !WriteStack(ms)| Diff(%)| | 16| 193| 251| 77 (%)| | 15| 390| 406| 96 (%)| | 14| 394| 401| 98 (%)| | 13| 381| 385| 99 (%)| | 12| 387| 370| 105 (%)| | 11| 368| 376| 98 (%)| | 10| 188| 192| 98 (%)| | 9| 193| 195| 99 (%)| | 8| 200| 188| 106 (%)| | 7| 187| 184| 102 (%)| | 6| 196| 200| 98 (%)| | 5| 197| 193| 102 (%)| | 4| 198| 190| 104 (%)| | 3| 193| 183| 105 (%)| |
当然,这个问题是您的堆栈跟踪现在指向实例化对象的位置,而不是从中抛出对象的位置。
以@austind的回答为起点,我做了一些调整。代码在底部。
除了添加重复抛出一个异常实例的情况外,我还关闭了编译器优化,这样我们可以得到准确的性能结果。根据这个答案,我在vm参数中添加了
结果:
1 2 3 4 |
因此,创建异常的成本大约是抛出+捕获异常的5倍。假设编译器没有优化大部分成本。
为了进行比较,以下是相同的测试运行,但不禁用优化:
1 2 3 4 |
代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 | public class ExceptionPerformanceTest { private static final int NUM_TRIES = 1000000; public static void main(String[] args) { double numIterations = 10; long exceptionPlusCatchTime = 0, excepTime = 0, strTime = 0, throwTime = 0; for (int i = 0; i < numIterations; i++) { exceptionPlusCatchTime += exceptionPlusCatchBlock(); excepTime += createException(); throwTime += catchBlock(); strTime += createString(); } System.out.println("new Exception + throw/catch =" + exceptionPlusCatchTime / numIterations); System.out.println("new Exception only =" + excepTime / numIterations); System.out.println("throw/catch only =" + throwTime / numIterations); System.out.println("new String (benchmark) =" + strTime / numIterations); } private static long exceptionPlusCatchBlock() { long start = System.currentTimeMillis(); for (int i = 0; i < NUM_TRIES; i++) { try { throw new Exception(); } catch (Exception e) { // do nothing } } long stop = System.currentTimeMillis(); return stop - start; } private static long createException() { long start = System.currentTimeMillis(); for (int i = 0; i < NUM_TRIES; i++) { Exception e = new Exception(); } long stop = System.currentTimeMillis(); return stop - start; } private static long createString() { long start = System.currentTimeMillis(); for (int i = 0; i < NUM_TRIES; i++) { Object o = new String("" + i); } long stop = System.currentTimeMillis(); return stop - start; } private static long catchBlock() { Exception ex = new Exception(); //Instantiated here long start = System.currentTimeMillis(); for (int i = 0; i < NUM_TRIES; i++) { try { throw ex; //repeatedly thrown } catch (Exception e) { // do nothing } } long stop = System.currentTimeMillis(); return stop - start; } } |