Phần nào của việc ném Ngoại lệ là đắt?


256

Trong Java, sử dụng throw / Catch như một phần của logic khi thực sự không có lỗi thường là một ý tưởng tồi (một phần) bởi vì ném và bắt một ngoại lệ là tốn kém và thực hiện nhiều lần trong một vòng lặp thường chậm hơn nhiều so với khác cấu trúc kiểm soát không liên quan đến ngoại lệ.

Câu hỏi của tôi là, chi phí phát sinh trong lần ném / bắt chính nó hay khi tạo đối tượng Exception (vì nó nhận được nhiều thông tin thời gian chạy bao gồm cả ngăn xếp thực thi)?

Nói cách khác, nếu tôi làm

Exception e = new Exception();

nhưng đừng ném nó, đó có phải là hầu hết các chi phí ném, hoặc là ném + bắt xử lý những gì tốn kém?

Tôi không hỏi liệu việc đặt mã vào khối thử / bắt có thêm chi phí cho việc thực thi mã đó hay không, tôi đang hỏi liệu bắt Exception là phần đắt tiền hay tạo (gọi hàm tạo cho) Exception là phần đắt .

Một cách khác để hỏi điều này là, nếu tôi tạo một trường hợp Ngoại lệ và ném và bắt nó nhiều lần, điều đó có nhanh hơn đáng kể so với việc tạo Ngoại lệ mới mỗi lần tôi ném không?


20
Tôi tin rằng nó đang điền và điền vào dấu vết ngăn xếp.
Elliott Frisch


"Nếu tôi tạo một trường hợp Ngoại lệ và ném và bắt nó lặp đi lặp lại", khi ngoại lệ được tạo, ngăn xếp của nó được lấp đầy, điều đó có nghĩa là nó sẽ luôn luôn giống nhau bất kể nơi nào nó bị ném. Nếu stacktrace không quan trọng đối với bạn hơn bạn có thể thử ý tưởng của mình nhưng điều này có thể khiến việc gỡ lỗi rất khó khăn nếu không phải là không thể trong một số trường hợp.
Pshemo

2
@Pshemo Tôi không có kế hoạch thực sự làm điều này bằng mã, tôi đang hỏi về hiệu suất và sử dụng sự vô lý này như một ví dụ để nó có thể tạo ra sự khác biệt.
Martin Carney

@MartinCarney Tôi đã thêm một câu trả lời cho đoạn cuối cùng của bạn, tức là bộ đệm ẩn Ngoại lệ có hiệu suất tăng. Nếu nó hữu ích tôi có thể thêm mã, nếu không tôi có thể xóa câu trả lời.
Harry

Câu trả lời:


267

Tạo một đối tượng ngoại lệ không tốn kém hơn so với việc tạo các đối tượng thông thường khác. Chi phí chính được ẩn trong fillInStackTracephương thức gốc đi qua ngăn xếp cuộc gọi và thu thập tất cả thông tin cần thiết để xây dựng theo dõi ngăn xếp: các lớp, tên phương thức, số dòng, v.v.

Huyền thoại về chi phí ngoại lệ cao xuất phát từ thực tế là hầu hết các nhà Throwablexây dựng gọi ngầm fillInStackTrace. Tuy nhiên, có một nhà xây dựng để tạo ra một Throwablemà không có một vết đống. Nó cho phép bạn thực hiện các cú ném rất nhanh để khởi tạo. Một cách khác để tạo ngoại lệ nhẹ là ghi đè fillInStackTrace.


Bây giờ những gì về ném một ngoại lệ?
Trong thực tế, nó phụ thuộc vào nơi một ngoại lệ ném được bắt .

Nếu nó được bắt trong cùng một phương thức (hoặc chính xác hơn là trong cùng một bối cảnh, vì bối cảnh có thể bao gồm một số phương thức do nội tuyến), thì throw nhanh và đơn giản như goto(tất nhiên, sau khi biên dịch JIT).

Tuy nhiên, nếu một catchkhối nằm ở đâu đó sâu hơn trong ngăn xếp, thì JVM cần phải thư giãn các khung ngăn xếp và việc này có thể mất nhiều thời gian hơn đáng kể. Nó thậm chí còn lâu hơn, nếu có synchronizedcác khối hoặc phương thức liên quan, bởi vì việc tháo gỡ ngụ ý giải phóng các màn hình thuộc sở hữu của các khung ngăn xếp bị loại bỏ.


