web-dev-qa-db-ja.com

同等の静的メソッドと非静的メソッドの速度の大きな違い

このコードでは、mainメソッドでオブジェクトを作成し、そのオブジェクトメソッドff.twentyDivCount(i)(16010ミリ秒で実行)を呼び出すと、このアノテーションを使用して呼び出すよりもはるかに高速に実行されます:twentyDivCount(i)(59516 msで実行)。もちろん、オブジェクトを作成せずに実行すると、メソッドが静的になり、メインで呼び出すことができます。

public class ProblemFive {

    // Counts the number of numbers that the entry is evenly divisible by, as max is 20
    int twentyDivCount(int a) {    // Change to static int.... when using it directly
        int count = 0;
        for (int i = 1; i<21; i++) {

            if (a % i == 0) {
                count++;
            }
        }
        return count;
    }

    public static void main(String[] args) {
        long startT = System.currentTimeMillis();;
        int start = 500000000;
        int result = start;

        ProblemFive ff = new ProblemFive();

        for (int i = start; i > 0; i--) {

            int temp = ff.twentyDivCount(i); // Faster way
                       // twentyDivCount(i) - slower

            if (temp == 20) {
                result = i;
                System.out.println(result);
            }
        }

        System.out.println(result);

        long end = System.currentTimeMillis();;
        System.out.println((end - startT) + " ms");
    }
}

編集:これまでのところ、マシンが異なれば結果も異なるようですが、JRE 1.8。*を使用すると元の結果が一貫して再現されるようです。

86
Stabbz

JRE 1.8.0_45を使用すると、同様の結果が得られます。

調査:

  1. 実行中Javaで-XX:+UnlockDiagnosticVMOptions -XX:+PrintCompilation -XX:+PrintInlining VM optionsは、両方のメソッドがコンパイルおよびインライン化されることを示します
  2. メソッド自体の生成されたアセンブリを見ても、大きな違いはありません。
  3. ただし、インライン化されると、main内で生成されるAssemblyは大きく異なり、インスタンスメソッドは、特にループの展開に関してより積極的に最適化されます。

次に、テストを再度実行しましたが、上記の疑いを確認するために異なるループ展開設定を使用しました。私はあなたのコードを実行しました:

  • -XX:LoopUnrollLimit=0および両方のメソッドの実行が遅くなります(デフォルトのオプションを使用した静的メソッドと同様)。
  • -XX:LoopUnrollLimit=100および両方のメソッドは高速に実行されます(デフォルトのオプションを使用したインスタンスメソッドに似ています)。

結論として、デフォルト設定では、メソッドが静的な場合、JIT hotspot 1.8.0_45はループを展開できません(なぜそのように動作するのかわかりませんが) )。他のJVMでは異なる結果が得られる場合があります。

72
assylias

ただの証明されていない推測は、アッシリアの答えに基づいていました。

JVMは、ループのアンロールに70のようなしきい値を使用します。何らかの理由で、静的呼び出しは少し大きくなり、アンロールされません。

結果を更新する

  • LoopUnrollLimitが52未満の場合、両方のバージョンが遅くなります。
  • 52と71の間では、静的バージョンのみが低速です。
  • 71より上では、両方のバージョンが高速です。

私の推測では、静的呼び出しは内部表現でわずかに大きく、OPは奇妙なケースにぶつかったので、これは奇妙です。しかし、違いは約20のようで、これは意味がありません。

-XX:LoopUnrollLimit=51
5400 ms NON_STATIC
5310 ms STATIC
-XX:LoopUnrollLimit=52
1456 ms NON_STATIC
5305 ms STATIC
-XX:LoopUnrollLimit=71
1459 ms NON_STATIC
5309 ms STATIC
-XX:LoopUnrollLimit=72
1457 ms NON_STATIC
1488 ms STATIC

実験したい人にとっては、 my version が役に立つかもしれません。

33
maaartinus

