20 September 2010

Part 1. "Какая гадость... Какая гадость эта ваша заливная рыба"

In past I spent some times on optimization of performance mostly JSE application. Recently I had a chance to play with enterprise Java.

Yes, they both are pretty same Java and life does not become heavier when you moving from Standard Edition land to Enterprise one. Except one note: the enterprise app supposed to be more complex so, is more "layered" application. Kind of "Shrek's onions":

- Ogres are like onions.
- They stink?
- Yes. No.
- Oh, they make you cry.
- No.
- Oh, you leave em out in the sun, they get all brown, start sproutin' little white hairs.
- No! Layers! Onions have layers. Ogres have layers. Onions have layers. You get it? We both have layers!
- Oh, you both have layers. Oh. You know, not everybody like onions.

Thats true, not everybody like ... layers. Especially when we talk about high performance, low-latency applications. Some people implied the Java to a heavy, multi-layered applications and blames it for so slow speed in comparison with C/C++. But, I think, that is unfairly...


In most cases, as far as I can see, there is used Spring as framework that binds components in application. Really, since birth, the Spring become something more than just a framework. I even failed to classify it. What is it really? Its definitely too big to be just a framework and, it seems like, it is still growing to become a real enterprise platform.

But that is not a matter of this story. I mentioned Spring because I was wondered how slow container managed transaction goes in one application that intensively used Spring. I was really wondered after profiling that transaction support is implemented by JDK Dynamic proxy. I thought, that explained my concerns: reflection never was a quick. Assuming that am not alone in making application as fast as it is possible, I begun to look the way how to switch to something more quick. I heard about different bytecode manipulation tools, so expect to see something that can achieve much better results.

But here the magic came. Yes, I found the way how to turn on the CGLib usage, but... people recommend do not use it even:


There's little performance difference between CGLIB proxying and dynamic proxies. As of Spring 1.0, dynamic proxies are slightly faster. However, this may change in the future. Performance should not be a decisive consideration in this case.

Never mind that reflection would be as quick as the direct invocation! That cancels everything I knew about reflection in SE. Surely Spring guys found the Holy Grail? Fortunately I met different opinions that calm down my doubts. There are couple posts that shares my concerns. The one of them, may be a bit clumsy, literally advised me to spend a bit more time on detail analysis. I drafted a small project to play with different proxy generation approaches to find out which difference is between CGILib and Dynamic proxy and what is really performs better. Eventually this experiment moved me outside the Spring implementation. Please find below results I got:


org.kot.aop.SpringProxyFactory: 104.19600 ns (104196000)
org.kot.aop.SpringOptProxyFactory: 88.48500 ns (88485000)
org.kot.aop.JDKProxyFactory: 19.45500 ns (19455000)
org.kot.aop.CGLibProxyFactory: 15.31600 ns (15316000)
org.kot.aop.proxy.ASMProxyFactory: 3.06900 ns (3069000)
org.kot.aop.NoProxyFactory: 3.32300 ns (3323000)


The org.kot.aop.SpringProxyFactory - the "classical" Spring proxy factory usage without any configuration:


final ProxyFactory factory = new ProxyFactory(target);
    factory.setInterfaces(...);
    factory.setTarget(...);

    factory.addAdvice(new SpringInterceptor());
    return factory.getProxy();
}


Where SpringInterceptor is a simple org.aopalliance.intercept.MethodInterceptor implementation. I tried to keep everything straight forward and follow to Spring documentation / tutorial I found about its AOP:


class SpringInterceptor implements MethodInterceptor {
    @SuppressWarnings({"FeatureEnvy"})
    public Object invoke(MethodInvocation methodInvocation) throws Throwable {
        try {
            interceptor.before();
            final Object returnValue = methodInvocation.proceed();
            interceptor.after();
            return returnValue;
        } catch (Throwable e) {
            interceptor.onFailure(e);
            throw e;
        }
    }
}


The org.kot.aop.SpringOptProxyFactory sets the optimize and proxyTargetClass properties (according Spring sources, has to be set only one, another is just control in head) that enforces Spring to use CGLib bytecode generator instead of Dynamic proxy:


final ProxyFactory factory = new ProxyFactory(target);
    factory.setInterfaces(...);
    factory.setTarget(...);

    factory.setOptimize(true);
    factory.setProxyTargetClass(true);

    factory.addAdvice(new SpringInterceptor());
    return factory.getProxy();
}


After execution I was not impressed by result so, decided continue with Dynamic proxy and CGLib directly. Additional reason to do so was numbers I got for a direct invocation (i.e. org.kot.aop.NoProxyFactory):


