3

このチケットに答えようとしています:instanceofとClass.isAssignableFrom(...)の違いは何ですか?

パフォーマンステストを行いました:

class A{}
class B extends A{}

A b = new B();

void execute(){
  boolean test = A.class.isAssignableFrom(b.getClass());
  // boolean test = A.class.isInstance(b);
  // boolean test = b instanceof A;
}

@Test
public void testPerf() {
  // Warmup the code
  for (int i = 0; i < 100; ++i)
    execute();

  // Time it
  int count = 100000;
  final long start = System.nanoTime();
  for(int i=0; i<count; i++){
     execute();
  }
  final long elapsed = System.nanoTime() - start;
System.out.println(count+" iterations took " + TimeUnit.NANOSECONDS.toMillis(elapsed) + "ms.);
}

それは私に与えました:

  • A.class.isAssignableFrom(b.getClass()) 100000回の反復に15ミリ秒かかりました
  • A.class.isInstance (b):100000回の反復に12ミリ秒かかりました
  • b instanceof A:100000回の反復に6ミリ秒かかりました

しかし、反復回数を試してみると、パフォーマンスは一定であることがわかります。Integer.MAX_VALUEの場合:

  • A.class.isAssignableFrom(b.getClass()) 2147483647の反復には15ミリ秒かかりました
  • A.class.isInstance (b):2147483647の反復には12ミリ秒かかりました
  • b instanceof A:2147483647の反復には6ミリ秒かかりました

それがコンパイラの最適化だと思って(私はこのテストをJUnitで実行しました)、私はそれを次のように変更しました:

@Test
public void testPerf() {
    boolean test = false;

    // Warmup the code
    for (int i = 0; i < 100; ++i)
        test |= b instanceof A;

    // Time it
    int count = Integer.MAX_VALUE;
    final long start = System.nanoTime();
    for(int i=0; i<count; i++){
        test |= b instanceof A;
    }
    final long elapsed = System.nanoTime() - start;
    System.out.println(count+" iterations took " + TimeUnit.NANOSECONDS.toMillis(elapsed) + "ms. AVG= " + TimeUnit.NANOSECONDS.toMillis(elapsed/count));

    System.out.println(test);
}

ただし、パフォーマンスは反復回数に「依存しません」。誰かがその行動を説明できますか?

4

3 に答える 3

5
  1. 100 回の繰り返しでは、ウォームアップには十分ではありません。デフォルトのコンパイルしきい値は 10000 反復 (100 倍以上) であるため、少なくともそのしきい値を少し超えることが最善です。
  2. コンパイルがトリガーされると、世界は停止しません。コンパイルはバックグラウンドで行われます。つまり、その効果は少し遅れて初めて観測可能になるということです。
  3. ループ全体が最終結果に折りたたまれるように、テストを最適化するための十分なスペースがあります。それは定数を説明するでしょう。

とにかく、私は常に、外側のメソッドが内側のメソッドを 10 回程度呼び出すようにして、ベンチマークを実行します。内部メソッドは、実行時間を少なくとも数十ミリ秒にするために、必要に応じて、たとえば 10,000 回以上の多数の反復を行います。nanoTimeマイクロ秒の精度が重要な場合、それは時間間隔が短すぎることの兆候にすぎないため、気にしません。

このようにすると、解釈されたバージョンに置き換えられた後、JIT がコンパイルされたバージョンの内部メソッドを実行しやすくなります。もう 1 つの利点は、内部メソッドの時間が安定していることを確認できることです。

于 2012-08-24T09:40:02.757 に答える
3

単純な関数の実際のベンチマークを作成する場合は、Caliperなどのマイクロ ベンチマーク ツールを使用する必要があります。独自のベンチマークを作成しようとする方がはるかに簡単です。

于 2012-08-24T09:43:00.570 に答える
1

JIT コンパイラーは、何もしないループを排除できます。これは、10,000 回の反復後にトリガーできます。

あなたがタイミングを取っていると思うのは、ループが何もしないことをJITが検出して削除するのにかかる時間です。これは、10,000 回の反復を実行するのにかかる時間よりも少し長くなります。

于 2012-08-24T09:49:56.933 に答える