Tôi có thể xác nhận các tuyên bố trên bằng các điểm chuẩn thích hợp, nhưng may mắn là tôi không cần phải làm điều này, vì tất cả các khía cạnh đã được trình bày hoàn hảo trong bài viết của kỹ sư hiệu suất của HotSpot Alexey Shipilev: The Performance Performance of Lil 'Exception .


8
Như đã lưu ý trong bài viết và được đề cập ở đây, kết quả cuối cùng là chi phí ném / bắt ngoại lệ phụ thuộc rất nhiều vào độ sâu của các cuộc gọi. Vấn đề ở đây là tuyên bố "ngoại lệ đắt đỏ" không thực sự chính xác. Một tuyên bố chính xác hơn là các ngoại lệ 'có thể' đắt tiền. Thành thật mà nói, tôi nghĩ rằng chỉ sử dụng các ngoại lệ cho "các trường hợp thực sự đặc biệt" (như trong bài viết) là quá mạnh mẽ. Chúng hoàn hảo cho hầu hết mọi thứ bên ngoài luồng hoàn trả thông thường và thật khó để phát hiện tác động hiệu suất của việc sử dụng chúng theo cách này trong một ứng dụng thực tế.
JimmyJames

14
Nó có thể là giá trị nó để định lượng chi phí ngoại lệ. Ngay cả trong trường hợp xấu nhất được báo cáo trong bài viết khá mệt mỏi này (ném và bắt một ngoại lệ động với một stacktrace thực sự được truy vấn, sâu 1000 khung hình stack), mất 80 micro giây. Điều đó có thể có ý nghĩa nếu hệ thống của bạn cần xử lý hàng ngàn ngoại lệ mỗi giây, nhưng nếu không thì không đáng lo ngại. Và đó là trường hợp xấu nhất; nếu stacktraces của bạn là một ít vệ sinh hơn hoặc bạn không truy vấn stacktrace của chúng, chúng ta có thể xử lý gần một triệu ngoại lệ mỗi giây.
meriton

13
Tôi nhấn mạnh điều này bởi vì nhiều người, khi đọc rằng các ngoại lệ là "đắt", không bao giờ dừng lại để hỏi "đắt so với cái gì", nhưng cho rằng họ là "phần đắt đỏ của chương trình", mà họ rất hiếm khi.
meriton

2
Có một phần không được đề cập ở đây: chi phí tiềm năng trong việc ngăn chặn tối ưu hóa được áp dụng. Một ví dụ cực đoan sẽ là JVM không nội tuyến để tránh các dấu vết ngăn xếp "lầy lội", nhưng tôi đã thấy các điểm chuẩn (vi mô) trong đó sự hiện diện hoặc vắng mặt của các ngoại lệ sẽ tạo ra hoặc phá vỡ tối ưu hóa trong C ++ trước đây.
Matthieu M.

3
@MatthieuM. Các ngoại lệ và các khối thử / bắt không ngăn JVM nội tuyến. Đối với các phương thức biên dịch, dấu vết ngăn xếp thực được xây dựng lại từ bảng khung ngăn xếp ảo được lưu trữ dưới dạng siêu dữ liệu. Tôi không thể nhớ lại tối ưu hóa JIT không tương thích với thử / bắt. Bản thân cấu trúc thử / bắt không thêm bất cứ thứ gì vào mã phương thức, nó chỉ tồn tại dưới dạng bảng ngoại lệ ngoài mã.
apangin

72

Hoạt động đầu tiên trong hầu hết các hàm Throwabletạo là điền vào dấu vết ngăn xếp, đây là nơi mà hầu hết các chi phí là.

Tuy nhiên, có một hàm tạo được bảo vệ có cờ để vô hiệu hóa dấu vết ngăn xếp. Hàm tạo này có thể truy cập được khi mở rộng Exception. Nếu bạn tạo một loại ngoại lệ tùy chỉnh, bạn có thể tránh việc tạo theo dõi ngăn xếp và có hiệu suất tốt hơn với chi phí ít thông tin hơn.

Nếu bạn tạo một ngoại lệ duy nhất của bất kỳ loại nào bằng cách thông thường, bạn có thể ném lại nó nhiều lần mà không cần phải điền vào dấu vết ngăn xếp. Tuy nhiên, dấu vết ngăn xếp của nó sẽ phản ánh nơi nó được xây dựng, chứ không phải nơi nó được ném trong một trường hợp cụ thể.

Các phiên bản hiện tại của Java thực hiện một số nỗ lực để tối ưu hóa việc tạo dấu vết ngăn xếp. Mã riêng được gọi để điền vào dấu vết ngăn xếp, ghi lại dấu vết trong cấu trúc gốc, trọng lượng nhẹ hơn. Các StackTraceElementđối tượng Java tương ứng được tạo một cách lười biếng từ bản ghi này chỉ khi getStackTrace(),printStackTrace() hoặc các phương pháp khác đòi hỏi phải có các dấu vết được gọi là.

Nếu bạn loại bỏ việc tạo dấu vết ngăn xếp, chi phí chính khác là giải phóng ngăn xếp giữa cú ném và cú bắt. Càng ít khung can thiệp gặp phải trước khi ngoại lệ bị bắt, điều này sẽ càng nhanh.

Thiết kế chương trình của bạn để các ngoại lệ chỉ được đưa ra trong các trường hợp thực sự đặc biệt và những tối ưu hóa như thế này rất khó để biện minh.



25

Có một bài viết tốt về Ngoại lệ ở đây.

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

Kết luận là việc xây dựng dấu vết ngăn xếp và tháo gỡ ngăn xếp là những phần đắt tiền. Mã dưới đây tận dụng một tính năng trong 1.7đó chúng ta có thể bật và tắt dấu vết ngăn xếp. Sau đó chúng ta có thể sử dụng điều này để xem các loại chi phí khác nhau có kịch bản

Sau đây là thời gian để tạo đối tượng một mình. Tôi đã thêm vào Stringđây để bạn có thể thấy rằng nếu không có ngăn xếp được viết thì hầu như không có sự khác biệt nào trong việc tạo JavaExceptionĐối tượng và a String. Với stack stack, sự khác biệt là rất lớn, tức là ít nhất một bậc độ lớn chậm hơn.

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)

Sau đây cho thấy mất bao lâu để trở về từ một cú ném ở độ sâu cụ thể một triệu lần.

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

Sau đây gần như chắc chắn là một tổng số đơn giản hóa ...

Nếu chúng ta có độ sâu 16 với việc viết stack, thì việc tạo đối tượng sẽ chiếm khoảng ~ 40% thời gian, dấu vết ngăn xếp thực tế chiếm phần lớn trong số này. ~ 93% việc khởi tạo đối tượng JavaException là do theo dõi ngăn xếp được thực hiện. Điều này có nghĩa là việc tháo gỡ ngăn xếp trong trường hợp này đang chiếm 50% thời gian còn lại.

Khi chúng ta tắt tính năng tạo đối tượng theo dõi ngăn xếp chiếm một phần nhỏ hơn nhiều, tức là 20% và việc hủy bỏ ngăn xếp hiện chiếm 80% thời gian.

Trong cả hai trường hợp, việc tháo gỡ ngăn xếp chiếm một phần lớn trong tổng thời gian.

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

Các khung ngăn xếp trong ví dụ này rất nhỏ so với những gì bạn thường thấy.

Bạn có thể xem mã byte bằng cách sử dụng javap

javap -c -v -constants JavaException.class

tức là đây là phương pháp 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

13

Việc tạo ra Exceptionmột nulldấu vết ngăn xếp mất nhiều thời gian như throwtry-catchkhối với nhau. Tuy nhiên, điền vào dấu vết ngăn xếp mất trung bình 5x lâu hơn .

Tôi đã tạo ra các tiêu chuẩn sau đây để chứng minh tác động đến hiệu suất. Tôi đã thêm vào -Djava.compiler=NONECấu hình Run để tắt tối ưu hóa trình biên dịch. Để đo lường tác động của việc xây dựng dấu vết ngăn xếp, tôi đã mở rộng Exceptionlớp để tận dụng lợi thế của hàm tạo không có ngăn xếp:

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

