首页 文章

即使从不抛出异常,使用try-catch块是否昂贵?

提问于
浏览
171

我们知道捕获异常是昂贵的 . 但是,即使从未抛出异常,在Java中使用try-catch块也是昂贵的吗?

我发现Stack Overflow问题/答案Why are try blocks expensive?,但它是.NET .

7 回答

  • 6

    try 几乎没有任何费用 . 代码的元数据不是在运行时设置 try 的工作,而是在编译时构造,这样当抛出异常时,它现在执行一个相对昂贵的操作,即向上移动堆栈并查看是否存在任何 try 块 grab 这个例外 . 从外行人的角度来看, try 可能也是免费的 . 它重新抛出数百或数千例外,你仍然不会注意到成本 .


    try 有一些与之相关的小费用 . Java无法对 try 块中的代码进行一些优化 . 例如,Java通常会在方法中重新排列指令以使其运行得更快 - 但Java还需要保证如果抛出异常,就会观察到方法的执行,就好像它的语句一样,在源代码中执行,执行按顺序排队 .

    因为在 try 块中可以抛出异常(在try块的任何一行!异步抛出一些异常,例如在Thread上调用 stop (不推荐使用),甚至OutOfMemoryError几乎可以在任何地方发生)然而它可以被捕获并且代码在之后以相同的方法继续执行,更难以推断可以进行的优化,因此它们不太可能发生 . (有人必须对编译器进行编程才能完成它们,推理并保证其正确性等等 . 注意这样的事情.'d be a big pain for something meant to be '例外') But again, in practice you won'吨 .

  • 2

    让我们衡量吧,好吗?

    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 put它:"premature optimization is the root of all evil" .

  • 39

    try / catch 可能会对性能产生一些影响 . 这是因为它阻止JVM进行一些优化 . Joshua Bloch在"Effective Java,"中说了以下内容:

    •将代码放在try-catch块中会禁止现有JVM实现可能执行的某些优化 .

  • 67

    是的,正如其他人所说的那样, try 块阻止了围绕它的 {} 字符的一些优化 . 特别是,优化器必须假设在块内的任何点都可能发生异常,因此无法保证语句可以执行 .

    例如:

    try {
            int x = a + b * c * d;
            other stuff;
        }
        catch (something) {
            ....
        }
        int y = a + b * c * d;
        use y somehow;
    

    如果没有 try ,则计算分配给 x 的值可以保存为"common subexpression"并重新分配给 y . 但由于 try 无法保证第一个表达式得到评估,因此必须重新计算表达式 . 这在"straight-line"代码中通常不是很大,但在循环中可能很重要 .

    但应注意,这仅适用于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
    }
    

    没有“GOTO” .

  • 7

    又一个微基准标记(source) .

    我创建了一个测试,在其中我根据异常百分比测量try-catch和no-try-catch代码版本 . 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
    

    其中说这些案件之间没有显着差异 .

  • 26

    要理解无法执行优化的原因,了解底层机制很有用 . 我能找到的最简洁的例子是在C宏中实现的:http://www.di.unipi.it/~nids/docs/longjump_try_trow_catch.html

    #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,因此它们会跳过优化他们无法保证安全,但实施本身相当轻松 .

  • 180

    我发现捕获NullPointException非常昂贵 . 对于1.2k操作,时间是200ms和12ms,当我用 if(object==null) 以相同的方式对它进行处理时,这对我来说是相当大的改进 .

相关问题