Performance JDK dynamic proxy versus CGLIB proxy

We learned what proxies are used for. According to the GoF book, Design Patterns: Elements of Reusable Object-Oriented Software, a proxy is a placeholder for another object to control access to it. As the proxy lies in between the caller of an object and the real object, it can decide whether to prevent the invocation of the real (or target) object or perform some action before the target object is invoked.

Many object-relational mappers use proxy patterns to implement a behavior that prevents data from being loaded until it is actually needed. Sometimes this is called lazy loading. Spring also uses proxies to develop some of its functionality, such as its transaction management, security, caching, and the AOP framework.

As proxies objects are an additional object created at runtime, either by JDK proxy or CGLIB library, and sit between the caller object and the target object, it is going to add some overhead to a plain method invocation.

Let's find out how much overhead proxies add to a plain method invocation. 

The following snippet shows the Spring Java-based configuration class for the CGLIB proxy:

@EnableAspectJAutoProxy
@Configuration
public class CGLIBProxyAppConfig {

@Bean
@Scope(proxyMode=ScopedProxyMode.TARGET_CLASS)
public TransferService transferService(){
return new TransferServiceImpl();
}
}

The Spring Java-based configuration class for the JDK proxy is as follows:

@Configuration
@EnableAspectJAutoProxy
public class JDKProxyAppConfig {

@Bean
@Scope(proxyMode=ScopedProxyMode.INTERFACES)
public TransferService transferService(){
return new TransferServiceImpl();
}
}

The JUnit class is as follows:

public class TestSpringProxyOverhead {
private static final Logger LOGGER =
Logger.getLogger(TestSpringProxyOverhead.class);

@Test
public void checkProxyPerformance() {
int countofObjects = 3000;
TransferServiceImpl[] unproxiedClasses = new
TransferServiceImpl[countofObjects];
for (int i = 0; i < countofObjects; i++) {
unproxiedClasses[i] = new TransferServiceImpl();
}

TransferService[] cglibProxyClasses = new
TransferService[countofObjects];
TransferService transferService = null;
for (int i = 0; i < countofObjects; i++) {
transferService = new
AnnotationConfigApplicationContext(CGLIBProxyAppConfig.class)
.getBean(TransferService.class);
cglibProxyClasses[i] = transferService;
}

TransferService[] jdkProxyClasses = new
TransferService[countofObjects];
for (int i = 0; i < countofObjects; i++) {
transferService = new
AnnotationConfigApplicationContext(JDKProxyAppConfig.class)
.getBean(TransferService.class);
jdkProxyClasses[i] = transferService;
}

long timeTookForUnproxiedObjects =
invokeTargetObjects(countofObjects,
unproxiedClasses);
displayResults("Unproxied", timeTookForUnproxiedObjects);

long timeTookForJdkProxiedObjects =
invokeTargetObjects(countofObjects,
jdkProxyClasses);
displayResults("Proxy", timeTookForJdkProxiedObjects);

long timeTookForCglibProxiedObjects =
invokeTargetObjects(countofObjects,
cglibProxyClasses);
displayResults("cglib", timeTookForCglibProxiedObjects);

}

private void displayResults(String label, long timeTook) {
LOGGER.info(label + ": " + timeTook + "(ns) " + (timeTook / 1000000)
+ "(ms)");
}

private long invokeTargetObjects(int countofObjects,
TransferService[] classes) {
long start = System.nanoTime();
Account source = new Account(123456, "Account1");
Account dest = new Account(987654, "Account2");
for (int i = 0; i < countofObjects; i++) {
classes[i].transfer(source, dest, 100);
}
long end = System.nanoTime();
long execution = end - start;
return execution;
}
}

The overhead time varies based on hardware tools, such as CPU and memory. The following is the kind of output we would get:

2018-02-06 22:05:01 INFO TestSpringProxyOverhead:52 - Unproxied: 155897(ns) 0(ms)
2018-02-06 22:05:01 INFO TestSpringProxyOverhead:52 - Proxy: 23215161(ns) 23(ms)
2018-02-06 22:05:01 INFO TestSpringProxyOverhead:52 - cglib: 30276077(ns) 30(ms)

We can do benchmarking using tools such as Google's Caliper, found at https://github.com/google/caliper, or Java Microbenchmark Harness (JMH), found at http://openjdk.java.net/projects/code-tools/jmh/. Many performance tests, using different tools and scenarios, delivered different results. A few tests showed CGLIB is faster than the JDK proxy, and a few got other results. If we test AspectJ, which we'll discuss later in this chapter, performance is still better than the JDK proxy and CGLIB proxy, due to its bytecode-weaving mechanism instead of a proxy object. 

The question here is do we really need to worry about the overhead we saw? The answer is both yes and no. We will discuss both these answers.

We don't have to really worry about the overhead because that amount of time the proxy added is negligible and the amount of benefits provided by AOP or the proxy pattern is high. We already saw the benefits of AOP in the preceding sections of this chapter, such as transaction management, security, lazy loading, or anything that is crosscutting but with code simplification, centralized management, or maintenance of code. 

Also, we need to worry about the overhead when our application has Service Level Agreement (SLA) to deliver in milliseconds or our application has a very high volume of concurrent requests or loads. In this case, each millisecond spent is important for our application. However, we still need to use AOP in our application in order to implement crosscutting concerns. So, what we need to take care of here is the right AOP configuration, avoiding the unnecessary scanning of objects for advice, configuring the exact join point to which we want advice, and avoiding the implementation of fine-grained requirements through AOP. For fine-grained requirements user AspectJ (the byte-code-weaving approach).

So the rule of thumb is, use AOP to implement crosscutting concerns and leverage its benefits. However, implement it cautiously, and with the right configurations that do not degrade system performance, by applying advice or proxies to each and every operation.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset