2

我正在做一个简单的项目,需要检索一个 bean 属性。首先我使用反射。然后我对 invokedynamic 和 Method Handler 进行了一些调查以获得更好的性能。

虽然invokeExact比反射快得多,但调用比反射慢得多。

测试环境:

  • Win7 32位
  • Java 7 U 80
  • 核心由于 CPU 3.06GHZ

我得到的 tp/ms 是这样的:

mhInvoke * 5 = reflect
reflect * 6 = mhInvokeExact
mhInvokeExact * 10 = direct call

这是性能测试输出(我运行了两次):

Ref tpms = 10479
mh invoke tpms = 273
mh invoke with convert tpms = 957
mh invoke exact tpms = 78033
invoke directly tpms = 883011


Ref tpms = 14181
mh invoke tpms = 282
mh invoke with convert tpms = 984
mh invoke exact tpms = 88768
invoke directly tpms = 883011

这是我的测试代码:

import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.lang.reflect.Method;

/**
 * User: Mark Zang
 * Date: 2015/4/28
 * Time: 13:00
 */
public class PerfTestAppMain {

    String strVar = String.valueOf(System.currentTimeMillis());

    public String getStrVar() {
        return strVar;
    }

    static int count = 1024 * 1024 * 16;

    public static void main(String[] args) throws Throwable {
        ref();
        mhInvoke();
        mhInvokeConvert();
        mhInvokeExact();
        invoke();

        System.out.println();
        System.out.println();

        ref();
        mhInvoke();
        mhInvokeConvert();
        mhInvokeExact();
        invoke();
    }

    static void ref() throws Throwable {
        PerfTestAppMain bean = new PerfTestAppMain();

        Method ref = PerfTestAppMain.class.getMethod("getStrVar");

        Object ret = null;

        long start = System.currentTimeMillis();

        for (int i = 0; i < count; i++) {
            ret = ref.invoke(bean);
        }

        long end = System.currentTimeMillis();

        System.out.println("Ref tpms = " + ((count) / (end - start)));

    }

    static void mhInvoke() throws Throwable {
        PerfTestAppMain bean = new PerfTestAppMain();

        MethodHandle mh = MethodHandles.lookup().findVirtual(
                PerfTestAppMain.class,
                "getStrVar",
                MethodType.methodType(String.class))
                .bindTo(bean);

        Object ret = null;

        long start = System.currentTimeMillis();

        for (int i = 0; i < count; i++) {
            ret = mh.invoke();
        }

        long end = System.currentTimeMillis();

        System.out.println("mh invoke tpms = " + ((count) / (end - start)));
    }

    static void mhInvokeConvert() throws Throwable {
        PerfTestAppMain bean = new PerfTestAppMain();

        MethodHandle mh = MethodHandles.lookup().findVirtual(
                PerfTestAppMain.class,
                "getStrVar",
                MethodType.methodType(String.class))
                .bindTo(bean);

        String ret = null;

        long start = System.currentTimeMillis();

        for (int i = 0; i < count; i++) {
            ret = (String) mh.invoke();
        }

        long end = System.currentTimeMillis();

        System.out.println("mh invoke with convert tpms = " + ((count) / (end - start)));
    }

    static void mhInvokeExact() throws Throwable {
        PerfTestAppMain bean = new PerfTestAppMain();

        MethodHandle mh = MethodHandles.lookup().findVirtual(
                PerfTestAppMain.class,
                "getStrVar",
                MethodType.methodType(String.class));

        String ret = null;

        long start = System.currentTimeMillis();

        for (int i = 0; i < count; i++) {
            ret = (String) mh.invokeExact(bean);
        }

        long end = System.currentTimeMillis();

        System.out.println("mh invoke exact tpms = " + ((count) / (end - start)));
    }

    static void invoke() throws Throwable {
        PerfTestAppMain bean = new PerfTestAppMain();

        long start = System.currentTimeMillis();

        for (int i = 0; i < count; i++) {
            bean.getStrVar();
        }

        long end = System.currentTimeMillis();

        System.out.println("invoke directly tpms = " + ((count) / (end - start + 1)));
    }


}

invokeExact无法满足我的用例,因为我在编译时不知道确切的返回类型。似乎返回类型(强制转换)是MethodHandle性能的关键。

这似乎不是预期的结果,因为它MethodType具有确切的返回类型。为什么进行强制施法以提高性能仍然很重要?

是否有一些文档解释了这方面的细节?此外,是否有任何关于比较使用反射与方法处理程序的 impl 细节的文档?

4

1 回答 1

4

由于许多原因,您的基准测试存在缺陷。您需要编写一个利用基准来创建一个受控环境,其中 JIT 编译器不会优化您正在测量的代码。与反射相比,我曾经写过这样一个基准目标方法句柄:https ://gist.github.com/raphw/881e1745996f9d314ab0

Invokeexact不强制转换或转换类型。相反,它适应参数和返回值的实际类型。当原始参数或返回类型在编译时已知时,这可能是有益的,因为 Java 编译器会为方法调用创建合成签名。这避免了由反射 API 强制执行的装箱。除此之外,方法句柄不提供优于反射 API 的性能优势。我最近写了一篇关于这个问题的博客:http ://mydailyjava.blogspot.fr/2015/03/dismantling-invokedynamic.html?m=1

于 2015-05-14T22:10:40.323 回答