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

jse*_*ano 181 java performance try-catch

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

我发现Stack Overflow问题/答案为什么尝试块昂贵?,但它适用于.NET.

Pat*_*shu 193

try几乎没有任何费用.而不是做设定的工作try运行时,代码的元数据是在编译时结构,即当一个异常被抛出,现在不走了堆栈,看到相对昂贵的操作,如果任何try存在的块,将抓住这个例外.从外行的角度来看,try也许是免费的.它实际上抛出了让你付出代价的例外 - 但除非你抛出数百或数千例外,否则你仍然不会注意到成本.


try有一些与之相关的小成本.Java不能对try块中的代码进行一些优化,否则它会执行.例如,Java将经常在方法重新安排的指令,使其运行速度更快-但Java也需要保证,如果抛出一个异常,该方法的执行被观察到,虽然它的语句,写在源代码中,执行按顺序排队.

因为在一个try块中可以抛出一个异常(在try块的任何一行!异步抛出一些异常,例如通过调用stop一个Thread(不推荐使用),甚至OutOfMemoryError几乎可以在任何地方发生)然而它可以捕获并且代码在之后以相同的方法继续执行,更难以推断可以进行的优化,因此它们不太可能发生.(有人将不得不在编译器程序做出来,推理和保证正确性,等它会是什么东西一个很大的痛苦的意思是"例外")但同样,在实践中,你不会注意到这样的事情.

  • @Patashu*"它实际上是在抛出让您付出代价的例外"*从技术上讲,**抛出异常并不昂贵; 实例化`Exception`对象是大部分时间. (5认同)
  • *异步抛出一些异常*,它们不是异步的,而是抛在安全点上.而这部分*尝试有一些与之相关的小成本.Java无法对try块中的代码进行一些优化,否则它会需要严格的引用.在某些时候,代码很可能在try/catch块中.尝试/捕获块可能更难以内联并为结果构建适当的晶格,但重新排列的部分是模糊的. (2认同)
  • 没有`catch`的`try ... finally`块也会阻止一些优化吗? (2认同)

mer*_*ike 69

让我们衡量吧,好吗?

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);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

在我的电脑上,这打印如下:

try     0.598 ns
no try  0.601 ns
Run Code Online (Sandbox Code Playgroud)

至少在这个简单的例子中,try语句对性能没有可测量的影响.随意测量更复杂的.

一般来说,在您有代码中存在实际性能问题的证据之前,我建议您不要担心语言结构的性能成本.或正如唐纳德克努特所说:"过早优化是万恶之源".

  • 虽然try/no尝试很可能在大多数JVM上都是相同的,但微基准测试是非常有缺陷的. (4认同)
  • 报告的时间是循环的每次迭代*.因为测量只会在总耗时> 0.1秒(或20亿次迭代,这里不是这种情况)的情况下使用.我发现你的断言已完全删除循环很难相信 - 因为如果循环被删除,需要0.1秒才能执行? (3认同)
  • 很多级别:你的意思是结果是在1ns以下计算的?编译代码将同时删除try/catch和循环(从1到n的求和数是一个简单的算术级数和).即使代码包含try/finally,编译器也可以证明,没有什么东西可以抛出.抽象代码只有2个调用站点,它将被克隆和内联.有更多的情况,只需查看microbenchmark上的一些文章,你决定写一个微基准*总是*检查生成的程序集. (2认同)

Evg*_*eev 43

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

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

  • "它阻止JVM做一些优化"......?你能详细说明吗? (24认同)
  • 作为一个例子,@ try块内的Kraken代码(通常是?总是?)不能用try块之外的代码重新排序. (5认同)
  • 请注意,问题是"它是否昂贵",而不是"它对性能有任何影响". (3认同)
  • *添加了Effective Java*的摘录,当然这是java的圣经; 除非有参考文献,否则摘录不会说明任何内容.实际上,java中的任何代码都在某个级别的try/finally中. (3认同)

Hot*_*cks 27

是的,就像其他人所说的那样,一个try区块禁止对其{}周围的角色进行一些优化.特别是,优化器必须假设在块内的任何点都可能发生异常,因此无法保证语句被执行.

例如:

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

如果没有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!");
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

输出:

C:\JavaTools>java TryFinally
Finally!
Exception in thread "main" java.lang.Throwable
        at TryFinally.main(TryFinally.java:4)
Run Code Online (Sandbox Code Playgroud)

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
}
Run Code Online (Sandbox Code Playgroud)

没有"GOTO".


tec*_*rus 7

要理解无法执行优化的原因,了解底层机制很有用.我能找到的最简洁的例子是在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)
Run Code Online (Sandbox Code Playgroud)

编译器通常很难确定跳转是否可以本地化为X,Y和Z,因此它们会跳过无法保证安全的优化,但实现本身相当轻松.

  • 您在try/catch中找到的这些C宏不等同于Java或C#实现,后者发出0运行时指令. (4认同)
  • 我不能谈论C.在C#和Java中,尝试是通过添加元数据而不是代码来实现的.当输入try块时,不会执行任何操作来指示这一点 - 当抛出异常时,将展开堆栈并检查该异常类型的处理程序的元数据(昂贵). (2认同)

And*_*hev 7

又一个微基准(来源).

我创建了一个测试,在其中我根据异常百分比测量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
Run Code Online (Sandbox Code Playgroud)
Percentage | Result (try/if, ns)   
    0%     |      88/90   
    1%     |      89/87    
    10%    |      86/97    
    90%    |      85/83   

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


Mat*_*ski 5

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