我们知道捕获异常的代价很高。但是,即使从未抛出异常,在Java中使用try-catch块的代价也很高吗?
我发现Stack Overflow的问题/答案为什么尝试块昂贵?,但它是用于。net的。
我们知道捕获异常的代价很高。但是,即使从未抛出异常,在Java中使用try-catch块的代价也很高吗?
我发现Stack Overflow的问题/答案为什么尝试块昂贵?,但它是用于。net的。
尝试/捕获可能会对性能产生一些影响。这是因为它阻止JVM进行一些优化。约书亚·布洛赫在《有效的Java》一书中说:
•将代码放置在try-catch块中会抑制现代JVM实现可能执行的某些优化。
try has almost no expense at all. Instead of doing the work of setting up the try at runtime, the code's metadata is structured at compile time such that when an exception is thrown, it now does a relatively expensive operation of walking up the stack and seeing if any try blocks exist that would catch this exception. From a layman's perspective, try may as well be free. It's actually throwing the exception that costs you - but unless you're throwing hundreds or thousands of exceptions, you still won't notice the cost.
Try有一些与之相关的小成本。Java不能对try块中的代码做一些它本来可以做的优化。例如,Java经常会重新安排方法中的指令,以使其运行得更快——但Java也需要保证,如果抛出异常,方法的执行就会被观察到,就像源代码中所写的语句一样,按照直到某一行的顺序执行。
Because in a try block an exception can be thrown (at any line in the try block! Some exceptions are thrown asynchronously, such as by calling stop on a Thread (which is deprecated), and even besides that OutOfMemoryError can happen almost anywhere) and yet it can be caught and code continue to execute afterwards in the same method, it is more difficult to reason about optimizations that can be made, so they are less likely to happen. (Someone would have to program the compiler to do them, reason about and guarantee correctness, etc. It'd be a big pain for something meant to be 'exceptional') But again, in practice you won't notice things like this.
是的,正如其他人所说,try块抑制了围绕它的{}字符的一些优化。特别是,优化器必须假设异常可能发生在块内的任何位置,因此不能保证执行语句。
例如:
try {
int x = a + b * c * d;
other stuff;
}
catch (something) {
....
}
int y = a + b * c * d;
use y somehow;
如果没有try,计算出来的赋值给x的值可以保存为“公共子表达式”,并重用给y赋值。但是由于try,不能保证第一个表达式被求值,因此必须重新计算表达式。这在“直线”代码中通常不是什么大问题,但在循环中可能很重要。
但是,应该注意的是,这只适用于JITCed代码。Javac只做了少量的优化,字节码解释器进入/离开try块的代价为零。(没有生成字节码来标记块边界。)
最好的礼物:
public class TryFinally {
public static void main(String[] argv) throws Throwable {
try {
throw new Throwable();
}
finally {
System.out.println("Finally!");
}
}
}
输出:
C:\JavaTools>java TryFinally
Finally!
Exception in thread "main" java.lang.Throwable
at TryFinally.main(TryFinally.java:4)
javap的输出:
C:\JavaTools>javap -c TryFinally.class
Compiled from "TryFinally.java"
public class TryFinally {
public TryFinally();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]) throws java.lang.Throwable;
Code:
0: new #2 // class java/lang/Throwable
3: dup
4: invokespecial #3 // Method java/lang/Throwable."<init>":()V
7: athrow
8: astore_1
9: getstatic #4 // Field java/lang/System.out:Ljava/io/PrintStream;
12: ldc #5 // String Finally!
14: invokevirtual #6 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
17: aload_1
18: athrow
Exception table:
from to target type
0 9 8 any
}
不是“扫描”。
我们来量一下,好吗?
public abstract class Benchmark {
final String name;
public Benchmark(String name) {
this.name = name;
}
abstract int run(int iterations) throws Throwable;
private BigDecimal time() {
try {
int nextI = 1;
int i;
long duration;
do {
i = nextI;
long start = System.nanoTime();
run(i);
duration = System.nanoTime() - start;
nextI = (i << 1) | 1;
} while (duration < 100000000 && nextI > 0);
return new BigDecimal((duration) * 1000 / i).movePointLeft(3);
} catch (Throwable e) {
throw new RuntimeException(e);
}
}
@Override
public String toString() {
return name + "\t" + time() + " ns";
}
public static void main(String[] args) throws Exception {
Benchmark[] benchmarks = {
new Benchmark("try") {
@Override int run(int iterations) throws Throwable {
int x = 0;
for (int i = 0; i < iterations; i++) {
try {
x += i;
} catch (Exception e) {
e.printStackTrace();
}
}
return x;
}
}, new Benchmark("no try") {
@Override int run(int iterations) throws Throwable {
int x = 0;
for (int i = 0; i < iterations; i++) {
x += i;
}
return x;
}
}
};
for (Benchmark bm : benchmarks) {
System.out.println(bm);
}
}
}
在我的电脑上,它会打印如下内容:
try 0.598 ns
no try 0.601 ns
至少在这个简单的示例中,try语句对性能没有可测量的影响。请随意测量更复杂的参数。
一般来说,我建议不要担心语言结构的性能成本,直到有证据表明代码中存在实际的性能问题。或者正如Donald Knuth所说:“过早的优化是万恶之源”。
要理解为什么不能执行优化,了解底层机制是有用的。我能找到的最简洁的例子是在http://www.di.unipi.it/~nids/docs/longjump_try_trow_catch.html上用C宏实现的
#include <stdio.h>
#include <setjmp.h>
#define TRY do{ jmp_buf ex_buf__; switch( setjmp(ex_buf__) ){ case 0: while(1){
#define CATCH(x) break; case x:
#define FINALLY break; } default:
#define ETRY } }while(0)
#define THROW(x) longjmp(ex_buf__, x)
编译器通常很难确定一个跳转是否可以本地化到X, Y和Z,所以他们跳过优化,他们不能保证安全,但实现本身相当轻。
另一个微基准测试(来源)。
我创建了一个测试,在这个测试中,我根据异常百分比来测量尝试捕获和不尝试捕获的代码版本。10%百分比意味着10%的测试用例除以零用例。在一种情况下,它由try-catch块处理,在另一种情况下由条件操作符处理。这是我的结果表:
OS: Windows 8 6.2 x64
JVM: Oracle Corporation Java HotSpot(TM) 64-Bit Server VM 23.25-b01
Percentage | Result (try/if, ns) 0% | 88/90 1% | 89/87 10% | 86/97 90% | 85/83
这表明这些案例之间没有显著差异。
我发现捕获NullPointException非常昂贵。对于1.2k操作,当我以同样的方式处理if(object==null)时,时间为200ms和12ms,这对我来说是很大的改进。