Tomcat/Jasper Performance Tuning

JavaServerPages (JSP) is productivity-enhancing technology for building dynamic web pages. With more than 10 years of history, it is popular and mature. JSP 2.0 includes new features that make the code cleaner, such as the Expression Language (EL), the tag file, and simple tags. However, these new features can cause some performance issues.

eBay uses JSP technology to build web pages. More specifically, we use Geronimo 3 as the web container and Geronimo’s JSP engine, Tomcat/Jasper. To handle eBay’s huge volume of requests, the JSP engine must be powerful and must provide exceptionally high performance. Because Tomcat/Jasper shows some weakness in performance when using hundreds of EL expressions and tags, we have focused much effort on profiling and optimizing Jasper to make JSP more efficient. Most of the performance solutions and patches that we have submitted to the Tomcat community have been accepted. As a result, Tomcat users will soon be able to benefit from our work.

This blog post discusses the main issues we have identified and addressed.

EL resolving

When a JSP page includes a large number of EL expressions, EL resolving becomes a bottleneck for page rendering. The following JProfiler screen shot shows a case where 40% of the Java processing time is for EL resolving; the whole JSP page costs 1,865 ms, while the EL resolving costs 722 ms.

This profiling enables us to identify the heavy methods from Jasper:

 

Looking into the code, we found that every expression node needs to call CompositeELResolver.getValue when trying to resolve the value. The bottleneck is that CompositeELResolver contains at least seven EL resolvers:  ImplicitObjectELResolver, MapELResolver, ResourceBundleELResolver, ListELResolver, ArrayELResolver, BeanELResolver, and ScopedAttributeELResolver. The method getValue tries to return the value from each of these resolvers one by one, until one of them returns a not-null value. This logic results in many unnecessary resolver calls.

We proposed an enhancement:  a customized CompositedELResolver in Jasper that avoids many unused resolver calls. This change, accepted in Tomcat 7.0.33, saves 10% of the EL resolving time. The Bugzilla URL for this issue is https://issues.apache.org/bugzilla/show_bug.cgi?id=53896 (The issue was filed by the Geronimo team, who tried to resolve the issue for us).

We’ve filed some related Tomcat bugs as well, such as Bug 53867 (also accepted in Tomcat 7.0.33) and Bug 53869.

JSP compilation

JSP compilation is a very heavy process that includes JSP reading, JSP parsing, Java generation and compilation, and class loading. We have achieved many optimizations in this logic as well.

JspReader was the focus of our initial performance-tuning work related to compilation. JspReader.mark is called too many times during JSP and tag files compilation. The following screen shot illustrates the excessive number of JspReader.mark invocations and the time they consume.

 

By using the JspReader buffer to reduce the number of invocations, JSP reading performance can improve fourfold. Tomcat 7.0.31 includes this enhancement; for more details, see https://issues.apache.org/bugzilla/show_bug.cgi?id=53713.

EL code generation

Even with multiple optimizations, EL evaluation is still a big hotspot. It is not only time-intensive but also CPU-expensive. EL resolving is the root cause. Therefore, if we can bypass EL resolving in some cases, we can improve performance.

The idea is to do code generation for simple EL expressions, such as ${model}, ${model.test}, ${empty model}, and ${model.value++}. Such expressions can be interpreted as simple Java code. We have identified three kinds of EL expressions that can use code generation:

  • Simple EL expression containing only one part of the expression — for example, ${model}. Here is an example of the generated code for this kind of expression :
this.getJspContext().findAttribute("model")
  • EL expression with two nodes where the type of the first node is specified by a tag file attribute—for example, ${model.location}, where model is specified by the following directive in the tag file:

   Here is the generated code for this example:

(getModel() != null ? getModel().getLocation() : null)
  • EL expression with logic or arithmetic where the value part can be generated. For example:
${(intlExpansion eq 'true' && not empty model.location) || sortType==7}

   The generated code for this example looks like this:

(org.apache.jasper.runtime.ELRuntimeUtil.equals(getIntlExpansion(), "true")&&(! org.apache.jasper.runtime.ELRuntimeUtil.isEmpty((getModel() != null ? getModel().getLocation() : null))))

We have proposed an extensible ELInterpreter solution to Tomcat and implemented an interpreter for these three kinds of EL expressions. After the extensible interpreter has been accepted, we will contribute the code to Tomcat as well. This customized interpreter improves total page performance by 10%. Application developers will be able to inject their own interpreter to replace the default JSPUtil.interpreterCall.

For more about this enhancement, see https://issues.apache.org/bugzilla/show_bug.cgi?id=54239

JSTL code generation

Tag library performance is another hotspot for page rendering. Among the problems are the numerous method calls in the standard generated code and the high number of context switches. Every tag needs to call doStartTag and doEndTag.

Jasper has a code-generation solution for JSTL, which is based on the TagPlugin interface. Although eBay leverages the implementations in Jasper, there are many bugs. Here are some of the fixes we’ve contributed:

Issue

Bug

Status

Fixed version

Bug in JSTL tagPlugin “Out”

Bug 54011

Fixed

7.0.33

Bug in JSTL tagPlugin “Set”

Bug 54012

Fixed

7.0.33

Configurable tagPlugins.xml

Bug 54240

In-Progress

 

NPE in BodyContentImpl

Bug 54241

In-Progress

 

Bug in ForEach

Bug 54242

In-Progress

 

Bug in When and Choose

Bug 54314

In-Progress

 

Summary

Tomcat/Jasper is eBay’s chosen JSP solution. However, few solutions can fully satisfy eBay’s daunting performance requirements, and Tomcat/Jasper is no exception. Our efforts have identified four major performance issues:  EL resolution, JSP compilation, EL code generation, and JSTL code generation. Working with the Tomcat community, we’ve made substantial progress on some of these issues, and more enhancements and bug fixes are underway.