これをデバッグモードで実行すると、インスタンスと静的ケースの数は同じになります。さらに、JITは、インスタンスメソッドの場合と同じ方法で、静的な場合にネイティブコードにコードをコンパイルすることをためらいます。

なぜそうするのですか?言うのは難しいです。これがより大きなアプリケーションであれば、おそらく正しいことをするでしょう...

0

してみてください:

public class ProblemFive {
    public static ProblemFive PROBLEM_FIVE = new ProblemFive();

    public static void main(String[] args) {
        long startT = System.currentTimeMillis();
        int start = 500000000;
        int result = start;


        for (int i = start; i > 0; i--) {
            int temp = PROBLEM_FIVE.twentyDivCount(i); // faster way
            // twentyDivCount(i) - slower

            if (temp == 20) {
                result = i;
                System.out.println(result);
                System.out.println((System.currentTimeMillis() - startT) + " ms");
            }
        }

        System.out.println(result);

        long end = System.currentTimeMillis();
        System.out.println((end - startT) + " ms");
    }

    int twentyDivCount(int a) {  // change to static int.... when using it directly
        int count = 0;
        for (int i = 1; i < 21; i++) {

            if (a % i == 0) {
                count++;
            }
        }
        return count;
    }
}
0
chengpohi

テストを微調整したところ、次の結果が得られました。

出力:

Dynamic Test:
465585120
232792560
232792560
51350 ms
Static Test:
465585120
232792560
232792560
52062 ms

[〜#〜] note [〜#〜]

それらを別々にテストしている間、私は動的で最大52秒、静的で最大200秒を得ました。

これはプログラムです:

public class ProblemFive {

    // Counts the number of numbers that the entry is evenly divisible by, as max is 20
    int twentyDivCount(int a) {  // Change to static int.... when using it directly
        int count = 0;
        for (int i = 1; i<21; i++) {

            if (a % i == 0) {
                count++;
            }
        }
        return count;
    }

    static int twentyDivCount2(int a) {
         int count = 0;
         for (int i = 1; i<21; i++) {

             if (a % i == 0) {
                 count++;
             }
         }
         return count;
    }

    public static void main(String[] args) {
        System.out.println("Dynamic Test: " );
        dynamicTest();
        System.out.println("Static Test: " );
        staticTest();
    }

    private static void staticTest() {
        long startT = System.currentTimeMillis();;
        int start = 500000000;
        int result = start;

        for (int i = start; i > 0; i--) {

            int temp = twentyDivCount2(i);

            if (temp == 20) {
                result = i;
                System.out.println(result);
            }
        }

        System.out.println(result);

        long end = System.currentTimeMillis();;
        System.out.println((end - startT) + " ms");
    }

    private static void dynamicTest() {
        long startT = System.currentTimeMillis();;
        int start = 500000000;
        int result = start;

        ProblemFive ff = new ProblemFive();

        for (int i = start; i > 0; i--) {

            int temp = ff.twentyDivCount(i); // Faster way

            if (temp == 20) {
                result = i;
                System.out.println(result);
            }
        }

        System.out.println(result);

        long end = System.currentTimeMillis();;
        System.out.println((end - startT) + " ms");
    }
}

また、テストの順序を次のように変更しました。

public static void main(String[] args) {
    System.out.println("Static Test: " );
    staticTest();
    System.out.println("Dynamic Test: " );
    dynamicTest();
}

そして、私はこれを得ました:

Static Test:
465585120
232792560
232792560
188945 ms
Dynamic Test:
465585120
232792560
232792560
50106 ms

ご覧のとおり、staticの前にdynamicが呼び出されると、staticの速度が劇的に低下します。

このベンチマークに基づいて:

hypothesizeそれはすべてJVM最適化に依存するということです。したがって、静的メソッドと動的メソッドの使用については経験則に従うことをお勧めします。

THUMBのルール:

Java:静的メソッドを使用する場合

0
nafas