23

java.lang.invoke.MethodHandleのパフォーマンスとjava.lang.reflect.Methodメソッドの直接呼び出しをテストする小さなベンチマークを書きました。

MethodHandle.invoke()私はそのパフォーマンスを直接呼び出しとほぼ同じと読みました。しかし、私のテスト結果は別のものを示しています:MethodHandleリフレクションより約 3 倍遅い呼び出し。私の問題は何ですか?これは、いくつかの JIT 最適化の結果でしょうか?

public class Main {
    public static final int COUNT = 100000000;
    static TestInstance test = new TestInstance();

    static void testInvokeDynamic() throws NoSuchMethodException, IllegalAccessException {
        int [] ar = new int[COUNT];

        MethodHandles.Lookup lookup = MethodHandles.lookup();
        MethodType mt = MethodType.methodType(int.class);

        MethodHandle handle = lookup.findStatic(TestInstance.class, "publicStaticMethod", mt) ;

        try {
            long start = System.currentTimeMillis();

            for (int i=0; i<COUNT; i++) {
                ar[i] = (int)handle.invokeExact();
            }

            long stop = System.currentTimeMillis();

            System.out.println(ar);

            System.out.println("InvokeDynamic time: " + (stop - start));
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }
    }

    static void testDirect() {
        int [] ar = new int[COUNT];

        try {
            long start = System.currentTimeMillis();

            for (int i=0; i<COUNT; i++) {
                ar[i] = TestInstance.publicStaticMethod();
            }

            long stop = System.currentTimeMillis();

            System.out.println(ar);

            System.out.println("Direct call time: " + (stop - start));
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }
    }

    static void testReflection() throws NoSuchMethodException {
        int [] ar = new int[COUNT];

        Method method = test.getClass().getMethod("publicStaticMethod");

        try {
            long start = System.currentTimeMillis();

            for (int i=0; i<COUNT; i++) {
                ar[i] = (int)method.invoke(test);
            }

            long stop = System.currentTimeMillis();

            System.out.println(ar);

            System.out.println("Reflection time: " + (stop - start));
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }
    }

    static void testReflectionAccessible() throws NoSuchMethodException {
        int [] ar = new int[COUNT];

        Method method = test.getClass().getMethod("publicStaticMethod");
        method.setAccessible(true);

        try {
            long start = System.currentTimeMillis();

            for (int i=0; i<COUNT; i++) {
                ar[i] = (int)method.invoke(test);
            }

            long stop = System.currentTimeMillis();

            System.out.println(ar);

            System.out.println("Reflection accessible time: " + (stop - start));
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }
    }

    public static void main(String ... args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException, InterruptedException {
        Thread.sleep(5000);

        Main.testDirect();
        Main.testInvokeDynamic();
        Main.testReflection();
        Main.testReflectionAccessible();

        System.out.println("\n___\n");

        System.gc();
        System.gc();

        Main.testDirect();
        Main.testInvokeDynamic();
        Main.testReflection();
        Main.testReflectionAccessible();
    }
}

環境: Java バージョン "1.7.0_11" Java(TM) SE ランタイム環境 (ビルド 1.7.0_11-b21) Java HotSpot(TM) 64 ビット サーバー VM (ビルド 23.6-b04、混合モード) OS - Windows 7 64

4

3 に答える 3

4

これは、別のクエリを参照して @AlekseyShipilev によって間接的に回答されたようです。次のリンク で Field.set のパフォーマンスを改善するにはどうすればよいですか (おそらく MethodHandles を使用して)?

読み通すと、同様の結果を示す追加のベンチマークが表示されます。上記の調査結果によると、違いは次のとおりです。

  • MethodHandle.invoke =~195ns
  • MethodHandle.invokeExact =~10ns
  • 直接呼び出し = 1.266ns

そのため、直接呼び出しは依然として高速ですが、MH は非常に高速です。ほとんどのユースケースでは、これで十分であり、確かに古いリフレクション フレームワークよりも高速です (ところで、上記の調査結果によると、リフレクションは java8 vm でも大幅に高速です)。

この違いがシステムで重要な場合は、直接呼び出しをサポートする直接反射ではなく、別のパターンを見つけることをお勧めします。

于 2015-01-26T18:31:19.513 に答える
1

他の人も同様の結果を見ているようです: http://vanillajava.blogspot.com/2011/08/methodhandle-performance-in-java-7.html

これは他の人のものです: http://andrewtil.blogspot.com/2011/08/using-method-handles.html

私はその2番目のものを実行し、ウォームアップを行うようにそのテストを修正しても、ほぼ同じ速度であることがわかりました. ただし、毎回args配列を作成しないように修正しました。デフォルトのカウントでは、同じ結果になりました。メソッドハンドルは少し高速でした。しかし、10000000 (デフォルト * 10) のカウントを行ったところ、リフレクションがはるかに高速になりました。

したがって、パラメーターを使用してテストすることをお勧めします。MethodHandles はより効率的にパラメーターを処理するのでしょうか? また、カウントの変更 (反復回数) を確認してください。

質問に対する @meriton のコメントは彼の作品にリンクしており、非常に役に立ちます:リフレクションを介して Java でゲッターを呼び出す: 繰り返し呼び出す最も速い方法は何ですか (パフォーマンスとスケーラビリティに関して)?

于 2013-04-10T18:53:23.433 に答える
0

定数を返すような単純な実装であれば、publicStaticMethod 直接呼び出しが JIT コンパイラによってインライン化された可能性が非常に高くなります。これは、methodHandles では不可能な場合があります。

RE http://vanillajava.blogspot.com/2011/08/methodhandle-performance-in-java-7.htmlの例では、実装が優れていないことをコメントしています。計算ループで型キャストを (Integer ではなく) int に変更すると、結果は直接メソッド呼び出しに近づきます。

(ランダムな int を返す将来のタスクを作成して呼び出す) の複雑な実装により、MethodStatic がダイレクト メソッドよりも最大 ~10% 遅い数値でベンチマークが得られました。したがって、JIT の最適化により、パフォーマンスが 3 倍遅くなる可能性があります。

于 2014-02-04T17:13:17.107 に答える