public Object createProxy(final Object target) {
    final Subject t = (Subject) target;
    return new SubjectImplProxy(t, this.interceptor);
}

static class SubjectImplProxy implements Subject {
    private final Subject target;
    private final InvocationInterceptor interceptor;

    public SubjectImplProxy(final Subject target, final InvocationInterceptor interceptor) {
        this.target = target;
        this.interceptor = interceptor;
    }

    public String getData() {
        try {
            interceptor.before();
            final String returnValue = target.getData();
            interceptor.after();
            return returnValue;
        } catch (Throwable e) {
            interceptor.onFailure(e);
            throw new UndeclaredThrowableException(e);
        }
    }

    public void setData(final String data) {
        try {
            interceptor.before();
            target.setData(data);
            interceptor.after();
        } catch (Throwable e) {
            interceptor.onFailure(e);
            throw new UndeclaredThrowableException(e);
        }
    }
}


The difference was in 30 times thats has no any excuses even for reflection. So far, I decided implement my own proxy factories that uses both: the JDK Dynamic proxy:


public Object createProxy(final Object target) {
    return Proxy.newProxyInstance(
            target.getClass().getClassLoader(),
            interfaces,
            new ReflectiveInvocationHandler(target)
    );
}

class ReflectiveInvocationHandler implements InvocationHandler {
    private final Object target;

    public ReflectiveInvocationHandler(final Object target) {
        this.target = target;
    }

    @SuppressWarnings({"FeatureEnvy"})
    public Object invoke(final Object proxy, final Method method, final Object[] arguments) throws Throwable {
        try {
            interceptor.before();
            Object returnValue = method.invoke(target, arguments);
            interceptor.after();
            if (target == returnValue) {
                return proxy;
            }
            return returnValue;
        } catch (Throwable t) {
            interceptor.onFailure(t);
            throw t;
        }
    }
}


and CGLib's Enhancer:


public Object createProxy(final Object target) {
    final Enhancer enhancer = new Enhancer();
    enhancer.setSuperclass(target.getClass());
    enhancer.setCallbacks(new Callback[] {new CGLibInterceptor(target)});

    return enhancer.create();
}

class CGLibInterceptor implements MethodInterceptor {
    private final Object target;

    public CGLibInterceptor(final Object target) {
        this.target = target;
    }

    @SuppressWarnings({"FeatureEnvy"})
    public Object intercept(Object object, Method method, Object[] arguments, MethodProxy methodProxy) throws Throwable {
        try {
            interceptor.before();
            Object returnValue = methodProxy.invoke(target, arguments);
            interceptor.after();
            if (target == returnValue) {
                return object;
            }
            return returnValue;
        } catch (Throwable t) {
            interceptor.onFailure(t);
            throw t;
        }
    }
}


Result exceeds all my expectations: they doing in 5 times quicker.


Even here we can stop. The reason why my application is so slow is not a "layers". And that is definitely not a Java fault. That is just a silly an over engineered Spring implementation.


But I decided to continue. Tempted reader could note, there is still reflection used. Really how else to explain that proxy, generated by CGLib's Enhancer, is in 5 times slower then explicit target invocation? I think the net.sf.cglib.proxy.MethodInterceptor is not a best way to do a target invocation. Isn't it? After trying to find something instead it, I decided implement my own proxy generator. My first choice was a pretty small bytecode generator - ASM. Finally, it gives approximately same time as explicit invocation.


So, whats wrong with this pice of shit Spring? Why it is in 30 times slower than direct target invocation? What it is doing there? I have no clue, really. And have no any interest to find out. I can remember earlier 00th when Mr. Rod Johnson blames JEE (J2EE then) for its complexity and paints Spring as its alternative. Is that what he meant originally?


P.S.That experiment made with Spring 2.5.6. With the latest Spring (at moment of writing this post, 3.0.4) the picture looks even worse:


org.kot.aop.SpringProxyFactory: 101.28300 ns (101283000)
org.kot.aop.SpringOptProxyFactory: 105.60000 ns (105600000)
org.kot.aop.JDKProxyFactory: 16.36500 ns (16365000)
org.kot.aop.CGLibProxyFactory: 15.34800 ns (15348000)
org.kot.aop.proxy.ASMProxyFactory: 3.27800 ns (3278000)
org.kot.aop.NoProxyFactory: 3.36100 ns (3361000)

Look, they do a great job since 2.5.6. The time spent on proxied method invocation with "optimize" property turned on increased on 19%. Althoug thats can explain why they aware on using the CGLib.

No comments: