Real life Jakarta Velocity – simple optimizations

Here's another nice “real life” story I'd like to share, and this time is about Velocity. Nothing nasty, just a quick check of if and how well Velocity caching is performing, why and how to make your own Velocity ResourceLoader.

The guys I'm working with are migrating a rather huge ERP app from mainframe to Java multi-tier. This isn't exactly an quick and easy job, so basically my first step here is to find out a lot of things about the old system (via some boring but extremely necessary training). However, in order not to lose my so-called “Java skills”, I am also performing some tasks, mainly testing and optimization stuff, preparing the baby to face the harsh real world.

If you haven't seen an ERP tailored for production sites before, you'll be amazed at the massive number of barcode stickers which have to be printed. They are everywhere, from production to distribution, relaying boxes, smaller boxes, bigger boxes, packs, containers, everything your mind can think of.

These barcodes are produced on special printers which are usually connected to the production systems via serial port (IP connection is possible, but quite expensive so it's used only in very special cases, like really large warehouses).

Barcode image

Then, there are these rather thick clients (SWT) deployed at different points in the production/packaging/distribution workflow. Each one has its particularities, however they ALL have to print barcodes, and print them FAST.

This barcode stuff is not as simple as you might think. Depending on the specific point in the workflow, a different barcode must be printed, containg different data or maybe similar data but in other printable formats. This is a perfect fit for a tool such as Velocity.

The main issue here is that the templates are not in the filesystem, but they are extracted from a central database, where they are stored and managed by specific tools (an IDE-like tool is used to position different barcode elements on the printed stickers). The first [and easiest] solution was to use the Velocity.evaluate() function. This one-liner worked just fine until the performance test, where it was decided that the barcode generation is slow. It wasn't apparent at first, but you see – in packaging half a second is a pretty long time and the cummulated delays might make the customer lose some serious money at the end of the day.

The first idea was to look for a way of using Velocity's ResourceLoader, thus being able to forget the usage of evaluate() and use the classic VelocityEngine-Context-Template-merge mantra #:

import;import;import org.apache.commons.collections.ExtendedProperties;import org.apache.velocity.exception.ResourceNotFoundException;import org.apache.velocity.runtime.resource.Resource;import org.apache.velocity.runtime.resource.loader.ResourceLoader;public class MyResourceLoader extends ResourceLoader { public void init(ExtendedProperties arg0) { // TODO Auto-generated method stub } public InputStream getResourceStream(String templateName) throws ResourceNotFoundException { //TODO: exceptions here InputStream in= new ByteArrayInputStream( (TemplateContentsSingleton.getUniqueInstance().getTemplate(templateName)).getBytes()); if (in == null) { String msg= "*** BuiltInTemplateResourceLoader Error: cannot find resource " + templateName; throw new ResourceNotFoundException(msg); } return in; }[...]}

This is pretty “spike-ish” and completely non-thread-safe code, kids don't try this at home without correctly processing all errors and managing modification flags. Basically, the loader is using a TemplateContentsSingleton class that wraps inside it a hashmap of templates, indexed by their key (which by the way is a String, and that's just about perfect).

public class TemplateContentsSingleton { / unique instance */ private static TemplateContentsSingleton sInstance= null; / template containers / private Map tmplContainers= new HashMap(); / * Private constuctor */ private TemplateContentsSingleton() { super(); } / * Get the unique instance of this class. / public static TemplateContentsSingleton getUniqueInstance() { if (sInstance == null) { sInstance= new TemplateContentsSingleton(); } return sInstance; } public void setTemplate(String key, String templateContent) { this.tmplContainers.put(key, templateContent); } public String getTemplate(String templateName) { return (String) this.tmplContainers.get(templateName); }}

For your extreme comfort, this is an ultra-classic singleton contaning a hashmap.

Don't forget to initialize Velocity with the corresponding properties :

Properties veloProps= new Properties();veloProps.setProperty("resource.loader", "custom");veloProps.setProperty("custom.resource.loader.description","Customized Velocity Template Resource Loader");veloProps.setProperty("custom.resource.loader.class", MyResourceLoader.class.getName());veloProps.setProperty("custom.resource.loader.path", "");veloProps.setProperty("custom.resource.loader.cache", "false");

Finally we'll be able to test the effect of caching different objects. Obvious candidates for caching are the template and the context. We'll render one of the (pretty small) templates 1000 times, then compute the mean rendering time. We'll do the benchmarking with Velocity caching disabled and enabled (by setting custom.resource.loader.cache to “true”). OK, let's get to work :

timing graph for 100

We see clearly that there is basically no performance difference between Velocity caching and “hand-picked” objects caching. However, wrapping String templates inside a Velocity ResourceLoader gives us an important speed improvement in template rendering, varying from a 10x (cache off) to 4x (cache on) factor. Interesting and rather unexpected here is that even with plain simple Velocity.evaluate() – Velocity caching decreases the merging time (probably Context caching). Meaning that a simple property set could speed up the barcode generation dividing by 2 the time necessary for the merge. Sometimes it really pays off to read the documentation.

In conclusion, use a custom ResourceLoader and don't forget to enable caching for Velocity maximum performance. Well, this is nice but simple; right ? Something crunchier probably in the next episode …

# Syntax coloring graciously provided by Codepaste