4

Android MethodHandle 反射性能优化

 3 years ago
source link: https://my.oschina.net/ososchina/blog/5023158
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client
Android MethodHandle 反射性能优化

一、关于指令调用

无论是基于栈的JVM还是还是基于寄存器的DVM,他们除了操作数栈中变量的移动和空间分配、以及程序计数器的不同,基本方法调用的基本指令都是遵守JSR规范的。

在讨论MethodHandle之前,说一下Java动态性缺陷。Java语言是一门偏向静态的语言,他的动态性一致存在很多局限:

  • 泛型缺陷: 前端编译(javac)时泛型被擦除、泛型无法传递、泛型不支持值类型、不支持动态泛型
  • 不支持动态方法替换
  • 不支持普通类的动态代理
  • 生成字节码的工具API相对繁琐等
  • 反射调用校验太多活着需要生成大量字节码

MethodHandle主要解决的是问题:

【1】反射优化,MethodHandle属于轻量级直接调用字节码,而反射属于重量级,并且完全无法代替反射

【2】动态方法分派,能做到子类调用父类的方法,即便这个方法被子类复写了。

【3】调用运行在JVM上的其他语言

我们知道,在JSR标准中,提供了四种调用方法的指令

  • invokestatic : 调用静态方法  (JIT 会对这种指令进行内联优化,而且不需要守护内联)
  • invokevirtual :调用虚方法,一般指非私有方法 和final方法(JIT会通过类层次结构分析 (CHA),将此类方法去虚化,进一步实现内联,这种内联一般需要解释器守护,防止陷阱出现无法逃逸)
  • invokespecial: 调用构造方法和非虚方法 (JIT 会对这种指令进行内联优化,而且不需要守护内联)
  • invokeinterface: 调用接口方法(JIT是否优化不取决于这种调用,而是他的实现invokevirtual)

MethodHandle本质上是为了调用JVM上其他语言,实际上也有方法分派的能力(注意:只能分派直接父类,JSR版本修改之前可以任意分派)

public class MethodHandleDynamicInvoke {

    public static void main(String[] args) {
        Son son = new Son();
        son.love(Father.class);
        son.love(Son.class);
    }
}

class Father   {
    String thinking() {
        return "Father";
    }
}

class Son extends Father {
    @Override
    String thinking() {
        return "Son";
    }

    public void love(Class target) {
        MethodType methodType = MethodType.methodType(String.class);
        try {
            MethodHandle thinkingMethodHanle = MethodHandles.lookup().findSpecial(target, "thinking", methodType, Son.class).bindTo(Son.this);
            System.out.println("I love " + ((String) thinkingMethodHanle.invokeExact()));
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }
    }
}

输出结果如下

I love Father
I love Son

二、MethodHandle如何优化反射?

前一节我们我们加单了解了JVM相关指令和MethodHandle解决的问题,接下来我们介绍以下几个重要的API

  • MethodHandles.lookup() ; 方法查询器
  • MethodType.methodType(....);  用于返回值,参数匹配,动态参数,第一个参数是返回值类型,从第二个参数起是方法参数
  • MethodHandle.bindTo(Object obj) ;绑定执行对象,绑定之后,invoke活着invokeExact第一个参数就不能传当前对象了
  • MethodHanle.invoke(...) ; 该方法属于模糊参数类型,参数可以传入父类类型,当然成本是该方法执行效率相当低
  • MethodHandle.invokeExact(...) ;参数类型方法必须和调用的方法完全一致,如果是String必须转为String,不能用Object 代替,必要时进行类型转换,带来的收益是该方法执行效率高
  • Lookup findConstructor 、findSpecial 、findVirtual 、findStatic 、findXXXGetter、findXXXSetter ...用于方法查询,其中findSpecial查询非虚方法、findStatic查询静态方法
  • Lookup unreflectXXX 配合反射使用,因为MethodHandle并不能完全代替反射,比如调用不同包和class中的私有方法,需要通过反射,然后转为MethodHandle

MethodHandle 如何做到性能优化的?

  • MethodHandle静态化
  • 提前bindTo性能会更高
  • 调用invokeExact

下面我们给出一个例子,定义一个私有方法,提升反射性能

public class ClassMethodHandle {
    private   int getDoubleVal(int val) {
        return val * 2;
    }
}

测试代码如下


