web-dev-qa-db-ja.com

例外のスローのどの部分が高価ですか?

Javaでは、実際にエラーが発生していないときにロジックの一部としてスロー/キャッチを使用することは一般に悪い考えです(一部)例外のスローを伴わない制御構造。

私の質問は、スロー/キャッチ自体で発生するコストですか、例外オブジェクトを作成するときに発生しますか(実行スタックを含む多くのランタイム情報を取得するため)?

言い換えれば、私がやれば

Exception e = new Exception();

しかし、投げないでください、それは投げのほとんどのコストですか、それとも投げ+キャッチの処理は高価ですか?

Try/catchブロックにコードを配置することで、そのコードの実行コストが増加するかどうかは問いません。 。

これを求めるもう1つの方法は、Exceptionのインスタンスを1つ作成し、何度もスローしてキャッチした場合、スローするたびに新しいExceptionを作成するよりも大幅に速いでしょうか?

248
Martin Carney

例外オブジェクトの作成は、他の通常のオブジェクトを作成するよりも高価ではありません。主なコストは、ネイティブ fillInStackTrace メソッドに隠されています。このメソッドは、コールスタックをウォークスルーし、スタックトレースを構築するために必要なすべての情報を収集します:クラス、メソッド名、行番号など.

例外コストが高いという神話は、ほとんどのThrowableコンストラクターが暗黙的にfillInStackTraceを呼び出すという事実に由来しています。ただし、スタックトレースなしでThrowableを作成する constructor が1つあります。インスタンス化が非常に速いスローアブルを作成できます。軽量の例外を作成する別の方法は、fillInStackTraceをオーバーライドすることです。


さて、例外を投げるはどうですか?
実際には、スローされた例外がcaughtである場所に依存します。

コンテキストがインライン化のために複数のメソッドを含むことができるため、同じメソッドで(より正確には同じコンテキストで)キャッチされた場合、throwgotoと同じくらい速くて簡単です(もちろんJITコンパイル後)。

ただし、catchブロックがスタックのどこか深い場合、JVMはスタックフレームをほどく必要があり、これにはかなり長い時間がかかる可能性があります。巻き戻しは、削除されたスタックフレームが所有するモニターの解放を意味するため、synchronizedブロックまたはメソッドが含まれている場合は、さらに時間がかかります。


適切なベンチマークで上記のステートメントを確認できましたが、幸いなことに、すべての側面がHotSpotのパフォーマンスエンジニアAlexey Shipilevの投稿で完全にカバーされているため、これを行う必要はありません: The Exceptional Performance of Lil 'Exception

251
apangin

ほとんどのThrowableコンストラクターでの最初の操作は、 スタックトレースの入力 です。これは、ほとんどの費用がかかります。

ただし、スタックトレースを無効にするフラグを持つ保護されたコンストラクタがあります。 このコンストラクター は、Exceptionを拡張するときにもアクセスできます。カスタム例外タイプを作成すると、スタックトレースの作成を回避し、情報量を減らしてパフォーマンスを向上させることができます。

通常の方法で任意のタイプの単一の例外を作成する場合、スタックトレースを埋めるオーバーヘッドなしで何度も再スローできます。ただし、スタックトレースは、特定のインスタンスでスローされた場所ではなく、構築された場所を反映します。

Javaの現在のバージョンは、スタックトレースの作成を最適化するためにいくつかの試みを行います。ネイティブコードが呼び出されてスタックトレースを埋めます。これにより、トレースがより軽量なネイティブ構造で記録されます。対応するJava StackTraceElement オブジェクトは、getStackTrace()printStackTrace()、またはトレースを必要とする他のメソッドが呼び出された場合にのみ、このレコードから遅延的に作成されます。

スタックトレースの生成を排除する場合、他の主なコストは、スローとキャッチの間でスタックを巻き戻すことです。例外がキャッチされる前に検出される介在フレームが少ないほど、これは速くなります。

例外が本当に例外的な場合にのみスローされるようにプログラムを設計し、このような最適化を正当化するのは困難です。

72
erickson

例外についてはこちらをご覧ください。

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

結論は、スタックトレースの構築とスタックの巻き戻しは高価な部品だということです。以下のコードは、1.7の機能を利用しており、スタックトレースのオンとオフを切り替えることができます。次に、これを使用して、さまざまなシナリオでどのようなコストが発生するかを確認できます

以下は、オブジェクト作成のみのタイミングです。ここにStringを追加したので、スタックを作成しなくても、JavaExceptionオブジェクトとStringを作成してもほとんど違いはありません。スタック書き込みをオンにすると、違いは劇的になります。つまり、少なくとも1桁遅くなります。

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)

以下は、特定の深さのスローから100万回戻るのにかかった時間を示しています。

|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%の時間がかかり、実際のスタックトレースがこの大部分を占めます。 JavaExceptionオブジェクトのインスタンス化の〜93%は、スタックトレースの取得によるものです。つまり、この場合のスタックの巻き戻しには、残りの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
25
Harry

Exceptionスタックトレースを使用したnullの作成には、throwブロックとtry-catchブロックを同時に使用した場合と同じくらいの時間がかかります。ただし、スタックトレースの充填には平均で5倍時間がかかります

パフォーマンスへの影響を示すために、次のベンチマークを作成しました。 -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;
    }

}

出力:

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

これは、NoStackExceptionを作成すると、同じExceptionを繰り返しスローするのとほぼ同じくらいのコストがかかることを意味します。また、Exceptionを作成してそのスタックトレースを埋めるのに、およそ4x時間がかかることも示しています。

12
Austin D

質問のこの部分...

これを求めるもう1つの方法は、Exceptionの1つのインスタンスを作成し、それを何度もスローしてキャッチした場合、スローするたびに新しい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 (%)| 

もちろん、これに関する問題は、スタックトレースが、オブジェクトがスローされた場所ではなく、インスタンスを作成した場所を指すようになったことです。

4
Harry

@AustinDの答えを出発点として使用して、いくつかの調整を行いました。下部のコード。

1つのExceptionインスタンスが繰り返しスローされるケースを追加することに加えて、コンパイラーの最適化をオフにして、正確なパフォーマンス結果を取得できるようにしました。 この回答 に従って、-Djava.compiler=NONEをVM引数に追加しました。 (Eclipseでは、実行構成→引数を編集して、このVM引数を設定します)

結果:

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

そのため、例外の作成には、投げてキャッチするのと同じくらいのコストがかかります。コンパイラがコストの大部分を最適化しないと仮定します。

比較のために、最適化を無効にせずに同じテストを実行します。

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;
    }
}
3
Martin Carney