代理10 cglib和jdk动态代理 调用性能测试

96
赤子心_d709
2017.05.03 22:02* 字数 1053

说明

这里将cglib 和 jdk动态代理进行对比,毕竟工业界用java assist或者asm比较奇怪
针对调用速度进行比较(创建速度jdk动态代理肯定要快)

版本:cglib 2.2.2 jdk 1.8.0_77

主要参考了下面这几篇文章,各自说法各不同,让人比较奇怪
http://www.cnblogs.com/haiq/p/4304615.html
http://adolphor.com/blog/2016/12/14/java-proxy-performance.html

第一篇结论就是jdk7以后 jdk动态代理 比 cglib快
第二篇结论就是cglib比 jdk动态代理快
实验了一下,两份代码,在我的win7上

实验机器配置

结果都相符,第一篇结论也是对的,第二篇结论也是对的。
实时是这样吗。

不,参考两个cglib实现的源码,略有不同。
不同体现在

实验代码对比

//说cglib慢的
Test cglibProxy = CglibProxyTest.newProxyInstance(TestImpl.class);
public static <T extends Test> Test newProxyInstance(Class<T> targetInstanceClazz){
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(targetInstanceClazz);
        enhancer.setCallback(new CglibProxyTest());
        return (Test) enhancer.create();
    }

    public Object intercept(Object obj, Method method, Object[] args,
            MethodProxy proxy) throws Throwable {
        return proxy.invokeSuper(obj, args);
    }

可以看到这是类似前面demo的写法,用的 setSuperclass 以及 invokeSuper这种方式

//说cglib快的
private static class CglibInterceptor implements MethodInterceptor {
    final Object delegate;

    CglibInterceptor(Object delegate) {
      this.delegate = delegate;
    }

    public Object intercept(Object object, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
      return methodProxy.invoke(delegate, objects);
    }
  }

private static CountService createCglibDynamicProxy(final CountService delegate) throws Exception {
    Enhancer enhancer = new Enhancer();
    enhancer.setCallback(new CglibInterceptor(delegate));
    enhancer.setInterfaces(new Class[]{CountService.class});
    CountService cglibProxy = (CountService) enhancer.create();
    return cglibProxy;
  }

CountService delegate = new CountServiceImpl();
CountService cglibProxy = createCglibDynamicProxy(delegate);

