15

次のコードを使用して、try ブロックの速度をテストしています。驚いたことに、try ブロックを使用すると高速になります。なんで?

public class Test {
    int value;

    public int getValue() {
        return value;
    }

    public void reset() {
        value = 0;
    }

    // Calculates without exception
    public void method1(int i) {
        value = ((value + i) / i) << 1;
        // Will never be true
        if ((i & 0xFFFFFFF) == 1000000000) {
            System.out.println("You'll never see this!");
        }
    }

    public static void main(String[] args) {
        int i;
        long l;
        Test t = new Test();

        l = System.currentTimeMillis();
        t.reset();
        for (i = 1; i < 100000000; i++) {
            t.method1(i);
        }
        l = System.currentTimeMillis() - l;
        System.out.println("method1 took " + l + " ms, result was "
                + t.getValue());

        // using a try block
        l = System.currentTimeMillis();
        t.reset();
        for (i = 1; i < 100000000; i++) {
            try {
                t.method1(i);
            } catch (Exception e) {

            }
        }

        l = System.currentTimeMillis() - l;
        System.out.println("method1 with try block took " + l + " ms, result was "
                + t.getValue());
    }
}

私のマシンは 64 ビットの Windows 7 と 64 ビットの JDK7 を実行しています。次の結果が得られました。

method1 took 914 ms, result was 2
method1 with try block took 789 ms, result was 2

そして、コードを何度も実行しましたが、毎回ほぼ同じ結果が得られました。

アップデート:

これは、MacBook Pro、Java 6 でテストを 10 回実行した結果です。Try-catch を使用すると、Windows と同じようにメソッドが高速になります。

method1 took 895 ms, result was 2
method1 with try block took 783 ms, result was 2
--------------------------------------------------
method1 took 943 ms, result was 2
method1 with try block took 803 ms, result was 2
--------------------------------------------------
method1 took 867 ms, result was 2
method1 with try block took 745 ms, result was 2
--------------------------------------------------
method1 took 856 ms, result was 2
method1 with try block took 744 ms, result was 2
--------------------------------------------------
method1 took 862 ms, result was 2
method1 with try block took 744 ms, result was 2
--------------------------------------------------
method1 took 859 ms, result was 2
method1 with try block took 765 ms, result was 2
--------------------------------------------------
method1 took 937 ms, result was 2
method1 with try block took 767 ms, result was 2
--------------------------------------------------
method1 took 861 ms, result was 2
method1 with try block took 744 ms, result was 2
--------------------------------------------------
method1 took 858 ms, result was 2
method1 with try block took 744 ms, result was 2
--------------------------------------------------
method1 took 858 ms, result was 2
method1 with try block took 749 ms, result was 2
4

4 に答える 4

19

同じメソッドに複数の実行時間の長いループがある場合、メソッド全体の最適化がトリガーされ、2 番目のループで予測できない結果が生じる可能性があります。これを回避する 1 つの方法は次のとおりです。

  • 各ループに独自のメソッドを与える
  • テストを複数回実行して、結果が再現可能であることを確認します
  • テストを 2 ~ 10 秒間実行します。

多少の変動が見られ、結果が決定的でない場合もあります。つまり、変動は差よりも大きくなります。

public class Test {
    int value;

    public int getValue() {
        return value;
    }

    public void reset() {
        value = 0;
    }

    // Calculates without exception
    public void method1(int i) {
        value = ((value + i) / i) << 1;
        // Will never be true
        if ((i & 0xFFFFFFF) == 1000000000) {
            System.out.println("You'll never see this!");
        }
    }

    public static void main(String[] args) {
        Test t = new Test();
        for (int i = 0; i < 5; i++) {
            testWithTryCatch(t);
            testWithoutTryCatch(t);
        }
    }

    private static void testWithoutTryCatch(Test t) {
        t.reset();
        long l = System.currentTimeMillis();
        for (int j = 0; j < 10; j++)
            for (int i = 1; i <= 100000000; i++)
                t.method1(i);

        l = System.currentTimeMillis() - l;
        System.out.println("without try/catch method1 took " + l + " ms, result was " + t.getValue());
    }

    private static void testWithTryCatch(Test t) {
        t.reset();
        long l = System.currentTimeMillis();
        for (int j = 0; j < 10; j++)
            for (int i = 1; i <= 100000000; i++)
                try {
                    t.method1(i);
                } catch (Exception ignored) {
                }

        l = System.currentTimeMillis() - l;
        System.out.println("with try/catch method1 took " + l + " ms, result was " + t.getValue());
    }
}

