Wednesday, January 16, 2008

Java 例外のコスト

気になったので次のようなコードで計測してみた。JDK のバージョンは 1.5 。


public static void main(String a[]) throws Exception {
int c = 0;
System.out.println(new Date());
for (int i = 0; i < 1000000000; i++) {
c = test(0);
}
System.out.println(new Date());
System.out.println(c);
}

static int test(int a) {
int c = 0;
if (a == 0) {
String s = new String("13");
return Integer.parseInt(s);
} else {
return c + 1;
}

}

ここでは、軽い仕事をして普通にリターンしている。


public static void main(String a[]) throws Exception {
int c = 0;
System.out.println(new Date());
for (int i = 0; i < 1000000000; i++) {
try {
c = test(0);
} catch (Exception e) {
c = i;
}

}
System.out.println(new Date());
System.out.println(c);
}

static int test(int a) throws Exception {
int c = 0;
if (a == 0) {
throw new Exception();
} else {
return c + 1;
}

}

ここでは、何もせずに例外をスローすることでリターンしているのだが、前のコードの 16 倍 の時間がかかった。Exception をその都度生成するのではなく、static に確保しておいて、それをスローするだけにすると時間はほぼ同じだった。

結局、例外の throw/catch そのものは大したことないが、例外オブジェクトの生成に相当のコストがかかることが分かる。この理由は、例外オブジェクトの生成時にスタックトレースのスナップショットをとることにある。これが高くつくというわけだ。(パフォーマンスへの目: 開発プロセスを改善する 中の例外のコスト)

したがって、パフォーマンスの面からだけでも、例外は例外状態のみに使用すべきであり、正常フローには使っていけないということになる。そして、正常フローでの使用を避けるために状態チェックメソッドや、null 等の区別できる値を返したりする。

ところで、例外がサポートされていない言語では、関数内でエラーが起こったときにそのエラーの種類を複数のエラーコードで返すようになっていることがよくある。

この場合、例外が使えるならば、回復可能と考えられるエラーにはチェックされる例外、そうでないエラーには実行時例外を対応させて例外クラスを定義し、エラーが起こったらそれをスローすることになるだろう。そうすれば、エラーコードと違って、呼び出し側に対応を強制することができ、コードの信頼性・保守性が向上する。これはまさに正しい例外の使い方といえる。

ただ、このようなメソッドでは例外状態であるにも関わらず、それが頻繁に発生するものがある。例外の生成には非常にコストがかかるので、これは困ったことであり、コードの信頼性・保守性とパフォーマンスとが両立しない状態になってしまっている。

このとき、例外を使わずにすまそうとするなら、状態チェックメソッドの導入が考えられるが、エラーが複数ある場合にはそれぞれに状態チェックメソッドが必要となり、メソッドが使いにくくなるし、成功か失敗かを判定するのにデータベースへのアクセスが必要だったりすると逆にパフォーマンスが悪くなってしまったりする。

これの解決案としては、例外のコストは例外オブジェクトの生成が大部分であり、throw/catch にはほとんどかからなかったことから、例外オブジェクトをあらかじめ static で作成しておき、それを使いまわすということが考えられる。

ただ、これをやると、スタックトレースの取得が不可能になるのと、(マルチスレッドで使用されるときには)エラー発生時の状況を例外オブジェクトに組み込むことができないという代償はある。

No comments: