可能な重複:
Javaの例外はどれくらい遅いですか?
次の2つのプログラムは、実行にほぼ同じ時間がかかります。
public class Break {
public static void main(String[] argv){
long r = 0, s = 0, t = 0;
for(long x = 10000000; x > 0; x--){
long y = x;
while(y != 1){
if(y == 0)
throw new AssertionError();
try2: {
try1: {
for(;;){
r++;
if(y%2 == 0)
break try1;
y = y*3 + 1;
}
}/*catch(Thr _1)*/{
for(;;){
s++;
if(y%2 == 1)
break try2;
y = y/2;
}
}
}/*catch(Thr _2)*/{
t++;
}
}
}
System.out.println(r + ", " + s + ", " + t);
}
}
public class Try {
private static class Thr extends Throwable {}
private static final Thr thrown = new Thr();
public static void main(String[] argv){
long r = 0, s = 0, t = 0;
for(long x = 10000000; x > 0; x--){
long y = x;
while(y != 1){
try{
if(y == 0)
throw new AssertionError();
try{
for(;;){
r++;
if(y%2 == 0)
throw thrown;
y = y*3 + 1;
}
}catch(Thr _1){
for(;;){
s++;
if(y%2 == 1)
throw thrown;
y = y/2;
}
}
}catch(Thr _2){
t++;
}
}
}
System.out.println(r + ", " + s + ", " + t);
}
}
$ for x in Break Try; do echo $x; time java $x; done Break 1035892632, 1557724831, 520446316 real 0m10.733s user 0m10.719s sys 0m0.016s Try 1035892632, 1557724831, 520446316 real 0m11.218s user 0m11.204s sys 0m0.017s
しかし、次の2つのプログラムにかかる時間は比較的異なります。
public class Return {
private static int tc = 0;
public static long find(long value, long target, int depth){
if(depth > 100)
return -1;
if(value%100 == target%100){
tc++;
return depth;
}
long r = find(target, value*29 + 4221673238171300827l, depth + 1);
return r != -1? r : find(target, value*27 + 4494772161415826936l, depth + 1);
}
public static void main(String[] argv){
long s = 0;
for(int x = 0; x < 1000000; x++){
long r = find(0, x, 0);
if(r != -1)
s += r;
}
System.out.println(s + ", " + tc);
}
}
public class Throw {
public static class Found extends Throwable {
// note the static!
public static int value = 0;
}
private static final Found found = new Found();
private static int tc = 0;
public static void find(long value, long target, int depth) throws Found {
if(depth > 100)
return;
if(value%100 == target%100){
Found.value = depth;
tc++;
throw found;
}
find(target, value*29 + 4221673238171300827l, depth + 1);
find(target, value*27 + 4494772161415826936l, depth + 1);
}
public static void main(String[] argv){
long s = 0;
for(int x = 0; x < 1000000; x++)
try{
find(0, x, 0);
}catch(Found _){
s += found.value;
}
System.out.println(s + ", " + tc);
}
}
$ for x in Return Throw; do echo $x; time java $x; done Return 84227391, 1000000 real 0m2.437s user 0m2.429s sys 0m0.017s Throw 84227391, 1000000 real 0m9.251s user 0m9.215s sys 0m0.014s
単純なtry / throw / catchメカニズムは、少なくとも部分的に末尾の呼び出しに最適化された戻り値のように見えます(したがって、制御が(最も近いcatch)に戻る場所は直接わかっています)。もちろん、JRE実装は多くの最適化を行います。
後者に大きな違いがあるが前者には違いがあるのはなぜですか?それは、制御フロー分析が前の2つのプログラムをほぼ同じであると判断し、実際のtry / throw / catchesが特に遅いからであるか、それともReturnのためです。find
Throwのメソッド呼び出しはできないが、メソッド呼び出しを回避するようなレベルに展開されます。
ありがとう。
編集:この質問は私には異なるようですJavaの例外はどれくらい遅いですか?なぜこれと似たようなケースでこんなに大きな違いがあるのか尋ねないからです。例外オブジェクトの作成に時間が費やされているという事実も無視されます。fillInStackTrace
オーバーライドされます - スタックをトラバースしてそれのために配列を作成するようなものを含みます)。しかし、それは私の質問の一部には明らかに答えます。「制御フロー分析が前の2つのプログラムをほぼ同じと決定するからでしょうか」 - スタックトレースを行う答えで言及されたのは奇妙に思えますこれは、スタックが見られないことを判断するための複雑な分析をしない限り、実際のthrow / catch操作を邪魔することになると思われます(@ Stephenの答えは奇妙になるでしょう)。
あなたのベンチマークはJVMウォームアップ効果を考慮に入れていません。そのため、実際のプログラムでtry / break / returnがどのように実行されるのかが、実際に見ている結果から明らかになっていることには、かなりの疑問があります。
(メソッド内の各時間テストを宣言し、メソッドを複数回呼び出す必要があります。その後、最初の数回の呼び出しからの出力を破棄するか、数値が安定するまで... JITの一時的なコストを排除します。数字からのコンパイル、クラスのロードなど)
何が起こっているのかを本当に知りたいのであれば、JITコンパイラが各ケースに対して生成するネイティブコードをダンプするようにする必要があります。
私はあなたが最初のケースでJITコンパイラがthrow / catchを回していることに気付くだろうと思いますメソッド内単純な分岐命令に。 2番目のケースでは、JITコンパイラがより複雑なコードを生成している可能性があります。おそらくこれがブランチと同等であると認識していないためです。
なぜ違いがありますか?まあ、JITオプティマイザによって試みられる各最適化のための費用対効果のトレードオフがあります。 JITコンパイラーによってサポートされるそれぞれの新しい最適化には、実装と保守のコストがあります。そして実行時に、コンパイラは最適化のための前提条件が満たされているかどうか見るためにそれがコンパイルしているコードを調べる必要があります。そうであれば最適化を実行することができます。そうでなければ、JITコンパイラーは時間を浪費しています。
これらの例では、(ある場合には)同じメソッド内でスローされてキャッチされ、(もう1つの場合には)メソッド呼び出しと戻り値の境界を越えて伝播するという例外があります。前者の例では、最適化と(推定される)最適化されたコードシーケンスのための十分な前提条件は、どちらもかなり簡単です。後者の例では、オプティマイザは、例外をスローしてキャッチするメソッドが異なるコンパイル単位にある(したがって、再ロードされる可能性がある)可能性、オーバーライドする可能性などに対処する必要があります。さらに、生成コードシーケンスはかなり複雑になります。つまり、標準でない呼び出しからの戻りシーケンスとそれに続く分岐です。
だから私の理論は、JITコンパイラの作成者は、より複雑な最適化では効果があるとは考えていなかったということです。ほとんどの人にそのようなJavaコードを書かないでください。彼らはおそらく正しいです。
find
ReturnとThrowで同じ回数と呼ばれますか? - assylias-1
中にReturn
のようなものですThrow
- この結果が直接に引き上げられていることはかなりわかりやすいでしょう。main
メソッドの最後の2行(他の唯一の呼び出し)を見てください。 - sigynsurfeitBreak
対Try
)以下の答えでStephenが指摘するように、JVM開発者がそれを行うことのコストがそれを行うことの利益を上回るかもしれないと決心したので、それはそれらを使うコードを最適化することの欠如によるのかもしれません。 - sigynsurfeitthrow
通常、誰かが"例外"を発生させたときに行われます。両方のテストで、私は単に同じものを投げますThrowable
オブジェクト、一部は一種のシグナルとして。 - sigynsurfeit