例外がスローされない場合でも、try-catchブロックを使用するとコストがかかりますか?


189

例外をキャッチするのはコストがかかることはわかっています。しかし、例外がスローされない場合でも、Javaでtry-catchブロックを使用するのもコストがかかりますか?

Stack Overflowの質問/回答を見つけました。なぜ、tryブロックが高価なのですか?しかし、それは.NET用です。


30
この質問には本当に意味がありません。Try..catchには非常に特定の目的があります。必要な場合は必要です。とにかく、引っ掛かりなしの試練はどんなポイントなのでしょうか?
JohnFx 2013年

49
try { /* do stuff */ } finally { /* make sure to release resources */ }合法で有用
A4L 2013年

4
そのコストは、利益と比較検討する必要があります。それは一人ではありません。いずれにせよ、費用は相対的であり、それができないことがわかるまでは、何もしないよりも最も明白な方法を使用する方が理にかなっています。プログラムの実行。
ジョエル

4
私は...これは「let's-改革・エラー・コード」タイプの状況につながらないことを願う
mikołak

6
@SAFX:Java7であなたも取り除くことができますfinally使用してブロックtry-with-resources
a_horse_with_no_name

回答:


201

try費用はほとんどかかりません。try実行時にコードを設定する代わりに、コードのメタデータはコンパイル時に構造化されるため、例外がスローされると、スタックをたどり、これtryをキャッチするブロックが存在するかどうかを確認するという比較的コストのかかる操作を実行します。例外。素人の観点からtryは、同様に自由かもしれません。実際にコストがかかる例外をスローしていますが、数百または数千の例外をスローしない限り、コストに気付くことはありません。


tryそれに関連するいくつかのマイナーなコストがあります。Javaは、tryブロック内のコードに対して、それ以外の場合とは異なり、一部の最適化を実行できません。たとえば、Javaは多くの場合、メソッドを高速に実行するためにメソッド内の命令を再配置しますが、Javaは、例外がスローされた場合に、メソッドの実行がソースコードに記述されたステートメントが実行されたかのように監視されることも保証する必要があります。いくつかの行まで順番に。

tryブロックでは例外がスローされる可能性があるため(tryブロックの任意の行で!stopスレッド(非推奨)を呼び出すなど、一部の例外は非同期でスローされ、さらに、OutOfMemoryErrorはほとんどどこでも発生する可能性があります)キャッチされ、その後同じ方法でコードが引き続き実行される場合、実行可能な最適化について推論することがより困難になるため、それらが発生する可能性は低くなります。(誰かがコンパイラーをプログラムして、正しいことを推論し、保証する必要があります。それは、「例外的」であることを意図したものには大きな苦痛になるでしょう)しかし、実際には、このようなことに気付くことはありません。


2
一部の例外は非同期スローされますが非同期ではなく安全なポイントでスローされます。そしてこの部分の試みはそれに関連するいくつかのマイナーなコストを持っています。Javaはtryブロックのコードに対していくつかの最適化を行うことができませんそうしないと、深刻な参照が必要になります。ある時点で、コードはtry / catchブロック内にある可能性が非常に高くなります。try / catchブロックがインライン化され、結果に対して適切なラティスを構築するのが困難になることは事実かもしれませんが、再配置されたパーツがあいまいです。
bestss 2013年

2
try...finallyのないブロックはcatchまた、いくつかの最適化を防ぎますか?
dajood 2015

5
@Patashu 「実際にコストがかかるのは例外をスローすることです」技術的には、例外をスローすることは高価ではありません。Exceptionオブジェクトのインスタンス化は、ほとんどの時間を要します。
オースティンD

72

測定してみましょうか。

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ステートメントはパフォーマンスに測定可能な影響を与えませんでした。より複雑なものを測定してください。

一般的に言えば、コードの実際のパフォーマンスの問題の証拠が見つかるまで、言語構造のパフォーマンスコストについて心配しないことをお勧めします。あるいは、ドナルド・クヌース言うように:「時期尚早な最適化はすべての悪の根源です」。


