【问题标题】:MethodHandle performance方法句柄性能
【发布时间】:2013-03-25 17:50:46
【问题描述】:

我写了一个小基准测试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 Runtime Environment (build 1.7.0_11-b21) Java HotSpot(TM) 64-Bit Server VM (build 23.6-b04,混合模式)操作系统 - Windows 7 64

【问题讨论】:

  • 首先确保您知道如何编写基准,请参阅:ibm.com/developerworks/java/library/j-jtp02225/index.html
  • @NathanHughes 他的基准有什么问题?
  • @Andremoniy:最明显的问题是 jvm 没有预热。
  • @Andremoniy:我不知道他的问题是什么,但结果令人怀疑。希望有人会为此提出一个有趣的答案,+1 的问题。
  • 我最近做了一个similar benchmark。服务器和客户端 VM 之间的选择,以及方法句柄是否来自静态 final 字段都对性能有很大影响。

标签: java performance methodhandle


【解决方案1】:

看起来这是由@AlekseyShipilev 间接回答的,参考了不同的查询。 在以下链接中 How can I improve performance of Field.set (perhap using MethodHandles)?

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

  • MethodHandle.invoke =~195ns
  • MethodHandle.invokeExact =~10ns
  • 直接调用 = 1.266ns

所以 - 直接调用仍然会更快,但 MH 非常快。 对于大多数用例来说,这应该足够了,而且肯定比旧的反射框架更快(顺便说一句 - 根据上面的发现,在 java8 vm 下反射也明显更快)

如果这种差异在您的系统中很明显,我建议您找到不同的模式,而不是支持直接调用的直接反射。

【讨论】:

    【解决方案2】:

    似乎其他人也看到了类似的结果: 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 though reflection: What's the fastest way to repeatedly call it (performance and scalability wise)?

    【讨论】:

      【解决方案3】:

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

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

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2022-01-14
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-03-16
        • 1970-01-01
        • 1970-01-01
        • 2011-11-29
        相关资源
        最近更新 更多