23

我写了一个小基准来测试方法的性能java.lang.invoke.MethodHandlejava.lang.reflect.Method直接调用。

我读到的MethodHandle.invoke()性能几乎与直接调用相同。但是我的测试结果显示了另一个:MethodHandle调用比反射慢大约三倍。我的问题是什么?这可能是一些 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,混合模式)操作系统 - Windows 7 64

4

3 回答 3

4

看起来这是@AlekseyShipilev 间接回答了一个不同的查询。在以下链接 中,如何提高 Field.set 的性能(可能使用 MethodHandles)?

如果您通读,您将看到显示类似结果的其他基准。很可能直接调用可以简单地通过 JIT 以根据上述发现的方式进行优化,区别在于:

  • 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 ://andrewtill.blogspot.com/2011/08/using-method-handles.html

我跑了第二个,看到他们的速度差不多,甚至修复了那个测试以进行热身。但是,我修复了它,所以它不会每次都创建一个 args 数组。在默认计数下,它产生了相同的结果:方法句柄要快一点。但我数了 10000000(默认*10),反射速度更快。

因此,我建议使用参数进行测试。我想知道 MethodHandles 是否更有效地处理参数?此外,检查更改计数 - 多少次迭代。

@meriton 对这个问题的评论链接到他的工作,看起来非常有帮助:Calling a getter in Java through reflection:什么是重复调用它的最快方法(性能和可扩展性方面)?

于 2013-04-10T18:53:23.433 回答
0

如果这publicStaticMethod 是一个简单的实现,比如返回一个常量,那么直接调用很可能是由 JIT 编译器内联的。使用 methodHandles 可能无法做到这一点。

RE http://vanillajava.blogspot.com/2011/08/methodhandle-performance-in-java-7.html示例,如前所述,评论它的实现并不好。如果在计算循环中将类型转换更改为 int(而不是 Integer),则结果更接近于直接方法调用。

通过复杂的实现(创建和调用返回随机整数的未来任务)给基准测试提供了更接近的数字,其中 MethodStatic 最大比直接方法慢约 10%。因此,由于 JIT 优化,您可能会看到性能降低 3 倍

于 2014-02-04T17:13:17.107 回答