看到区别没有!!!
这种方式并没有setSuperClass以及invokeSuper
而是setInterfaces以及invoke
而是传递了一个实现类,实现类的fastclass
(上一节分析了invoke和invokeSuper的区别,invoke必须传入''另外一个obj",否则会导致死循环)

实验结果分析

为什么两份代码结果差距那么大
cglib快的那一份代码,用的invoke,也就是上一节源码分析中的f1(即实现类的fastclass),对应的class decompile如下

//cglib快的那一份代码中的f1,也就是
package cglib;

import cglib.CglibLearn;
import java.lang.reflect.InvocationTargetException;
import net.sf.cglib.core.Signature;
import net.sf.cglib.reflect.FastClass;

public class CglibLearn$service$$FastClassByCGLIB$$eebe5c1
extends FastClass {
    public CglibLearn$service$$FastClassByCGLIB$$eebe5c1(Class class_) {
        super(class_);
    }

    public int getIndex(Signature signature) {
        String string = signature.toString();
        switch (string.hashCode()) {
            case -909388886: {
                if (!string.equals("say()V")) break;
                return 0;
            }
        }
        return -1;
    }

    public int getIndex(String string, Class[] arrclass) {
        String string2 = string;
        Class[] arrclass2 = arrclass;
        switch (string2.hashCode()) {
            case 113643: {
                if (!string2.equals("say")) break;
                arrclass2 = arrclass2;
                switch (arrclass2.length) {
                    case 0: {
                        return 0;
                    }
                }
                break;
            }
            default: {
                break;
            }
        }
        return -1;
    }

    public int getIndex(Class[] arrclass) {
        Class[] arrclass2 = arrclass;
        Class[] arrclass3 = arrclass2;
        arrclass2.length;
        return -1;
    }

    public Object invoke(int n, Object object, Object[] arrobject) throws InvocationTargetException {
        try {
            switch (n) {
                case 0: {
                    ((CglibLearn.service)object).say();
                    return null;
                }
            }
        }
        catch (Throwable v0) {
            throw new InvocationTargetException(v0);
        }
        throw new IllegalArgumentException("Cannot find matching method/constructor");
    }

    public Object newInstance(int n, Object[] arrobject) throws InvocationTargetException {
        CglibLearn.service service2;
        CglibLearn.service service3 = service2;
        CglibLearn.service service4 = service2;
        int n2 = n;
        try {
        }
        catch (Throwable v4) {
            throw new InvocationTargetException(v4);
        }
        throw new IllegalArgumentException("Cannot find matching method/constructor");
    }

    public int getMaxIndex() {
        return 0;
    }
}

可以看到该fastClass里面的invoke函数非常简单,正常情况只有一种,那就是case 0;
那么,为什么invokeSuper和setSuperClass的实现方式,就会慢呢。
invokeSuper用的是f2,也就是enhance的fastClass,这里面函数就多了,稍微感受下
invokeSuper里面调用f2的invoke,要处理26种case

public Object invoke(int n, Object object, Object[] arrobject) throws InvocationTargetException {
        CglibLearn$service$$EnhancerByCGLIB$$e9bfe24c cglibLearn$service$$EnhancerByCGLIB$$e9bfe24c = (CglibLearn$service$$EnhancerByCGLIB$$e9bfe24c)object;
        try {
            switch (n) {
                case 0: {
                    return new Boolean(cglibLearn$service$$EnhancerByCGLIB$$e9bfe24c.equals(arrobject[0]));
                }
                case 1: {
                    return cglibLearn$service$$EnhancerByCGLIB$$e9bfe24c.toString();
                }
                case 2: {
                    return new Integer(cglibLearn$service$$EnhancerByCGLIB$$e9bfe24c.hashCode());
                }
                case 3: {
                    return cglibLearn$service$$EnhancerByCGLIB$$e9bfe24c.newInstance((Class[])arrobject[0], (Object[])arrobject[1], (Callback[])arrobject[2]);
                }
                case 4: {
                    return cglibLearn$service$$EnhancerByCGLIB$$e9bfe24c.newInstance((Callback[])arrobject[0]);
                }
                case 5: {
                    return cglibLearn$service$$EnhancerByCGLIB$$e9bfe24c.newInstance((Callback)arrobject[0]);
                }
                case 6: {
                    cglibLearn$service$$EnhancerByCGLIB$$e9bfe24c.setCallbacks((Callback[])arrobject[0]);
                    return null;
                }
                case 7: {
                    cglibLearn$service$$EnhancerByCGLIB$$e9bfe24c.setCallback(((Number)arrobject[0]).intValue(), (Callback)arrobject[1]);
                    return null;
                }
                case 8: {
                    cglibLearn$service$$EnhancerByCGLIB$$e9bfe24c.say();
                    return null;
                }
                case 9: {
                    CglibLearn$service$$EnhancerByCGLIB$$e9bfe24c.CGLIB$SET_THREAD_CALLBACKS((Callback[])arrobject[0]);
                    return null;
                }
                case 10: {
                    CglibLearn$service$$EnhancerByCGLIB$$e9bfe24c.CGLIB$SET_STATIC_CALLBACKS((Callback[])arrobject[0]);
                    return null;
                }
                case 11: {
                    return cglibLearn$service$$EnhancerByCGLIB$$e9bfe24c.getCallback(((Number)arrobject[0]).intValue());
                }
                case 12: {
                    return cglibLearn$service$$EnhancerByCGLIB$$e9bfe24c.getCallbacks();
                }
                case 13: {
                    return CglibLearn$service$$EnhancerByCGLIB$$e9bfe24c.CGLIB$findMethodProxy((Signature)arrobject[0]);
                }
                case 14: {
                    CglibLearn$service$$EnhancerByCGLIB$$e9bfe24c.CGLIB$STATICHOOK1();
                    return null;
                }
                case 15: {
                    cglibLearn$service$$EnhancerByCGLIB$$e9bfe24c.CGLIB$finalize$0();
                    return null;
                }
                case 16: {
                    return new Boolean(cglibLearn$service$$EnhancerByCGLIB$$e9bfe24c.CGLIB$equals$1(arrobject[0]));
                }
                case 17: {
                    return cglibLearn$service$$EnhancerByCGLIB$$e9bfe24c.CGLIB$toString$2();
                }
                case 18: {
                    return new Integer(cglibLearn$service$$EnhancerByCGLIB$$e9bfe24c.CGLIB$hashCode$3());
                }
                case 19: {
                    return cglibLearn$service$$EnhancerByCGLIB$$e9bfe24c.CGLIB$clone$4();
                }
                case 20: {
                    cglibLearn$service$$EnhancerByCGLIB$$e9bfe24c.CGLIB$say$5();
                    return null;
                }
                case 21: {
                    cglibLearn$service$$EnhancerByCGLIB$$e9bfe24c.wait();
                    return null;
                }
                case 22: {
                    cglibLearn$service$$EnhancerByCGLIB$$e9bfe24c.wait(((Number)arrobject[0]).longValue(), ((Number)arrobject[1]).intValue());
                    return null;
                }
                case 23: {
                    cglibLearn$service$$EnhancerByCGLIB$$e9bfe24c.wait(((Number)arrobject[0]).longValue());
                    return null;
                }
                case 24: {
                    return cglibLearn$service$$EnhancerByCGLIB$$e9bfe24c.getClass();
                }
                case 25: {
                    cglibLearn$service$$EnhancerByCGLIB$$e9bfe24c.notify();
                    return null;
                }
                case 26: {
                    cglibLearn$service$$EnhancerByCGLIB$$e9bfe24c.notifyAll();
                    return null;
                }
            }
        }
        catch (Throwable v1) {
            throw new InvocationTargetException(v1);
        }
        throw new IllegalArgumentException("Cannot find matching method/constructor");
    }

所以cglib两种实现方式,invokeSuper + setSuperClass 永远比 invoke + setInterfaces慢
因为同样的情况下,后者生成的函数更多,后者switch判断的case也就更多
net.sf.cglib.proxy.MethodProxy#invoke比net.sf.cglib.proxy.MethodProxy#invokeSuper快

那么cglib invoke+setInterfaces方式和 jdk动态代理谁快
通过上面的例子,可以看出cglib其实和实现类的函数个数有关系,毕竟要switch各个index

自定义实验

利用java benchmark即jmh包进行测试

package cglibtest;

import net.sf.cglib.core.DebuggingClassWriter;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.OutputTimeUnit;
import org.openjdk.jmh.annotations.Scope;
import org.openjdk.jmh.annotations.State;
import org.openjdk.jmh.runner.Runner;
import org.openjdk.jmh.runner.options.Options;
import org.openjdk.jmh.runner.options.OptionsBuilder;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.concurrent.TimeUnit;

@OutputTimeUnit(TimeUnit.SECONDS)
@State(Scope.Thread)
public class DynamicProxyPerformanceTest {

    static CountService jdkProxy;

    static CountService cglibProxy;

    static {
        try {
            init();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static void init() throws Exception {
        CountService delegate = new CountServiceImpl();

        long time = System.currentTimeMillis();
        jdkProxy = createJdkDynamicProxy(delegate);
        time = System.currentTimeMillis() - time;
        System.out.println("Create JDK Proxy: " + time + " ms");

        time = System.currentTimeMillis();
        cglibProxy = createCglibDynamicProxy(delegate);
        time = System.currentTimeMillis() - time;
        System.out.println("Create CGLIB Proxy: " + time + " ms");
    }

    public static void main(String[] args) throws Exception {
        System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, ".//");
        try {
            init();
        } catch (Exception e) {
            e.printStackTrace();
        }
        Options opt = new OptionsBuilder()
                .include(DynamicProxyPerformanceTest.class.getSimpleName())
                .forks(1)
                .warmupIterations(3)
                .measurementIterations(5)
                .build();
        new Runner(opt).run();
    }

    @Benchmark
    public void cglibProxy() {
        int count = 1000000;
        for (int i = 0; i < count; i++) {
            cglibProxy.count();
            cglibProxy.test1();
            cglibProxy.test2();
            cglibProxy.test3();
            cglibProxy.test4();
            cglibProxy.test5();
            cglibProxy.test6();
            cglibProxy.test7();
            cglibProxy.test8();
            cglibProxy.test9();
            cglibProxy.t0();
            cglibProxy.t1();
            cglibProxy.t2();
            cglibProxy.t3();
            cglibProxy.t4();
            cglibProxy.t5();
            cglibProxy.t6();
            cglibProxy.t7();
            cglibProxy.t8();
            cglibProxy.t9();
            cglibProxy.a1();
            cglibProxy.a2();
            cglibProxy.a3();
            cglibProxy.a4();
            cglibProxy.a5();
            cglibProxy.a6();
            cglibProxy.a7();
            cglibProxy.a8();
            cglibProxy.a9();
        }
    }

    @Benchmark
    public void testjdkProxy() {
        int count = 1000000;
        for (int i = 0; i < count; i++) {
            jdkProxy.count();
            jdkProxy.test1();
            jdkProxy.test2();
            jdkProxy.test3();
            jdkProxy.test4();
            jdkProxy.test5();
            jdkProxy.test6();
            jdkProxy.test7();
            jdkProxy.test8();
            jdkProxy.test9();
            jdkProxy.t0();
            jdkProxy.t1();
            jdkProxy.t2();
            jdkProxy.t3();
            jdkProxy.t4();
            jdkProxy.t5();
            jdkProxy.t6();
            jdkProxy.t7();
            jdkProxy.t8();
            jdkProxy.t9();
            jdkProxy.a1();
            jdkProxy.a2();
            jdkProxy.a3();
            jdkProxy.a4();
            jdkProxy.a5();
            jdkProxy.a6();
            jdkProxy.a7();
            jdkProxy.a8();
            jdkProxy.a9();
        }
    }

    private static <T extends CountService> CountService createJdkDynamicProxy(
            final CountService delegate) {
        CountService jdkProxy = (CountService) Proxy
                .newProxyInstance(ClassLoader.getSystemClassLoader(),
                        new Class[] { CountService.class },
                        new JdkHandler(delegate));
        return jdkProxy;
    }

    private static class JdkHandler implements InvocationHandler {

        final Object delegate;

        JdkHandler(Object delegate) {
            this.delegate = delegate;
        }

        public Object invoke(Object object, Method method, Object[] objects)
                throws Throwable {
            return method.invoke(delegate, objects);
        }
    }

    private static CountService createCglibDynamicProxy(
            final CountService delegate) throws Exception {
        Enhancer enhancer = new Enhancer();
        enhancer.setCallback(new CglibInterceptor(delegate));
//        enhancer.setSuperclass(CountServiceImpl.class);
        enhancer.setInterfaces(new Class[] { CountService.class });
        CountService cglibProxy = (CountService) enhancer.create();
        return cglibProxy;
    }

    private static class CglibInterceptor implements MethodInterceptor {

        final Object delegate;

        CglibInterceptor(Object delegate) {
            this.delegate = delegate;
        }

        public Object intercept(Object object, Method method, Object[] objects,
                MethodProxy methodProxy) throws Throwable {
            return methodProxy.invoke(delegate, objects);
        }
    }
}

CountService 提供了这些接口a0,test1()等等
CountServiceImpl实现了这些接口,什么事情都没干,主要权衡函数调用的时间

实验结果

接口提供29个函数,实验对比

Benchmark Mode Cnt Score Units
DynamicProxyPerformanceTest.cglibProxy thrpt 5 7.385 ± 0.749 ops/s
DynamicProxyPerformanceTest.testjdkProxy thrpt 5 6.164 ± 0.711 ops/s

cglib比jdk动态代理快一点

接口保留19个函数

Benchmark Mode Cnt Score Units
DynamicProxyPerformanceTest.cglibProxy thrpt 5 15.126 ± 0.730 ops/s
DynamicProxyPerformanceTest.testjdkProxy thrpt 5 9.790 ± 1.008 ops/s

cglib是jdk动态代理1.5倍速度

接口保留49个函数,平均调用

Benchmark Mode Cnt Score Units
DynamicProxyPerformanceTest.cglibProxy thrpt 5 4.987 ± 0.859 ops/s
DynamicProxyPerformanceTest.testjdkProxy thrpt 5 3.826 ± 0.259 ops/s

接口保留49个函数,只调用清单上最后一个函数

Benchmark Mode Cnt Score Units
DynamicProxyPerformanceTest.cglibProxy thrpt 5 193.406 ± 6.209 ops/s
DynamicProxyPerformanceTest.testjdkProxy thrpt 5 347.192 ± 26.464 ops/s

这个时候,java利用反射,比cglib每次都switch到一个比较靠后case快

结论

1.同样情况下,cglib两种实现方式,invokeSuper + setSuperClass 永远比 invoke + setInterfaces慢
2.cglib invoke + setInterfaces 在方法数量较少的时候,在函数平均调用的情况下 比jdkProxy快,随着函数增多,优势越来越不明显,到达某个数量级一定比jdk动态代理慢
3.cglib invoke + setInterfaces 在调用特定函数(在switch中靠后的case) 会比jdk动态代理慢

思考

cglib的瓶颈在于:
调用net.sf.cglib.reflect.FastClass#invoke(int, java.lang.Object, java.lang.Object[])时需要switch的case:
如果有n个函数,那么就要一个个比较,复杂度O(n)
这里如果有一个key -> behavior的映射就好了,目前并没有。
如果可以用asm在这里写成一个二分搜索,cglib就会快多了,变成O(log2 n),时间上就是一个飞跃,只不过这个fastclass就会看起来很丑。(目前最新3.2.5的版本也没有改动这一块)

refer

http://www.cnblogs.com/haiq/p/4304615.html
http://adolphor.com/blog/2016/12/14/java-proxy-performance.html
https://zeroturnaround.com/rebellabs/testing-the-performance-of-4-java-runtime-code-generators-cglib-javassist-jdk-proxy-byte-buddy/
https://github.com/cglib/cglib/releases/tag/RELEASE_3_2_5
http://mvnrepository.com/artifact/cglib/cglib/3.2.5

代理
Web note ad 1