首页 文章

抛出异常的哪一部分是昂贵的?

提问于
浏览
247

在Java中,当实际上没有错误时使用throw / catch作为逻辑的一部分通常是一个坏主意(部分)因为抛出和捕获异常是昂贵的,并且在循环中多次执行它通常比其他更慢控制结构,不涉及抛出异常 .

我的问题是,抛出/捕获本身或创建Exception对象时产生的成本(因为它获得了很多运行时信息,包括执行堆栈)?

换句话说,如果我这样做

Exception e = new Exception();

但是不要扔它,是投掷的大部分成本,还是投掷捕获处理成本高昂?

我不是在将代码放入try / catch块中是否会增加执行该代码的成本,我问的是,捕获Exception是否是昂贵的部分,或者创建(调用构造函数)Exception是否是昂贵的部分 .

问这个问题的另一种方法是,如果我创建了一个Exception实例并且一遍又一遍地抛出它,那么这会比每次抛出时创建一个新的Exception快得多吗?

6 回答

  • 3

    Creating 异常对象并不比创建其他常规对象更昂贵 . 主要成本隐藏在本机fillInStackTrace方法中,该方法遍历调用堆栈并收集构建堆栈跟踪所需的所有信息:类,方法名称,行号等 .

    关于高异常成本的神话来自于大多数 Throwable 构造函数隐含地调用 fillInStackTrace . 但是,有一个constructor来创建没有堆栈跟踪的 Throwable . 它允许您制作非常快速实例化的throwable . 创建轻量级异常的另一种方法是覆盖 fillInStackTrace .


    那么 throwing 怎么样?
    实际上,它取决于抛出异常的位置 caught .

    如果它被捕获在相同的方法中(或者更确切地说,在相同的上下文中,因为上下文可以包括由于内联的几个方法),那么 throwgoto 一样快(和当然,在JIT编译之后) .

    但是,如果 catch 块位于堆栈的更深处,那么JVM需要展开堆栈帧,这可能需要更长的时间 . 如果涉及 synchronized 块或方法,则需要更长的时间,因为展开意味着释放由移除的堆栈帧拥有的监视器 .


    我可以通过适当的基准来确认上述陈述,但幸运的是我不需要这样做,因为HotSpot性能工程师AlexeyShipilëv的帖子已经完全涵盖了所有方面:The Exceptional Performance of Lil' Exception .

  • 25

    大多数 Throwable 构造函数中的第一个操作是fill in the stack trace,,这是大部分费用的地方 .

    但是,有一个受保护的构造函数,其中包含一个禁用堆栈跟踪的标志 . 扩展 Exception 时也可以访问This constructor . 如果创建自定义异常类型,则可以避免创建堆栈跟踪并以更少的信息为代价获得更好的性能 .

    如果通过常规方法创建任何类型的单个异常,则可以多次重新抛出它,而无需填充堆栈跟踪 . 但是,它的堆栈跟踪将反映它的构造位置,而不是它在特定实例中抛出的位置 .

    当前版本的Java尝试优化堆栈跟踪创建 . 调用本机代码以填充堆栈跟踪,该跟踪以较轻的本机结构记录跟踪 . 仅当 getStackTrace()printStackTrace() 或其他需要跟踪的方法被调用时,才会从此记录延迟创建相应的Java StackTraceElement对象 .

    如果消除堆栈跟踪生成,另一个主要成本是在throw和catch之间展开堆栈 . 在捕获异常之前遇到的中间帧越少,这将越快 .

    设计您的程序,以便仅在真正特殊情况下抛出异常,并且很难证明这些优化是合理的 .

  • 4

    这里有关于例外的好文章 .

    http://shipilev.net/blog/2014/exceptional-performance/

    结论是堆栈跟踪结构和堆栈展开是昂贵的部分 . 下面的代码利用了 1.7 中的一个功能,我们可以打开和关闭堆栈跟踪 . 然后我们可以使用它来查看不同场景的成本

    以下是单独创建对象的时间 . 我在这里添加了 String 所以你可以看到,如果没有写入堆栈,创建 JavaException 对象和 String 几乎没有区别 . 随着堆栈写入开启,差异是显着的,即至少慢一个数量级 .

    Time to create million String objects: 41.41 (ms)
    Time to create million JavaException objects with    stack: 608.89 (ms)
    Time to create million JavaException objects without stack: 43.50 (ms)
    

    以下显示了从特定深度的投掷返回一百万次所需的时间 .

    |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%的时间,实际的堆栈跟踪占绝大多数这个 . ~93%实例化JavaException对象是由于采用了堆栈跟踪 . 这意味着在这种情况下展开堆栈占用了其他50%的时间 .

    当我们关闭堆栈跟踪对象创建帐户的小得多,即20%,堆栈展开现在占80%的时间 .

    在这两种情况下,堆栈展开占用了总时间的很大一部分 .

    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)\n", iterations, interval_string);
        System.out.printf("Time to create %d JavaException objects with    stack: %.2f (ms)\n", iterations, interval_with);
        System.out.printf("Time to create %d JavaException objects without stack: %.2f (ms)\n", 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(%%)|\n");
        for(; i > 0 ; --i) {
          double ratio = (with_stack[i] / (double) without_stack[i]) * 100;
          System.out.printf("|%5d| %14.0f| %15.0f| %2.0f (%%)| \n", i + 2, with_stack[i] , without_stack[i], ratio);
          //System.out.printf("%d\t%.2f (ms)\n", 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查看字节码

    javap -c -v -constants JavaException.class
    

    即这是方法4 ...

    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
    
  • 249

    使用 null 堆栈跟踪创建 Exception 所需的时间与 throwtry-catch 块一样多 . 但是, filling the stack trace takes on average 5x longer .

    我创建了以下基准来演示对性能的影响 . 我将 -Djava.compiler=NONE 添加到运行配置以禁用编译器优化 . 为了衡量构建堆栈跟踪的影响,我扩展了 Exception 类以利用无堆栈构造函数:

    class NoStackException extends Exception{
        public NoStackException() {
            super("",null,false,false);
        }
    }
    

    基准代码如下:

    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;
        }
    
    }
    

    Output:

    throwCatchTime = 19
    newExceptionTime = 77
    newObjectTime = 3
    noStackExceptionTime = 15
    

    这意味着创建 NoStackException 与重复抛出相同的 Exception 一样昂贵 . 它还显示创建 Exception 并填充其堆栈跟踪大约需要 4x .

  • 71

    这部分问题......

    另一种问这个问题的方法是,如果我创建了一个Exception实例并且一遍又一遍地抛出它,那么每次抛出时创建一个新的Exception会明快得多吗?

    似乎在询问是否创建异常并在某处缓存它可以提高性能 . 是的,它确实 . 这与关闭正在创建对象的堆栈是一样的,因为它已经完成了 .

    这些是我得到的时间,请在此之后阅读警告......

    |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 (%)|
    

    当然,问题是你的堆栈跟踪现在指向你实例化对象的位置,而不是它被抛出的位置 .

  • 12

    以@ AustinD的答案为出发点,我做了一些调整 . 代码在底部 .

    除了添加重复抛出一个Exception实例的情况之外,我还关闭了编译器优化,以便我们可以获得准确的性能结果 . 我按照this answer-Djava.compiler=NONE 添加到VM参数中 . (在eclipse中,编辑Run Configuration→Arguments以设置此VM参数)

    结果:

    new Exception + throw/catch = 643.5
    new Exception only          = 510.7
    throw/catch only            = 115.2
    new String (benchmark)      = 669.8
    

    所以创建异常的成本大约是抛出它的5倍 . 假设编译器没有优化大部分成本 .

    为了比较,这里是相同的测试运行而没有禁用优化:

    new Exception + throw/catch = 382.6
    new Exception only          = 379.5
    throw/catch only            = 0.3
    new String (benchmark)      = 15.6
    

    码:

    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;
        }
    }
    

相关问题