MethodHandle性能

25

我写了一个小基准测试,测试了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的版本为(1.7.0_11-b21),Java HotSpot(TM) 64位服务器虚拟机的版本为(23.6-b04, 混合模式)。操作系统- Windows 7 64位。


1
首先确保您知道如何编写基准测试,请参阅:http://www.ibm.com/developerworks/java/library/j-jtp02225/index.html - Nathan Hughes
@NathanHughes 他的基准测试有什么问题? - Andremoniy
他的测试由两个相同的部分组成。此外,JVM的预热无法消除方法调用时间之间的显着差异。 - Andremoniy
1
@Andremoniy:我不知道他的问题是什么,但结果值得怀疑。希望有人能提出一个有趣的答案,为这个问题点赞。 - Nathan Hughes
3
最近我做了一个类似的基准测试。选择服务器虚拟机和客户端虚拟机以及方法句柄是否来自静态final字段会极大地影响性能。 - meriton
显示剩余3条评论
3个回答

4
看起来这个问题在@AlekseyShipilev的不同查询中间接得到了回答。 在以下链接中 如何通过MethodHandles提高Field.set的性能? 如果您仔细阅读,您将看到其他类似的基准测试结果。直接调用可能只是可以通过JIT进行优化的方式。
根据上述发现,差异为:
- MethodHandle.invoke =~195ns - MethodHandle.invokeExact =~10ns - 直接调用 = 1.266ns
所以,直接调用仍然会更快,但MH非常快。对于大多数用例,这应该足够,并且肯定比旧的反射框架快(顺便说一下,根据上述发现,在java8 vm下反射也显着更快)。
如果在您的系统中这种差异很重要,我建议找到不同的模式,而不是支持直接调用的直接反射。

1

看起来其他人也看到了类似的结果: http://vanillajava.blogspot.com/2011/08/methodhandle-performance-in-java-7.html

这是别人的: http://andrewtill.blogspot.com/2011/08/using-method-handles.html

我运行了第二个测试,并发现它们的速度大致相同,即使修复了那个测试以进行预热。然而,我将其修复,使其不会每次都创建一个args数组。在默认计数下,结果是相同的: methodhandles稍微快一点。但是当我对10000000(默认*10)进行计数时,反射速度更快。

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

@meriton在评论中提供了他的作品链接,看起来非常有帮助:通过反射调用Java中的getter:性能和可扩展性方面最快的重复调用方式是什么?

0

如果publicStaticMethod只是简单的返回一个常量实现,那么直接调用很可能会被JIT编译器内联。而这对于methodHandles来说可能不太可能。

关于http://vanillajava.blogspot.com/2011/08/methodhandle-performance-in-java-7.html的例子,正如评论所提到的,它并不是一个很好的实现。如果你在计算循环中将类型强制转换为int(而不是Integer),结果会更接近直接方法调用。

通过复杂的实现(创建和调用返回随机整数的future任务),我们得到了更接近的基准数据,其中MethodStatic最多比直接方法慢约10%。因此你可能会看到由于JIT优化而导致3倍的性能下降。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接