4
try / no tryはほとんどのJVMで同じである可能性が非常に高いですが、マイクロベンチマークにはひどく欠陥があります。
bestss 2013年

2
かなりの数のレベル:結果は1ns未満で計算されるということですか?コンパイルされたコードは、try / catchとループの両方を完全に削除します(1からnまでの数の合計は、単純な算術進行の合計です)。コードにtry / finallyが含まれている場合でも、コンパイラーはそれを証明できますが、そこには何もスローされません。抽象コードには2つの呼び出しサイトしかなく、それは複製されてインライン化されます。さらに多くのケースがあります。マイクロベンチマークに関するいくつかの記事を参照しください。マイクロベンチマークを作成すると、生成されたアセンブリを常にチェックします。
bestss 2013年

3
報告される時間は、ループの反復ごとです。合計経過時間が0.1秒(または20億回の繰り返し)の場合にのみ測定が使用されるため(ここではそうではありません)、ループが完全に削除されたというあなたの主張は信じられません-なぜならループが削除されました。実行に0.1秒かかったものは何ですか。
メリトン2013年

...そして実際、によれば-XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly、ループとその中の追加の両方が、生成されたネイティブコードに存在します。いいえ、抽象メソッドはインライン化されていません。これは、呼び出し元がコンパイルされたばかりではないためです(おそらく、十分に呼び出されていないためです)。
メリトン2013年

どのように私はJavaで正しいマイクロベンチマークを書くのです:stackoverflow.com/questions/504103/...
ヴァジム・

46

try/ catchはパフォーマンスに影響を与える可能性があります。これは、JVMが一部の最適化を実行できないためです。Joshua Blochは、「Effective Java」で次のように述べています。

•try-catchブロック内にコードを配置すると、最新のJVM実装が実行する可能性がある特定の最適化が禁止されます。


24
「JVMが一部の最適化を実行できないようにする」...?詳しく説明してもらえますか?
クラーケン2013年

5
一例として、tryブロック内のKrakenコード(通常?常に?)をtryブロック外のコードで並べ替えることはできません。
Patashu 2013年

3
問題は「コストがかかる」かどうかであり、「パフォーマンスに影響を与える」かどうかではありません。
mikołak

3
もちろん、Effective Javaからの抜粋を追加しました。もちろん、これはjavaの聖書です。参照がない限り、抜粋は何も伝えません。実際には、Javaのすべてのコードは、あるレベルでtry / finally内にあります。
bestss 2013年

29

うん、他の人が言ったように、tryブロックは全体のいくつかの最適化を阻害します{}それを取り巻くキャラクターします。特に、オプティマイザは、ブロック内の任意のポイントで例外が発生する可能性があると想定する必要があるため、ステートメントが実行される保証はありません。

例えば:

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

なしではtry、割り当てに計算された値をx「共通の副次式」として保存し、再利用してに割り当てることができyます。しかし、try、最初の式が評価されたという保証がないため、式を再計算する必要があります。これは通常、「直線的な」コードでは大したことではありませんが、ループでは重要な場合があります。

ただし、これはJITCされたコードにのみ適用されることに注意してください。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」はありません。


ブロック境界をマークするために生成されるバイトコードはありませんが、これは必ずしもそうである必要はありません。GOTOがブロックを離れる必要があります。そうでない場合、catch/finallyフレームに分類されます。
bestss 2013年

@bestsss-GOTOが生成されても(これは指定されていません)、そのコストはごくわずかであり、ブロック境界の「マーカー」とはほど遠い-GOTOは多くの構成で生成できます。
ホットリックス

コストについて言及したことはありませんが、偽のステートメントであるバイトコードは生成されません。それで全部です。実際には、バイトコードにブロックはなく、フレームはブロックと同じではありません。
bestss 2013年