public class MethodHandleTest   {
    static  Method method_getDoubleVal;
    static MethodHandle methodHandle_getDoubleVal;
    static {
        try {
            MethodHandles.Lookup lookup = MethodHandles.lookup();
            method_getDoubleVal =  ClassMethodHandle.class.getDeclaredMethod("getDoubleVal",int.class);
            method_getDoubleVal.setAccessible(true);
            methodHandle_getDoubleVal = lookup.unreflect(method_getDoubleVal); 
        //私有方法不能通过findSpecial访问,需要借助Method Reflection
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }

    }
    public static void main(String[] args) throws Throwable {

        long startTime  = System.nanoTime();
        testMethodReflection();
        long dx1 = System.nanoTime() - startTime;
        System.out.println("[1] " + (dx1));

        startTime  = System.nanoTime();
        testMethodHandle();
        long dx2 = System.nanoTime() - startTime;
        System.out.println("[2] " + (dx2));

        System.out.println(">>"+(dx1*1f)/dx2+"<<");

    }

    private static void testMethodReflection() {
        try {

             List<Integer> dataList = Arrays.asList(1, 2, 3, 4, 5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20);
            for (int i=0;i<5;i++) {
                MethodHandleTest.transformRelection(dataList,method_getDoubleVal,new ClassMethodHandle());
            }
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }
    }

    private static void testMethodHandle() throws Throwable {

        List<Integer> dataList = Arrays.asList(1, 2, 3, 4, 5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20);
        for (int i=0;i<5;i++) {
            MethodHandleTest.transform(dataList, methodHandle_getDoubleVal, new ClassMethodHandle());// 方法做为参数
        }

    }

    public static List<Integer> transformRelection(List<Integer> dataList, Method method,Object obj) throws Throwable {
        for (int i = 0; i < dataList.size(); i++) {
            dataList.set(i, (int) method.invoke(obj,dataList.get(i)));//relect invoke
        }
        return dataList;
    }

    public static List<Integer> transform(List<Integer> dataList, MethodHandle handle,Object obj) throws Throwable {
        for (int i = 0; i < dataList.size(); i++) {
            dataList.set(i, (int) handle.invokeExact((ClassMethodHandle)obj,(int)dataList.get(i)));//注意参数类型转换
        }
        return dataList;  
    }  
}

性能测试结果如下,取5次中的最大值

[1] 2952626
[2] 1031642
 >>2.8620646<<

如果提前绑定呢?


public class MethodHandleTest {
    static Method method_getDoubleVal;
    static MethodHandle methodHandle_getDoubleVal;

    static {
        try {
            MethodHandles.Lookup lookup = MethodHandles.lookup();
            method_getDoubleVal = ClassMethodHandle.class.getDeclaredMethod("getDoubleVal", int.class);
            method_getDoubleVal.setAccessible(true);
            methodHandle_getDoubleVal = lookup.unreflect(method_getDoubleVal).bindTo(new ClassMethodHandle());
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }

    }

    public static void main(String[] args) throws Throwable {

        long startTime = System.nanoTime();
        testMethodReflection();
        long dx1 = System.nanoTime() - startTime;
        System.out.println("[1] " + (dx1));

        startTime = System.nanoTime();
        testMethodHandle();
        long dx2 = System.nanoTime() - startTime;
        System.out.println("[2] " + (dx2));

        System.out.println(" "+((dx1*1f)/dx2));

    }

    private static void testMethodReflection() {
        try {
            ClassMethodHandle classMethodHandle = new ClassMethodHandle();
            List<Integer> dataList = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20);
            for (int i = 0; i < 5; i++) {
                MethodHandleTest.transformRelection(dataList, method_getDoubleVal, classMethodHandle);
            }
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }
    }

    private static void testMethodHandle() throws Throwable {
        List<Integer> dataList = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20);
        for (int i = 0; i < 5; i++) {
            MethodHandleTest.transform(dataList, methodHandle_getDoubleVal);// 方法做为参数
        }

    }

    public static List<Integer> transformRelection(List<Integer> dataList, Method method, Object obj) throws Throwable {
        for (int i = 0; i < dataList.size(); i++) {
            dataList.set(i, (int) method.invoke(obj, dataList.get(i)));//relect invoke
        }
        return dataList;
    }

    public static List<Integer> transform(List<Integer> dataList, MethodHandle handle) throws Throwable {
        for (int i = 0; i < dataList.size(); i++) {
            dataList.set(i, (int) handle.invokeExact((int) dataList.get(i)));//注意参数类型转换
        }
        return dataList;
    }
}

性能测试,取5次中的最大值

[1] 5893674
[2] 1279878
 4.6048717



About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK