6

Java Hotspot クライアントでタイミング テスト プログラムを実行すると、一貫した動作が得られます。ただし、Hotspot サーバーで実行すると、予期しない結果が得られます。基本的に、ポリモーフィズムのコストは、以下で複製しようとした特定の状況では許容できないほど高くなります。

これは Hotspot サーバーの既知の問題/バグですか、それとも何か間違っていますか?

テスト プログラムとタイミングは次のとおりです。

Intel i7, Windows 8
Java HotSpot(TM) 64-Bit Server VM (build 24.45-b08, mixed mode)
Mine2: 0.387028831 <--- polymorphic call with expected timing
Trivial: 1.545411765 <--- some more polymorphic calls
Mine: 0.727726371 <--- polymorphic call with unexpected timing. Should be about 0.38
Mine: 0.383132698 <--- direct call with expected timing

テストを追加すると、状況は悪化します。リストの終わり近くのテストのタイミングは完全にずれています。

interface canDoIsSquare {
    boolean isSquare(long x);
}

final class Trivial implements canDoIsSquare {
    @Override final public boolean isSquare(long x) {
        if (x > 0) {
            long t = (long) Math.sqrt(x);
            return t * t == x;
        }
        return x == 0;
    }
    @Override public String toString() {return "Trivial";}
}

final class Mine implements canDoIsSquare {
    @Override final public boolean isSquare(long x) {
        if (x > 0) {
            while ((x & 3) == 0)
                x >>= 2;
            if ((x & 2) != 0 || (x & 7) == 5)
                return false;
            final long t = (long) Math.sqrt(x);
            return (t * t == x);
        }
        return x == 0;
    }

    @Override public String toString() {return "Mine";}
}

final class Mine2 implements canDoIsSquare {
    @Override final public boolean isSquare(long x) {
        // just duplicated code for this test
        if (x > 0) {
            while ((x & 3) == 0)
                x >>= 2;
            if ((x & 2) != 0 || (x & 7) == 5)
                return false;
            final long t = (long) Math.sqrt(x);
            return (t * t == x);
        }
        return x == 0;
    }
    @Override final public String toString() {return "Mine2";}
}

public class IsSquared {
    static final long init = (long) (Integer.MAX_VALUE / 8)
            * (Integer.MAX_VALUE / 2) + 1L;

    static long test1(final canDoIsSquare fun) {
        long r = init;
        long startTimeNano = System.nanoTime();
        while (!fun.isSquare(r))
            ++r;
        long taskTimeNano = System.nanoTime() - startTimeNano;
        System.out.println(fun + ": " + taskTimeNano / 1e9);
        return r;
    }

    static public void main(String[] args) {
        Mine mine = new Mine();
        Trivial trivial = new Trivial();
        Mine2 mine2 = new Mine2();

        test1(mine2);
        test1(trivial);
        test1(mine);

        long r = init;
        long startTimeNano = System.nanoTime();
        while (!mine.isSquare(r))
            ++r;
        long taskTimeNano = System.nanoTime() - startTimeNano;
        System.out.println(mine + ": " + taskTimeNano / 1e9);
        System.out.println(r);
    }
}
4

2 に答える 2

0

要するに、JIT は 1 つのメソッド呼び出しと 2 つのメソッド呼び出しを最適化できますが、複数のポリモーフィックな呼び出しでは不可能な方法です。特定の行で呼び出される可能性のあるメソッドの数が重要であり、JIT は時間の経過とともにこの状況を構築します。メソッドがインライン化されると、さらなる最適化が可能になりますが、あなたのケースでは、問題の行がtest1実行中の可能なメソッド呼び出しの数を増やすため、遅くなります。

これを回避する方法は、短いテスト コードを複製して、各クラスが同等にテストされるようにすることです (これが現実的であると仮定します)。結果が変わる可能性があることを確認してください。

新しいループからメソッドを実行すると、そのコード行からメソッドを 1 つだけ呼び出すことの利点がわかります。

以下は、個々の行が呼び出すことができるメソッドの数に応じて表示されるさまざまなコストの表です。http://vanillajava.blogspot.co.uk/2012/12/performance-of-inlined-virtual-method.html

ポリモーフィズムはパフォーマンスを向上させるようには設計されていません。私にとって、ポリモーフィズムの複雑さが増すにつれて速度が低下することは完全に理にかなっています。

ところで、メソッドを作成finalしてもパフォーマンスは向上しません。行ごとにサブクラスを呼び出した場合、JIT は機能します (前述のとおり)。


編集 ご覧のとおり、clientJVM は比較的軽い 8 回の起動時間で設計されているため、コードを最適化していません。これは、クライアント JVM の一貫性は向上しますが、一貫して低速であることを意味します。最高のパフォーマンスが必要な場合は、最適化が適用されるかどうかに応じて、複数の可能な結果につながる多くの最適化戦略を検討する必要があります。

于 2013-11-08T09:54:13.620 に答える