Mã điểm chuẩn như sau:

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

}

Đầu ra:

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

Điều này ngụ ý rằng việc tạo ra một thứ NoStackExceptionđắt gần bằng việc ném liên tục như vậy Exception. Nó cũng cho thấy rằng việc tạo Exceptionvà điền dấu vết ngăn xếp của nó mất khoảng 4 lần lâu hơn.


1
Bạn có thể thêm một trường hợp nữa khi bạn tạo một phiên bản Ngoại lệ trước thời gian bắt đầu, sau đó ném + bắt liên tục trong một vòng lặp không? Điều đó sẽ cho thấy chi phí của chỉ ném + bắt.
Martin Carney

@MartinCarney Đề nghị tuyệt vời! Tôi cập nhật câu trả lời của tôi để làm điều đó.
Austin D

Tôi đã thực hiện một số điều chỉnh mã kiểm tra của bạn và có vẻ như trình biên dịch đang thực hiện một số tối ưu hóa để ngăn chúng tôi lấy số chính xác.
Martin Carney

@MartinCarney Tôi đã cập nhật câu trả lời để tối ưu hóa trình biên dịch giảm giá
Austin D

FYI, có lẽ bạn nên đọc câu trả lời cho Làm thế nào để tôi viết một điểm chuẩn vi mô chính xác trong Java? Gợi ý: đây không phải là nó.
Daniel Pryden

4

Phần này của câu hỏi ...

Một cách khác để hỏi điều này là, nếu tôi tạo một trường hợp Ngoại lệ và ném và bắt nó nhiều lần, điều đó có nhanh hơn đáng kể so với việc tạo Ngoại lệ mới mỗi lần tôi ném không?

Có vẻ như được hỏi nếu tạo một ngoại lệ và lưu trữ nó ở đâu đó cải thiện hiệu suất. Có nó làm. Nó giống như tắt ngăn xếp được viết khi tạo đối tượng vì nó đã được thực hiện.

Đây là những khoảng thời gian tôi có, xin vui lòng đọc cảnh báo sau ...

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

Tất nhiên, vấn đề với điều này là theo dõi ngăn xếp của bạn bây giờ chỉ đến nơi bạn khởi tạo đối tượng không phải nơi nó được ném từ đó.


3

Sử dụng câu trả lời của @ AustinD làm điểm khởi đầu, tôi đã thực hiện một số điều chỉnh. Mã ở phía dưới.

Ngoài việc thêm trường hợp một trường hợp Ngoại lệ bị ném liên tục, tôi cũng tắt tối ưu hóa trình biên dịch để chúng tôi có thể nhận được kết quả hiệu suất chính xác. Tôi đã thêm -Djava.compiler=NONEvào các đối số VM, theo câu trả lời này . (Trong nhật thực, chỉnh sửa Cấu hình Chạy → Đối số để đặt đối số VM này)

Kết quả:

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

Vì vậy, tạo ra ngoại lệ có chi phí khoảng 5x như ném + bắt nó. Giả sử trình biên dịch không tối ưu hóa phần lớn chi phí.

Để so sánh, đây là cùng một lần chạy thử mà không tắt tối ưu hóa:

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

Mã số:

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

Vô hiệu hóa tối ưu hóa = kỹ thuật tuyệt vời! Tôi sẽ chỉnh sửa câu trả lời ban đầu của mình để không đánh lừa bất cứ ai
Austin D

3
Vô hiệu hóa tối ưu hóa không có cách nào tốt hơn là viết một điểm chuẩn thiếu sót, vì chế độ diễn giải thuần túy không liên quan gì đến hiệu suất trong thế giới thực. Sức mạnh của JVM là trình biên dịch JIT, vậy điểm đo lường một cái gì đó không phản ánh cách thức ứng dụng thực sự hoạt động?
apangin

2
Có rất nhiều khía cạnh của việc tạo, ném và bắt ngoại lệ hơn là hội tụ trong 'điểm chuẩn' này. Tôi đề nghị bạn đọc bài viết này .
apangin
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.