JSF Central - Speed up your Data-Driven JSF/Seam Application by Two Orders of Magnitude - Part 1
JSF Central

 
 Home 
 
 Products 
 
 Articles & Books 
 
 Resources 
Speed up your Data-Driven JSF/Seam Application by Two Orders of Magnitude
 
Speed up your Data-Driven JSF/Seam Application by Two Orders of Magnitude - Part 1
(continued)
by Dan Allen
05 Feb 2009 03:00 EST

In the first of this two-part article, Dan Allen discusses some common performance problems you may encounter when using JSF components, Seam components, and the EL. You'll learn about the set of best practices for eliminating them that led to an improvement of two orders of magnitude in the performance of his application.


The performance roadblock

Development was going smoothly until my colleague noticed something peculiar about the performance. Five or ten records on the page took a reasonable amount of time to render, but when that number went up to 50 or 100 records, the performance of the page plummeted. It turns out that the degradation was linear, but the slope was very steep. The page with 100 records was taking over 15 seconds to render. Obviously, that just wasn't going to fly. And so our optimization work began. Could we find the bottleneck and how low could we go?

As we optimized, we first looked at the most basic page and established a performance baseline. Then we added additional components to the page and tried to identify which ones were contributing to the major slowdown. As it turns out, in a Seam application, the first area to place your focus is on page actions, which are methods that execute before the page begins to render.

Page actions and component initialization

When a page is taking 15 seconds to render, there is likely a single culprit that is chewing up a bulk of that time. To establish a baseline, and to make sure I was focusing on the right problem, I first stripped everything from the page and requested it. The response took a couple of seconds to come back. This had me puzzled for a moment. I soon realized that that a Seam page action was registered with the page (i.e., view ID). A page action is a method-binding expression that is assigned to a JSF view ID (or a group of view IDs if a wildcard is used) in a Seam page descriptor and is evaluated by Seam just before the view ID is rendered by the JSF view handler. Here's the expression that was registered with the view ID of the results page.

<action execute="#{benthicMsmntEditor.prepareResults}" />

The page action is there to eagerly fetch the results that are to be displayed on the page. However, the query in that method was executing in about a tenth of a second. So that wasn't the problem. After studying the code a bit longer, I recognized that the problem was not in the page action method, but rather the @Create method of the component being invoked. The @Create method is a synonym to the standard @PostConstruct method in Java EE and marks a method to be evaluated immediately after a component is instantiated.

Inside the @Create method was a handful of queries that retrieved more than 10,000 records of reference data. This data is used by select menus in various forms on the page, but those forms are all being conditionally rendered. So basically, we were charging the user a toll to enter the page with a chance that that reference data would never be referenced. That brings us to lesson #1.

Lesson #1: Don't make the user pay an upfront fee to view a page. Defer logic where possible.

Since the forms are rendered conditionally, and some via Ajax, the reference data can be retrieved at the same time the forms are activated. If you must display a form unconditionally, think about the most efficient way to prepare the data (perhaps using a cache). It's also preferable to use Ajax-based autocomplete rather than select menus with a large list of options, since making this switch can drastically reduce the speed of the initial rendering of the form. The user will likely be more patient when working on the field with autocomplete, and you can even keep the number of options delivered to a minimum as the user types.

With the toll skimmed off the top, we could get back to the performance of the elements on the page. Bringing back the page piece-by-piece, I determined that the next big time hog was in fact the data table. Again, I stripped out elements in the data table until I pinned down what was causing the problem. As it turns out, it was the expressions in the rendered attributes that I was using to hide or show various components in the table.

The cost of conditional rendering

In each row there are 6 "editable" columns, each containing an output and an input component and 4 icons for controlling editing (edit, delete, approve, cancel). In total, there are 16 uses of the rendered attribute appearing in each row. (Initially I had a couple columns with multiple input components, which I realized I needed to group within a panel group [i.e., <h:panelGroup>] so that the rendered attribute was only applied once).

As you know, logic that occurs in a single row is multiplied by the number of rows in the table. In a table with 100 rows, there are 1600 uses of the rendered attribute! But wait, there's even more! The rendered attribute is a nasty beast in JSF because it's often evaluated multiple times as the UI component tree is assembled. In fact, during the render response phase, it's resolved 3 or 4 times per component in a data table. What that means is that for our data table, the conditional rendering logic we are using is executed 5200 times for 100 rows! Whatever that logic is better be darn efficient or else it will have a huge impact on performance.

Warning: After hearing the bad news about how the rendered expression is abused by JSF, you might be inclined to use the <c:if> tag from Facelets. This tag emulates the behavior of the equivalently named tag from the JSTL tag library. You have to be careful with this tag, though, because it's not a true JSF component. It's processed by the Facelets compiler prior to building the UI component tree and can thus exclude a region of the markup from contributing to the UI component tree for that view. The benefit of this tag is that it can reduce the size of the component tree when you know that certain parts of the page aren't needed. However, the conditional rendering that you expect to happen on a postback does not, because at that point, the tree is already built and Facelets does not reprocess the <c:if> statements.

As it turns out, we were not being very efficient. Let's take a look at one of the columns of the data table in the /BenthicMeasurementList.xhtml view template:

<h:column> <f:facet name="header">Taxonomic Unit</f:facet> <h:outputText value="#{_item.taxon.name}" rendered="#{!benthicMsmntEditor.editing(_item)}"/> <h:selectOneMenu id="taxonomicUnit" rendered="#{benthicMsmntEditor.editing(_item)}" defaultLabel="No value set" value="#{_item.taxon}"> <s:selectItems value="#{taxonValues}" var="_taxon" label="#{_taxon.name}" noSelectionLabel="No value set"/> <s:convertEntity/> </h:selectOneMenu> </h:column>

As you can see here, I am calling the editing() method on a Seam component named benthicMsmntEditor to test whether the current row is in edit mode. We can pass the iteration variable, _item, to the method because Seam integrates with the JBoss EL, which introduces parameterized method calls to the Unified EL. The editing() method performs an identity check between the row data and the selected row.

public boolean editing(T itemInRow) { return itemInEditMode == itemInRow; }

Here we are only allowing one row to be in edit mode at a time, but this logic could easily be enhanced to support editing multiple rows simultaneously.

So where's the bottleneck? Initially, you may be inclined to point the finger at the EL or Java reflection. I did some testing and determined that the EL is surprisingly fast and Java reflection is equally optimized. And if you are inclined to believe that the slowness is caused by the parameterized method call, I'll inform you that comparing the current item using an EL operator to the item in edit mode retrieved using the JavaBean style accessor yields the same timing results:

rendered="#{_item == benthicMsmntEditor.itemInEditMode}"

The culprit is that the editing() method resides on a Seam component and each method call to a Seam component passes through a stack of interceptors, unless otherwise skipped by the presence of the @BypassInterceptors annotation at the component or method level.

Resources



RSS feed(all feeds)

The Editor's Desk
Podcasts
Inside Facelets
In the Trenches

Site version 1.83  Report web site problems

Copyright (C) 2003-2015 Virtua, Inc. All Rights Reserved. Java, JavaServer Faces, and all Java-based marks are trademarks or registered trademarks of Oracle Corporation. in the United States and other countries. Virtua, Inc. is independent of Oracle Corporation. All other trademarks are the sole property of their respective owners.