FreshMarker – compile it!

I’m a shooting star leaping through the sky like a tiger
Defying the laws of gravity
I’m a racing car, passing by like Lady Godiva
I’m gonna go, go, go, there’s no stopping me

Don’t Stop Me Now – Queen

The template engine FreshMarker occupies a middle position in terms of processing speed for the benchmark displayed on the FreshMarker project page. In line with the concept of a dynamic, expandable engine, a noticeable acceleration of processing is not to be expected. The question therefore arises as to which concept can be thrown overboard in order to significantly increase speed.

An obvious feature of FreshMarker is the dynamic loading of templates from textual template descriptions. Normally the content is read from a text file, but can of course also come from a database or other sources. This text file is interpreted by the parser and converted into a data structure. This data structure is then interpreted together with a data model to generate textual content.

This concept is helpful if the template to be used is variable in some way. But what about when a static template is used in the application? For example, as a template for an EnumConverter class or for an HTML page in a benchmark? In this case, it may make sense to use a template that has been specially optimized for this application.

The first obvious optimization would be the elimination of the parser and the loading routines for the template. Regardless of the actual solution, the template could be instantiated directly as part of the application. Another optimization would be to formulate the directives directly in the Java code. This means formulating the if directive with an if statement and a list directive with an enhanced for loop. This eliminates a lot of overhead, in particular the need to manage a variable scope. This task is then taken over by the Java code.

The following example shows an excerpt from the HTML template of the Java template engine benchmark I use.

<#list items as item with looper>
  <tr class="${looper?item_parity}">
    <td>${looper?counter?c}</td>
    <td><a href="/stocks/${item.symbol}">${item.symbol}</a></td>
    <td><a href="${item.url}">${item.name}</a></td>
    <td><strong>${item.price?c}</strong></td><#if (item.change < 0.0)>
    <td class="minus">${item.change?c}</td>
    <td class="minus">${item.ratio?c}</td><#else>
    <td>${item.change?c}</td>
    <td>${item.ratio?c}</td></#if>
  </tr>
</#list>

A list directive, an if directive and several interpolations are used here. A prototypical, semi-automatic conversion of a FreshMarker template into Java code produces the following class.

private final static class ExampleTemplate {
  public String process(Map<String, Object> model) {
    StringWriter writer = new StringWriter();
    // ...
    int looper = 0;
    for (Object item : (List<Object>)model.get("items")) {
      writer.write("        <tr class=\"");
      writer.write(item_parity(looper));
      writer.write("\">\n          <td>");
      writer.write(c(counter(looper)));
      writer.write("</td>\n            <td><a href=\"/stocks/");
      writer.write(String.valueOf(dotkey(item, "symbol")));
      writer.write("\">");
      writer.write(String.valueOf(dotkey(item, "symbol")));
      writer.write("</a></td>\n            <td><a href=\"");
      writer.write(String.valueOf(dotkey(item, "url")));
      writer.write("\">");
      writer.write(String.valueOf(dotkey(item, "name")));
      writer.write("</a></td>\n            <td><strong>");
      writer.write(c(dotkey(item, "price")));
      writer.write("</strong></td>");
      if ((Double)dotkey(item, "change") < 0.0) {
        writer.write("\n             <td class=\"minus\">");
        writer.write(c(dotkey(item, "change")));
        writer.write("</td>\n            <td class=\"minus\">");
        writer.write(c(dotkey(item, "ratio")));
        writer.write("</td>");
      } else {
        writer.write("\n             <td>");
        writer.write(c(dotkey(item, "change")));
        writer.write("</td>\n            <td>");
        writer.write(c(dotkey(item, "ratio")));
        writer.write("</td>");
      }
      writer.write("\n          </tr>\n");
    }
    // ...
    return writer.toString();
  }

  private String item_parity(int looper) {
    return looper % 2 == 0 ? "even" : "odd";
  }

  private int counter(int looper) {
    return looper + 1;
  }
  
  Object dotkey(Object item, String key) {
    try {
      return switch (key) {
        case "change" -> item.getClass().getMethod("getChange").invoke(item);
        case "ratio" -> item.getClass().getMethod("getRatio").invoke(item);
        case "symbol" -> item.getClass().getMethod("getSymbol").invoke(item);
        case "name" -> item.getClass().getMethod("getName").invoke(item);
        case "url" -> item.getClass().getMethod("getUrl").invoke(item);
        default -> null;
      };
    } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
      throw new RuntimeException(e);
    }
  }
 
  public String c(Object value) {
    return String.valueOf(value);
  }
} 

In the process method, the constant parts of the template are written directly to the output as a string using writer.write. The for directive has become an enhanced for loop, which gets its list as List<Object> from the model. The various built-ins are methods in the class and the looper variable is represented here by an int variable.

If this template is used in the benchmark, the performance on the test computer increases from ~38000 ops/s to over 90000 ops/s. A very considerable increase. There is still a long way to go before static templates can be used in FreshMarker, but this was a first successful step.

1 thought on “FreshMarker – compile it!”

Leave a Comment