版画

with try/catch method1 took 9723 ms, result was 2
without try/catch method1 took 9456 ms, result was 2
with try/catch method1 took 9672 ms, result was 2
without try/catch method1 took 9476 ms, result was 2
with try/catch method1 took 8375 ms, result was 2
without try/catch method1 took 8233 ms, result was 2
with try/catch method1 took 8337 ms, result was 2
without try/catch method1 took 8227 ms, result was 2
with try/catch method1 took 8163 ms, result was 2
without try/catch method1 took 8565 ms, result was 2

これらの結果から、try/catch を使用するとわずかに遅くなるように見えるかもしれませんが、常にそうとは限りません。

Java 7 update 7 を適用した Windows 7、Xeon E5450 で実行します。

于 2012-10-12T07:39:58.857 に答える
5

Caliper Microbenchmark で試してみましたが、違いはわかりませんでした。

コードは次のとおりです。

public class TryCatchBenchmark extends SimpleBenchmark {

    private int value;

    public void setUp() {
        value = 0;
    }

    // Calculates without exception
    public void method1(int i) {
        value = ((value + i) / i) << 1;
        // Will never be true
        if ((i & 0xFFFFFFF) == 1000000000) {
            System.out.println("You'll never see this!");
        }
    }

    public void timeWithoutTryCatch(int reps) {
        for (int i = 1; i < reps; i++) {
            this.method1(i);
        }
    }

    public void timeWithTryCatch(int reps) {
        for (int i = 1; i < reps; i++) {
            try {
                this.method1(i);
            } catch (Exception ignore) {
            }
        }
    }

    public static void main(String[] args) {
        new Runner().run(TryCatchBenchmark.class.getName());
    }
}

結果は次のとおりです。

0% シナリオ{vm=java、トライアル=0、ベンチマーク=WithoutTryCatch} 8,23 ns; σ=0.03 ns @ 3 回の試行
50% シナリオ{vm=java、トライアル=0、ベンチマーク=WithTryCatch} 8,13 ns; σ=0.03 ns @ 3 回の試行

      ベンチマーク ns 線形ランタイム
WithoutTryCatch 8,23 ==============================
   WithTryCatch 8,13 =============================

関数の順序を入れ替えると (逆の順序で実行されるようにするため)、結果は次のようになります。

0% シナリオ{vm=java、トライアル=0、ベンチマーク=WithTryCatch} 8,21 ns; σ=0.05 ns @ 3 回の試行
50% シナリオ{vm=java、トライアル=0、ベンチマーク=WithoutTryCatch} 8,14 ns; σ=0.03 ns @ 3 回の試行

      ベンチマーク ns 線形ランタイム
   WithTryCatch 8,21 ==============================
WithoutTryCatch 8,14 =============================

基本的には同じだと言えます。

于 2012-10-12T08:49:59.627 に答える
2

私はいくつかの実験をしました。

まず、OPの発見を完全に確認します。最初のループを削除したり、例外をまったく無関係なものに変更したりしても、例外を再スローして分岐を追加しない限り、try キャッチはコードを高速化します。実際に例外をキャッチする必要がある場合 (たとえば、ループを 1 ではなく 0 から開始する場合)、コードはさらに高速になります。

私の「説明」は、JIT は野生の最適化マシンであり、JIT レベルでの非常に具体的な研究なしでは一般的に理解できない方法で、他の時間よりも優れたパフォーマンスを発揮する場合があるということです。変更できる可能性のあるものはたくさんあります (レジスターの使用など)。

これは、C# JIT の非常によく似たケースで世界的に発見されたものです。

いずれにせよ、Java は try-catch 用に最適化されています。例外が発生する可能性は常にあるため、try-catch を追加することによって実際に多くの分岐を追加することはないため、2 番目のループが最初のループよりも長くないことは驚くことではありません。

于 2012-10-12T06:33:59.360 に答える
1

To avoid any hidden optimization or cache that could be performed by the JVM and the OS, I first developed two seed java programs, TryBlock and NoTryBlock, where their difference is use a try block or not. These two seed programs will be used to generate different programs to disallow JVM or OS to do hidden optimization. In each test, a new java program will be generated and compiled, and I repeated the test 10 times.

Based on my experiment, running without try block takes 9779.3 ms in average while running with try block takes 9775.9ms: a 3.4ms (or 0.035%) difference in their average running time, which can be viewed as noise. This indicates that using a void try block (by void, I mean other than null-pointer exception there exists no possible exceptions) or not seems not have impact on running time.

The test is running on same linux machine (cpu 2392MHz) and under java version "1.6.0_24".

Below is my script for generating testing programs based on the seed programs:

for i in `seq 1 10`; do
  echo "NoTryBlock$i"
  cp NoTryBlock.java NoTryBlock$i.java
  find . -name "NoTryBlock$i.java" -print | xargs sed -i "s/NoTryBlock/NoTryBlock$i/g";
  javac NoTryBlock$i.java;
  java NoTryBlock$i
  rm NoTryBlock$i.* -f;
done

for i in `seq 1 10`; do
  echo "TryBlock$i"
  cp TryBlock.java TryBlock$i.java
  find . -name "TryBlock$i.java" -print | xargs sed -i "s/TryBlock/TryBlock$i/g";
  javac TryBlock$i.java;
  java TryBlock$i
  rm TryBlock$i.* -f;
done

Here are the seed programs, first is the NoTryBlock.java

import java.util.*;
import java.lang.*;

public class NoTryBlock {
    int value;

    public int getValue() {
        return value;
    }

    public void reset() {
        value = 0;
    }

    // Calculates without exception
    public void method1(int i) {
        value = ((value + i) / i) << 1;
        // Will never be true
        if ((i & 0xFFFFFFF) == 1000000000) {
            System.out.println("You'll never see this!");
        }
    }

    public static void main(String[] args) {
        int i, j;
        long l;
        NoTryBlock t = new NoTryBlock();

        // using a try block
        l = System.currentTimeMillis();
        t.reset();
        for (j = 1; j < 10; ++j) {
          for (i = 1; i < 100000000; i++) {
              t.method1(i);
          }
        }
        l = System.currentTimeMillis() - l;
        System.out.println(
            "method1 with try block took " + l + " ms, result was "
                + t.getValue());
    }
}

the second is the TryBlock.java, which uses a try-block on the method function call:

import java.util.*;
import java.lang.*;

public class TryBlock {
    int value;

    public int getValue() {
        return value;
    }

    public void reset() {
        value = 0;
    }

    // Calculates without exception
    public void method1(int i) {
        value = ((value + i) / i) << 1;
        // Will never be true
        if ((i & 0xFFFFFFF) == 1000000000) {
            System.out.println("You'll never see this!");
        }
    }

    public static void main(String[] args) {
        int i, j;
        long l;
        TryBlock t = new TryBlock();

        // using a try block
        l = System.currentTimeMillis();
        t.reset();
        for (j = 1; j < 10; ++j) {
          for (i = 1; i < 100000000; i++) {
            try {
              t.method1(i);
            } catch (Exception e) {
            }
          }
        }
        l = System.currentTimeMillis() - l;
        System.out.println(
            "method1 with try block took " + l + " ms, result was "
                + t.getValue());
    }
}

Below is the diff of my two seed programs, and you can see except the class name, the try block is their only difference:

$ diff TryBlock.java NoTryBlock.java
4c4
<     public class TryBlock {
---
>     public class NoTryBlock {
27c27
<             TryBlock t = new TryBlock();
---
>             NoTryBlock t = new NoTryBlock();
34d33
<                 try {
36,37d34
<                 } catch (Exception e) {
<                 }
42c39
<                 "method1 with try block took " + l + " ms, result was "
---
>                 "method1 without try block took " + l + " ms, result was "

Below is the output:

method1 without try block took,9732,ms, result was 2
method1 without try block took,9756,ms, result was 2
method1 without try block took,9845,ms, result was 2
method1 without try block took,9794,ms, result was 2
method1 without try block took,9758,ms, result was 2
method1 without try block took,9733,ms, result was 2
method1 without try block took,9763,ms, result was 2
method1 without try block took,9893,ms, result was 2
method1 without try block took,9761,ms, result was 2
method1 without try block took,9758,ms, result was 2

method1 with try block took,9776,ms, result was 2
method1 with try block took,9751,ms, result was 2
method1 with try block took,9767,ms, result was 2
method1 with try block took,9726,ms, result was 2
method1 with try block took,9779,ms, result was 2
method1 with try block took,9797,ms, result was 2
method1 with try block took,9845,ms, result was 2
method1 with try block took,9784,ms, result was 2
method1 with try block took,9787,ms, result was 2
method1 with try block took,9747,ms, result was 2
于 2013-06-12T03:31:45.057 に答える