Lambda、MethodHandle、CallSite调用简单性能测试与调优

参考反射调用简单性能测试与调优

直接调用性能:

public class Test {
    public static void target(int i) { }

    public static void main(String[] args) throws Exception {
        long current = System.currentTimeMillis();
        for (int i = 1; i <= 2_000_000_000; i++) {
            if (i % 100_000_000 == 0) {
                long temp = System.currentTimeMillis();
                System.out.println(temp - current);
                current = temp;
            }
            target(128);
        }
    }
}
82
82
84
85
81

1.Lambda表达式调用性能

import java.util.function.IntConsumer;

public class TestV6 {
    public static void target(int i) { }

    public static void main(String[] args) throws Exception {
        long current = System.currentTimeMillis();
        for (int i = 1; i <= 2_000_000_000; i++) {
            if (i % 100_000_000 == 0) {
                long temp = System.currentTimeMillis();
                System.out.println(temp - current);
                current = temp;
            }

            ((IntConsumer) j -> TestV6.target(j)).accept(128);
            // ((IntConsumer) TestTestV6::target).accept(128);
        }
    }
}

结果:

111
110
110
110
113

((IntConsumer) TestTestV6::target).accept(128)调用如下:

111
110
113
112
110

与直接调用性能差别不大,说明JIT能够对内联Lambda和方法引用的accept操作,最终优化为空操作。

2.捕获变量版本

import java.util.function.IntConsumer;

public class TestV7 {
    public static void target(int i) { }

    public static void main(String[] args) throws Exception {
        int x = 2;

        long current = System.currentTimeMillis();
        for (int i = 1; i <= 2_000_000_000; i++) {
            if (i % 100_000_000 == 0) {
                long temp = System.currentTimeMillis();
                System.out.println(temp - current);
                current = temp;
            }

            ((IntConsumer) j -> TestV7.target(x + j)).accept(128);
        }
    }
}
82
83
83
83
83

性能跟直接调用一样,JIT的逃逸分析将新建的适配器类实例优化掉了。

关闭逃逸分析,-XX:-DoEscapeAnalysis

305
301
339
318
308

逃逸分析会生效,需要满足两个条件:
1)invokedynamic指令所执行的MethodHandle能够内联
2)对accept方法的调用也能够内联

3.MethodHandle调用

public class TestV8 {
    public static void target(int i) { }

    public static void main(String[] args) throws Throwable {
        MethodHandles.Lookup l = MethodHandles.lookup();
        MethodType t = MethodType.methodType(void.class, int.class);
        MethodHandle mh = l.findStatic(TestV8.class, "target", t);

        long current = System.currentTimeMillis();
        for (int i = 1; i <= 2_000_000_000; i++) {
            if (i % 100_000_000 == 0) {
                long temp = System.currentTimeMillis();
                System.out.println(temp - current);
                current = temp;
            }

            mh.invokeExact(128);
        }
    }
}

325
323
321
325
324

MethodHandle与Lambda表达式或方法引用的差别在于,JIT无法将该MethodHandle识别为常量,从而无法进行内联。

4.将MethodHandle定义为final

public class TestV9 {
    public static void target(int i) {
    }

    static final MethodHandle mh;

    static {
        try {
            MethodHandles.Lookup l = MethodHandles.lookup();
            MethodType t = MethodType.methodType(void.class, int.class);
            mh = l.findStatic(TestV9.class, "target", t);
        } catch (Throwable e) {
            throw new RuntimeException(e);
        }
    }

    public static void main(String[] args) throws Throwable {
        long current = System.currentTimeMillis();
        for (int i = 1; i <= 2_000_000_000; i++) {
            if (i % 100_000_000 == 0) {
                long temp = System.currentTimeMillis();
                System.out.println(temp - current);
                current = temp;
            }

            mh.invokeExact(128);
        }
    }
}
83
83
83
83
84

定义为常量后,JIT即可进行内联。

5.final MyCallSite无法内联

public class TestV10 {
    public static void target(int i) {
    }

    public static class MyCallSite {

        public final MethodHandle mh;

        public MyCallSite() {
            mh = findTarget();
        }

        private static MethodHandle findTarget() {
            try {
                MethodHandles.Lookup l = MethodHandles.lookup();
                MethodType t = MethodType.methodType(void.class, int.class);
                return l.findStatic(TestV10.class, "target", t);
            } catch (Throwable e) {
                throw new RuntimeException(e);
            }
        }
    }

    private static final MyCallSite myCallSite = new MyCallSite();

    public static void main(String[] args) throws Throwable {
        long current = System.currentTimeMillis();
        for (int i = 1; i <= 2_000_000_000; i++) {
            if (i % 100_000_000 == 0) {
                long temp = System.currentTimeMillis();
                System.out.println(temp - current);
                current = temp;
            }

            myCallSite.mh.invokeExact(128);
        }
    }
}
299
310
302
298
301

可以看出自定义的调用点,即使定义为final,也无法进行内联

6.继承ConstantCallSite即可内联

public class TestV11 {
    public static void target(int i) {
    }

    public static class MyCallSite extends ConstantCallSite {

        public MyCallSite() {
            super(findTarget());
        }

        private static MethodHandle findTarget() {
            try {
                MethodHandles.Lookup l = MethodHandles.lookup();
                MethodType t = MethodType.methodType(void.class, int.class);
                return l.findStatic(TestV11.class, "target", t);
            } catch (Throwable e) {
                throw new RuntimeException(e);
            }
        }
    }

    public static final MyCallSite myCallSite = new MyCallSite();

    public static void main(String[] args) throws Throwable {
        long current = System.currentTimeMillis();
        for (int i = 1; i <= 2_000_000_000; i++) {
            if (i % 100_000_000 == 0) {
                long temp = System.currentTimeMillis();
                System.out.println(temp - current);
                current = temp;
            }

            myCallSite.getTarget().invokeExact(128);
        }
    }
}
82
82
83
83
85

可以看出ConstantCallSite及子类被特殊对待了,可以进行内联。

你可能感兴趣的:(Lambda、MethodHandle、CallSite调用简单性能测试与调优)