Cglib 및 JDK 동적 에이전트의 성능 비교

Cglib에서 만든 동적 에이전트의 운행 성능은 JDK 동적 에이전트보다 약 10배 높다고 하는데 오늘 의심의 정신으로 검증한 결과 상황이 다르다는 것을 발견하고 실험 결과를 붙여 참고와 토론을 제공한다.
코드는 매우 간단합니다. 우선 Test 인터페이스를 정의하고 TestImpl을 실현합니다.Test 인터페이스는 하나의 방법test만 정의하고 전송된 int 인자에 1을 더하면 되돌아옵니다.코드는 다음과 같습니다.
package my.test;



public interface Test {

    

    public int test(int i);

    

}

 
package my.test;



public class TestImpl implements Test{

    public int test(int i) {

        return i+1;

    }

}

 
 
그 다음에 세 가지 에이전트의 실현을 정의했다. 장식자 모드가 실현된 에이전트 (decorator), JDK 동적 에이전트 (dynamic proxy), Cglib 동적 에이전트 (cglib proxy) 이다.코드는 다음과 같습니다.
package my.test;



public class DecoratorTest implements Test{

    private Test target;

    

    public DecoratorTest(Test target) {

        this.target = target;

    }



    public int test(int i) {

        return target.test(i);

    }

}

 
package my.test;



import java.lang.reflect.InvocationHandler;

import java.lang.reflect.Method;

import java.lang.reflect.Proxy;



public class DynamicProxyTest implements InvocationHandler {

    private Test target;



    private DynamicProxyTest(Test target) {

        this.target = target;

    }



    public static Test newProxyInstance(Test target) {

        return (Test) Proxy

                .newProxyInstance(DynamicProxyTest.class.getClassLoader(),

                        new Class<?>[] { Test.class },

                        new DynamicProxyTest(target));



    }



    public Object invoke(Object proxy, Method method, Object[] args)

            throws Throwable {

        return method.invoke(target, args);

    }

}
package my.test;



import java.lang.reflect.Method;



import net.sf.cglib.proxy.Enhancer;

import net.sf.cglib.proxy.MethodInterceptor;

import net.sf.cglib.proxy.MethodProxy;



public class CglibProxyTest implements MethodInterceptor {

    

    private CglibProxyTest() {

    }

    

    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);

    }



}

 
 
TestImpl의 호출 소모 시간을 기준으로 다른 세 가지 에이전트를 통해 호출되는 소모 시간을 비교합니다.테스트 코드는 다음과 같습니다.
package my.test;



import java.util.LinkedHashMap;

import java.util.Map;



public class ProxyPerfTester {



    public static void main(String[] args) {

        //

        Test nativeTest = new TestImpl();

        Test decorator = new DecoratorTest(nativeTest);

        Test dynamicProxy = DynamicProxyTest.newProxyInstance(nativeTest);

        Test cglibProxy = CglibProxyTest.newProxyInstance(TestImpl.class);



        //

        int preRunCount = 10000;

        runWithoutMonitor(nativeTest, preRunCount);

        runWithoutMonitor(decorator, preRunCount);

        runWithoutMonitor(cglibProxy, preRunCount);

        runWithoutMonitor(dynamicProxy, preRunCount);

        

        //

        Map<String, Test> tests = new LinkedHashMap<String, Test>();

        tests.put("Native   ", nativeTest);

        tests.put("Decorator", decorator);

        tests.put("Dynamic  ", dynamicProxy);

        tests.put("Cglib    ", cglibProxy);

        int repeatCount = 3;

        int runCount = 1000000;

        runTest(repeatCount, runCount, tests);

        runCount = 50000000;

        runTest(repeatCount, runCount, tests);

    }

    

    private static void runTest(int repeatCount, int runCount, Map<String, Test> tests){

        System.out.println(String.format("
==================== run test : [repeatCount=%s] [runCount=%s] [java.version=%s] ====================", repeatCount, runCount, System.getProperty("java.version"))); for (int i = 0; i < repeatCount; i++) { System.out.println(String.format("
--------- test : [%s] ---------", (i+1))); for (String key : tests.keySet()) { runWithMonitor(tests.get(key), runCount, key); } } } private static void runWithoutMonitor(Test test, int runCount) { for (int i = 0; i < runCount; i++) { test.test(i); } } private static void runWithMonitor(Test test, int runCount, String tag) { long start = System.currentTimeMillis(); for (int i = 0; i < runCount; i++) { test.test(i); } long end = System.currentTimeMillis(); System.out.println("["+tag + "] Elapsed Time:" + (end-start) + "ms"); } }

 
 
테스트 용례는 각각 jdk6, jdk7, jdk8에서 테스트를 진행했고 매번 테스트는 각각 1000000과 50000000의 순환 횟수로 테스트 방법을 호출하고 3회 반복했다.
  • jdk6에서의 테스트 결과는 다음과 같다:
  • ==================== run test : [repeatCount=3] [runCount=1000000] [java.version=1.6.0_45] ====================
    
    
    
    --------- test : [1] ---------
    
    [Native   ] Elapsed Time:2ms
    
    [Decorator] Elapsed Time:12ms
    
    [Dynamic  ] Elapsed Time:31ms
    
    [Cglib    ] Elapsed Time:31ms
    
    
    
    --------- test : [2] ---------
    
    [Native   ] Elapsed Time:7ms
    
    [Decorator] Elapsed Time:7ms
    
    [Dynamic  ] Elapsed Time:31ms
    
    [Cglib    ] Elapsed Time:27ms
    
    
    
    --------- test : [3] ---------
    
    [Native   ] Elapsed Time:7ms
    
    [Decorator] Elapsed Time:6ms
    
    [Dynamic  ] Elapsed Time:23ms
    
    [Cglib    ] Elapsed Time:29ms
    
    
    
    ==================== run test : [repeatCount=3] [runCount=50000000] [java.version=1.6.0_45] ====================
    
    
    
    --------- test : [1] ---------
    
    [Native   ] Elapsed Time:212ms
    
    [Decorator] Elapsed Time:226ms
    
    [Dynamic  ] Elapsed Time:1054ms
    
    [Cglib    ] Elapsed Time:830ms
    
    
    
    --------- test : [2] ---------
    
    [Native   ] Elapsed Time:184ms
    
    [Decorator] Elapsed Time:222ms
    
    [Dynamic  ] Elapsed Time:1020ms
    
    [Cglib    ] Elapsed Time:826ms
    
    
    
    --------- test : [3] ---------
    
    [Native   ] Elapsed Time:184ms
    
    [Decorator] Elapsed Time:208ms
    
    [Dynamic  ] Elapsed Time:979ms
    
    [Cglib    ] Elapsed Time:832ms

     
    테스트 결과에 의하면 jdk6에서 운행 횟수가 비교적 적은 상황에서 jdk동적 에이전트와 cglib의 차이가 뚜렷하지 않고 심지어 더 빠르다.그러나 호출 횟수가 증가한 후에 cglib은 조금 더 빨리 나타났지만'약간'이 좋았을 뿐 10배 차이에 이르지 못했다.
  • jdk7에서의 테스트 결과는 다음과 같다:
  • ==================== run test : [repeatCount=3] [runCount=1000000] [java.version=1.7.0_60] ====================
    
    
    
    --------- test : [1] ---------
    
    [Native   ] Elapsed Time:2ms
    
    [Decorator] Elapsed Time:12ms
    
    [Dynamic  ] Elapsed Time:19ms
    
    [Cglib    ] Elapsed Time:26ms
    
    
    
    --------- test : [2] ---------
    
    [Native   ] Elapsed Time:3ms
    
    [Decorator] Elapsed Time:5ms
    
    [Dynamic  ] Elapsed Time:17ms
    
    [Cglib    ] Elapsed Time:20ms
    
    
    
    --------- test : [3] ---------
    
    [Native   ] Elapsed Time:4ms
    
    [Decorator] Elapsed Time:4ms
    
    [Dynamic  ] Elapsed Time:13ms
    
    [Cglib    ] Elapsed Time:27ms
    
    
    
    ==================== run test : [repeatCount=3] [runCount=50000000] [java.version=1.7.0_60] ====================
    
    
    
    --------- test : [1] ---------
    
    [Native   ] Elapsed Time:208ms
    
    [Decorator] Elapsed Time:210ms
    
    [Dynamic  ] Elapsed Time:551ms
    
    [Cglib    ] Elapsed Time:923ms
    
    
    
    --------- test : [2] ---------
    
    [Native   ] Elapsed Time:238ms
    
    [Decorator] Elapsed Time:210ms
    
    [Dynamic  ] Elapsed Time:483ms
    
    [Cglib    ] Elapsed Time:872ms
    
    
    
    --------- test : [3] ---------
    
    [Native   ] Elapsed Time:217ms
    
    [Decorator] Elapsed Time:208ms
    
    [Dynamic  ] Elapsed Time:494ms
    
    [Cglib    ] Elapsed Time:881ms

     
    테스트 결과:jdk7에서 상황이 역전되었습니다!운행 횟수가 비교적 적은 (10000000) 상황에서 jdk 동적 에이전트는 cglib보다 30% 정도 빠르지 않다.호출 횟수가 증가할 때(50000000) 동적 에이전트는 cglib보다 1배 가까이 빠르다.
    다음은 jdk8의 활약이 어떤지 다시 한 번 봅시다.
  • jdk8에서의 테스트 결과는 다음과 같다:
  • ==================== run test : [repeatCount=3] [runCount=1000000] [java.version=1.8.0_05] ====================
    
    
    
    --------- test : [1] ---------
    
    [Native   ] Elapsed Time:5ms
    
    [Decorator] Elapsed Time:11ms
    
    [Dynamic  ] Elapsed Time:27ms
    
    [Cglib    ] Elapsed Time:52ms
    
    
    
    --------- test : [2] ---------
    
    [Native   ] Elapsed Time:4ms
    
    [Decorator] Elapsed Time:6ms
    
    [Dynamic  ] Elapsed Time:11ms
    
    [Cglib    ] Elapsed Time:24ms
    
    
    
    --------- test : [3] ---------
    
    [Native   ] Elapsed Time:4ms
    
    [Decorator] Elapsed Time:5ms
    
    [Dynamic  ] Elapsed Time:9ms
    
    [Cglib    ] Elapsed Time:26ms
    
    
    
    ==================== run test : [repeatCount=3] [runCount=50000000] [java.version=1.8.0_05] ====================
    
    
    
    --------- test : [1] ---------
    
    [Native   ] Elapsed Time:194ms
    
    [Decorator] Elapsed Time:211ms
    
    [Dynamic  ] Elapsed Time:538ms
    
    [Cglib    ] Elapsed Time:965ms
    
    
    
    --------- test : [2] ---------
    
    [Native   ] Elapsed Time:194ms
    
    [Decorator] Elapsed Time:214ms
    
    [Dynamic  ] Elapsed Time:503ms
    
    [Cglib    ] Elapsed Time:969ms
    
    
    
    --------- test : [3] ---------
    
    [Native   ] Elapsed Time:190ms
    
    [Decorator] Elapsed Time:209ms
    
    [Dynamic  ] Elapsed Time:495ms
    
    [Cglib    ] Elapsed Time:939ms

     
    테스트 결과: jdk8하, JDK7하의 경천대역전이 이어졌다!그러나 또 다른 미세한 변화를 관찰했다. 절대치로 볼 때 cglib의 jdk8에서의 표현은 jdk7보다 조금 못한 것 같다. 비록 조금이지만 여러 번의 집행을 거쳤지만 여전히 이 추세이다(주: 이 추세의 결론은 엄격하지 않다. 단지 결론을 얻기 위해서는 더욱 다양한 비교 실험을 해야 한다).
     
    결론: jdk6에서 jdk7, jdk8까지 동적 에이전트의 성능이 현저하게 향상되었고 cglib의 표현은 따라가지 못했으며 심지어 약간 떨어질 수도 있다.소문에 의하면 cglib이 jdk 동적 에이전트보다 10배 높은 경우는 아마도 더 낮은 버전의 jdk에 나타날 것이다.
    이상의 테스트 용례는 간단하지만 jdk버전의 업그레이드는 일부 신기술의 변화를 가져올 수 있고 우리의 이전 경험을 무효화시킬 수 있음을 보여 준다.실제 업무 장면에 놓을 때 실제 상황에 따라 테스트를 한 후에야 장면에 특정된 결론을 얻을 수 있다.
    한 마디로 하면 실천에서 참된 지식이 나오고 시대와 함께 과거의 경험을 검토하고 갱신해야 한다.
     
    주: 상기 실험에서 cglib의 버전은 3.1입니다.

    좋은 웹페이지 즐겨찾기