Skip to content

Defining template engine performance

pyykkis edited this page May 25, 2012 · 50 revisions

Context is everything

To measure a performance, one should first define the task. After that, the performance over that specific task can be measured. Clearly, any tool used for the task should produce the same end results, only with different performance.

Regarding template engines, there's no general 'performance' to be measured. It's either client-side performance or server-side performance.

At the server-side, the end result is a HTML string with rendered data values, e.g., something that Jade, Handlebars or numerous other string-based template engines provide. After rendering, the HTML string is sent to the browser via HTTP.

At the client-side, HTML string provided by a template engine is just a part of the story. The goal at the client-side is to show the results to the user, i.e., visible DOM elements with the data. For string-based template engines, it means that the result string needs to be set to .innerHTML of the parent element. That's a significant part of the measurement, which is often forgotten.

This article focuses on client-side performance, as that is the primary target for Transparency.

How browsers work from template engine point of view?

For string-based template engines, there's more or less just one relevant interaction with the browser. That is, assigning the HTML snippet to .innerHTML of the parent element. At that point, the browser discards all the existing child nodes, parses the HTML snippet to DOM elements and appends them as child nodes to the parent element. Naturally, if the parent element is visible, the operation also triggers reflow calculations.

With string-based templates, parsing HTML string to DOM objects with .innerHTML takes 80% to 90% of the total rendering time. As template engines can't optimize that, it really doesn't matter much which is faster, Mustache, Handlebars or Hogan, if you use them on the client-side. Even though one might be faster in generating HTML strings, overall they have about the same performance.

With DOM-based template engines, there's many interactions with the DOM API. Identifying the costly ones opens doors for optimizations. At least following actions have significant cost

  • Manipulating visible elements (triggers reflow calculations)
  • Reading and writing .innerHTML, .className or other attributes, even if the elements are detached from DOM
  • Creating and removing DOM elements, even if the elements are detached from DOM
  • Running document.getElementsByTagName and similar queries

Naturally, performance characteristics varies between browser implementations. From the template engine point of view, main browser differences in the test cases can be explained by

  • The speed and optimization strategies of the JIT engine
  • The speed and optimization strategies of the regular expression engine
  • The speed and optimization strategies of the DOM implementation

Optimization techniques employed in Transparency

Terminology

  • Context: The target node for .render call, e.g., $('#template').render(data)
  • Data: The data to be rendered. It might be a single model or list of models.
  • Model: Plain javascript object, i.e., key-value pairs. Keys are used to match values to the template elements.
  • Template: Original child nodes of the context. Never used for actual rendering.
  • Template instance: A cloned template with rendered model in place. The number of instances matches to the number of models.
  • Instance cache: Instances which are not in use at the moment.
  • Query cache: Instance specific cache for queries like template.querySelectorAll(key).

Techniques

Minimize manipulating of the visible DOM elements. By far the easiest optimization. Transparency implements this by detaching the context node at the before any manipulations and attaching it back to its place after all the manipulations have been done. This way, only two reflow calculations are triggered, even rendering when rendering a list of hundreds of models.

Create new DOM elements only if needed and keep them forever. Let's imagine we've a to-do application with ten items on the list. You complete most of them and add a few more. Under the hood, Transparency detaches the template instances (i.e. list elements) from DOM as you complete the tasks. However, it keeps the unused instances in the instance cache. When you add new items, Transparency first uses template instances in instance cache. Only if there's not enough instances in the cache, new instances are created by cloning the template elements.

Cache query selector results. Executing query selectors like template.querySelectorAll(key) is potentially expensive. So, Transparency saves the results to a query cache. The query itself, as well as the cache, is specific to a given template instance. This enables Transparency to render a list of models without template instances interfering each other. The query cache is initialized when the instance is used for the first time, which means that the existing template instances are faster to use than new ones. This is an important reason to keep and reuse instances until the end of world.

Update element attributes only if needed. Finally, getting and setting text, html or any attributes on DOM elements has a non-trivial cost, which is significantly larger than getting or setting a value in a plain javascript object. For that reason, Transparency saves the data values to its own data structures, remotely similar to jQuery.data() implementation. If there's no changes in the data between .render(data) calls, Trasparency doesn't even touch the DOM elements.

Show me the money

Below is a collection of client-side template performance tests on jsperf.com. You can repeat the results in your own browser by clicking on of the links below. Related result graphs were updated 2012-05.

Template language shootoff - DOM vs. innerHTML

http://jsperf.com/dom-vs-innerhtml-based-templating/366

Note: Because Transparency is so fast we have a scale problem on the diagram below.

xxx

Transparency vs. Handlebars finite lists

http://jsperf.com/transparency-vs-handlebars-finite-list/8

xxx

Transparency vs. Handlebars infinite lists

http://jsperf.com/transparency-vs-handlebars-infinite-list/7

xxx