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

说明

这里将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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,847评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,208评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,587评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,942评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,332评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,587评论 1 218
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,853评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,568评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,273评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,542评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,033评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,373评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,031评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,073评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,830评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,628评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,537评论 2 269

推荐阅读更多精彩内容