例外をキャッチするのにコストがかかることはわかっています。しかし、たとえ例外がスローされない場合でも、Java で try-catch ブロックを使用するのはコストがかかるのでしょうか?
7 に答える
try
費用はほとんどかかりません。実行時にを設定する作業を行う代わりにtry
、コードのメタデータはコンパイル時に構造化され、例外がスローされたときに、スタックを調べて、try
これをキャッチするブロックが存在するかどうかを確認するという比較的コストのかかる操作を実行します。例外。素人の観点からは、try
無料かもしれません。実際にはコストがかかる例外をスローしていますが、数百または数千の例外をスローしない限り、コストに気付くことはありません。
try
それに関連するいくつかの小さなコストがあります。try
Java は、ブロック内のコードに対して、他の方法では行うであろう一部の最適化を行うことができません。たとえば、Java はメソッド内の命令を再配置して高速に実行することがよくありますが、例外がスローされた場合、メソッドの実行が、ソース コードに記述されているステートメントが実行されたかのように監視されることも保証する必要があります。いくつかの行まで順番に。
ブロック内で例外がスローされる可能性があるためtry
(try ブロックの任意の行で! 一部の例外は、Thread (非推奨) の呼び出しなどによって非同期的にスローされます。stop
さらに、OutOfMemoryError はほとんどどこでも発生する可能性があります)。キャッチされ、コードが同じメソッドで引き続き実行される可能性がある場合、実行できる最適化について推論するのが難しくなるため、最適化が行われる可能性が低くなります。(誰かがコンパイラをプログラムして、それらを実行し、正当性について推論し、保証する必要があります。「例外的」であることを意味するものにとっては、大きな苦痛になります)しかし、繰り返しますが、実際には、このようなことに気付くことはありません。
測ってみましょうか。
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 ステートメントはパフォーマンスに測定可能な影響を与えませんでした。より複雑なものを自由に測定してください。
一般的に言えば、コードに実際のパフォーマンスの問題があるという証拠が得られるまでは、言語構造のパフォーマンス コストについて心配しないことをお勧めします。または、Donald Knuthが述べたように、「時期尚早の最適化は諸悪の根源です」。
try
/catch
パフォーマンスに影響を与える可能性があります。これは、JVM が一部の最適化を実行できないためです。Joshua Bloch は、「Effective Java」で次のように述べています。
• try-catch ブロック内にコードを配置すると、最新の JVM 実装で実行される特定の最適化が阻害されます。
ええ、他の人が言ったように、ブロックはそれを取り巻くキャラクター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
、最初の式が評価されたという保証がないため、式を再計算する必要があります。これは通常、「直線的な」コードでは大したことではありませんが、ループでは重要になる可能性があります。
ただし、これは JITCed コードにのみ適用されることに注意してください。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」はありません。
さらに別のマイクロベンチマーク ( source )。
例外の割合に基づいて、try-catch および no-try-catch コード バージョンを測定するテストを作成しました。10% のパーセンテージは、テスト ケースの 10% が 0 で除算されたことを意味します。ある状況では try-catch ブロックによって処理され、別の状況では条件演算子によって処理されます。ここに私の結果表があります:
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
つまり、これらのケースのいずれにも大きな違いはありません。
最適化を実行できない理由を理解するには、基礎となるメカニズムを理解することが役立ちます。私が見つけた最も簡潔な例は、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 にローカライズできるかどうかを判断するのが難しいため、安全であると保証できない最適化をスキップしますが、実装自体はかなり軽量です。