試行がファイナルに直接当てはまる場合、GOTOはありません。また、GOTOがないシナリオが他にもあります。ポイントは、「エンタートライ」/「エグジットトライ」のバイトコードの順序には何もないということです。
ホットリックス

試行がファイナルに直接当てはまる場合、GOTOはありません -False!finallyバイトコードにはありません。try/catch(Throwable any){...; throw any;}そして、それは定義されている必要があります(非null)などのフレームとスロー可能なw / catchステートメントがあります。なぜあなたはトピックについて議論しようとしますか?少なくともいくつかのバイトコードをチェックできますか?実装の現在のガイドライン。最終的には、ブロックをコピーしてgotoセクション(以前の実装)を回避していますが、バイトコードは、存在する出口点の数に応じてコピーする必要があります。
bestss 2013年

8

最適化を実行できない理由を理解するには、基になるメカニズムを理解することが役立ちます。私が見つけた最も簡潔な例は、次の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にローカライズできるかどうかを判断するのが難しいため、安全性を保証できない最適化をスキップしますが、実装自体はかなり軽量です。


4
try / catchで見つけたこれらのCマクロは、JavaやC#実装とは異なり、実行時の命令を0で発行します。
Patashu 2013年

Javaの実装は広すぎて完全に含めることはできません。これは、例外を実装する方法の基本的な考え方を理解するための簡略化された実装です。それが0のランタイム命令を発行すると言うことは誤解を招くものです。たとえば、単純なClassCastExceptionが含まれているスロー可能な拡張例外拡張のRuntimeException拡張grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/...を Cでスイッチケースを言うようなのがあることを... 1つのケースしか使用されていない場合は無料ですが、起動時のオーバーヘッドはわずかです。
テクノサウルス2013年

1
@Patashuこれらのプリコンパイル済みビットはすべて、使用されるかどうかにかかわらず、起動時にロードする必要があります。コンパイル時に実行時にメモリ不足の例外が発生するかどうかを知る方法はありません-それがランタイム例外と呼ばれる理由です-そうでなければそれらはコンパイラの警告/エラーになるため、すべてを最適化するわけではありません、それらを処理するためのすべてのコードはコンパイル済みコードに含まれており、起動コストがかかります。
テクノサウルス2013年

2
Cについて話すことはできません。C#とJavaでは、tryではコードではなくメタデータを追加することで実装されています。tryブロックに入ると、これを示すものは何も実行されません。例外がスローされると、スタックが巻き戻され、メタデータでその例外タイプのハンドラーがチェックされます(高価)。
Patashu 2013年

1
はい、私は実際にJavaインタープリターと静的バイトコードコンパイラーを実装し、後続のJITC(IBM iSeriesの場合)で作業しました。範囲は別の表で識別されます。インタプリタは、(例外が発生するまで)試行範囲に対して特別なことは何もしません。JITC(または静的バイトコードコンパイラ)は、前述のように最適化を抑制するために境界を認識する必要があります。
ホットリックス2013年

8

さらに別のマイクロベンチマーク(ソース)。

例外の割合に基づいて、try-catchとno-try-catchのコードバージョンを測定するテストを作成しました。10%のパーセンテージは、テストケースの10%がゼロケースで除算されたことを意味します。1つの状況では、try-catchブロックによって処理され、もう1つの状況では、条件演算子によって処理されます。これが私の結果表です:

OS: Windows 8 6.2 x64
JVM: Oracle Corporation Java HotSpot(TM) 64-Bit Server VM 23.25-b01
パーセンテージ| 結果(try / if、ns)   
    0%| 88/90   
    1%| 89/87    
    10%| 86/97    
    90%| 85/83   

つまり、これらのケースのいずれにも大きな違いはないということです。


3

NullPointExceptionのキャッチは非常に高価であることがわかりました。1.2kオペレーションの場合、200msと12msでしたが、同じ方法で操作すると、if(object==null)かなり改善されました。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.