Day 8: Raku web templating engines: boost up the parsing performance

Modern Raku web templating engines

A templating engine basically provides tools for effective metadata interpolation inside static files (templates). At web application runtime, the engine parses and replaces variables with actual content values. Finally client gets a HTML page generated from the template, where all metadata (variables, statements, expressions) has been processed.

Raku ecosystem has a few modern templating engines: Template::Mojo (last commit on 12 Jun 2017), Template::Mustache (last commit on 25 Jul 2020 — it’s alive!), Template6 (last commit on 20 Nov 2020 – active maintenance), Template::Classic (last commit on 11 Apr 2020), Template::Toolkit (by @DrForr, unfortunately it idles now) and HTML::Template (last commit on 28 Oct 2016).

Also there is the handy multi-module adapter Web::Template — a simple abstraction layer, providing a consistent API for different template engines.

What engine should you choose? My criteria was: the project should be alive and be the part of Rakudo Star Bundle distributive. Well, Template::Mustache is the chosen one innocent.

What are web templates?

Web templates are HTML documents with additional metadata (markup), that’s going to be processed by templating engine — in simple templates metadata is presented by variables (e.g., Template6 templating engine interpolates variable to [% varname %] and Template::Mustache to {{ varname }}). After the web template is processed all metadata variables are replaced with actual content values.

Сomposite web template includes the links (bindings) to another templates. For example canonical Mustache templates could perform import with {{> template_name }} (see Partials). By the way, it’s possible to use links at imported template, so recursive partials are accepted.

A web template with logic (inline programs) uses extended meta markup. We can write simple layout management programs right inside the template. Template6 engine successfully «executes» constructions like [% for item in list %][% item %]\n[% end %] or [% if flag %]foo[% else %]bar[% end %].

Regular practice for the most of web applications — templating with simple and composite templates. We use only variables and import dependencies (header, footer, comments block, feedback form, etc…). All extended logic (that could be implemented with inline programs) should be excluded as much as possible, or put onto web application layer.

In this article we will consider the most trivial case — web template with variables (no imports, no inline logic).


As I have mentioned above, my criteria to choose a templating engine were the project support and accessibility. But the in real life actually the important criteria is performance. Well, no matter the project is very much alive or is included into all known distros — if the client waits a few second for page rendering, we have to seek another module or solution.

So, to test the performance i have used the Pheix CMS embedded template as a one of the most trivial. In my opinion — if the templating engine will easily deal with it, we can go to the next step of testing — for example, inline programs.

Notes: the template has 14 variables and 2 of them are using the extended Mustache syntax {{{ varname }}}. Triple curly braces are telling the engine to skip the escaping inside the content block we are replacing into.

The test suite is based on the processing script, where the render() method from helper module Pheix::View::TemplateM is used. The sources are quite simple and as close as possible to documentation guidelines. We are profiling the render() method and measuring the execution time (no compiling, loading, object initialization, etc… time is considered). Also we use automated bash helper to run tmpl-mustache.raku in loop for 100 iterations and count average run time.

Tests are performed on MacBook Unibody Core2Duo 2.6 GHz, 8Gb RAM platform (consider it like the mid-performance VPS):

mustache render time: 1.8555348 sec

In other words, if the web application works as the classic CGI instance (no caching, no available workers, no proxies — every run is from the scratch), the request will be rendered at least in 2 seconds (network latency + duty cycle + templating [+ server resource throttling]). Actually this time may be more than 10 seconds (a few parallel clients case) — absolutely bad.

The same test is performed for Template6:

template6 render time: 0.5481035 sec

This module is x3 faster. Sources: template and script.

First optimization

The first idea on optimization was: «ok, it seems that the considered modules are heavy for this task — let’s write something simple». And I have implemented my own render() method, based on generic regular expressions:

method fast_render(Str :$template is copy, :%vars) returns Str {
for %vars.keys -> $key {
if $key ~~ /tmpl_timestamp/ {
$template ~~ s:g/ \{\{\{?: <$key>: \}\}\}?: /%vars{$key}/;
else {
$template ~~ s/ \{\{\{?: <$key>: \}\}\}?: /%vars{$key}/;

Sources: script, helper module.


regexpr render time: 0.2721529

This is a 2x increase in performance compared to Template6 and 6x increase compared to Template::Mustache.

Second optimization

The next idea was: «well, let’s parse HTML template to the tree and replace/substitute required blocks». I have used the XML module for this task.

This approach requires a little bit tricky template: as the template is parsed to the tree, we need to address the blocks to interpolate. In case of Template6 or Template::Mustache we use meta markup, but it fails on XML validation.

Well, I add the XML markup into the basic template instead of meta markup:

  • specific tags: <pheixtemplate variable="tmpl_pagetitle"></pheixtemplate>;
  • specific attributes:
    • <link href="resources/skins/akin/css/pheix.css" pheix-timestamp-to="href" rel="stylesheet" /> — this means the timestamp value will be concatenated to string from href attribute;
    • <meta name="keywords" content="" pheix-variable-to="content" pheix-variable="tmpl_metakeys" /> — this means the tmpl_metakeys value will be inserted to content attribute.

Also specific tags should have pre-built HTML code and bindings to existed tree nodes, specific attributes just have to be defined — this is done so straight-forward at initialization step:

my %tparams =
title => {
name => 'tmpl_pagetitle',
new => make-xml('title', "This is the page title"),
existed => Nil,
value => q{}
mkeys => {
name => 'tmpl_metakeys',
new => Nil,
existed => Nil,
value => 'This is meta.keywords data'
for %tparams.keys -> $k {
%tparams{$k}<existed> =
$xml.root.elements(:TAG<pheixtemplate>, :variable(%tparams{$k}<name>), :RECURSE, :SINGLE);
if !%tparams{$k}<existed> {
%tparams{$k}<existed> = $xml.root.elements(:pheix-variable(%tparams{$k}<name>), :RECURSE, :SINGLE);

Timestamps blocks are collected with:

my @timestampto = $xml.root.elements(:pheix-timestamp-to(* ~~ /<[a..z]>+/), :RECURSE);

And processed by (as trivial as possible):

for (@timestampto) {
my Str $attr = $_.attribs<pheix-timestamp-to>;
if $attr {
$_.set($attr, ($_.attribs{$attr} ~ q{?} ~ now.Rat));

Variable tags are processed with:

for %tparams.keys -> $k {
if %tparams{$k}<new> {
%tparams{$k}<existed>.parent.replace(%tparams{$k}<existed>, %tparams{$k}<new>);
else {
my Str $attr = %tparams{$k}<existed>.attribs<pheix-variable-to>;
if $attr {
%tparams{$k}<existed>.set($attr, %tparams{$k}<value>);

Yeah! Let’s run the script:

$ raku html-2-xml.raku

# processing time: 0.15519139
# added 6 timestamps
# replaced 8 variables

Average result on 100 iterations:

xml render time: 0.1550534 sec

This is a 2x increase in performance compared to custom RegExpr, x4 increase compared to Template6 and 12x increase compared to Template::Mustache exploding_head.


Canonical usage (following guideline)

Just for fun I have measured the performance of the old and forgotten HTML::Template module. Test sources: template, script (toggle comments Pheix::View::TemplateH to Pheix::View::TemplateH2), helper module.

Average result on 100 iterations:

htmltmpl render time: 0.1911648 sec

Well, it’s quite fast out-of-the-box.

Make it ~ x100 faster

HTML::Template module provides simple grammar, so the third idea was «yep, let’s parse HTML template into the variable (according the given grammar) at initialization stage and substitute at runtime».

Test sources: template, script (toggle comments Pheix::View::TemplateH2 to Pheix::View::TemplateH), helper module.

Average result on 100 iterations:

htmltmpl render time: 0.0021661 sec

This is a 100x increase in performance compared to canonical usage tada tada tada.

Need more boost?!

Modern web development techniques involve the backend templating engine load balancing.

The common technique is based on idea of distributing template rendering task between the server and the client: depending on server load, the template is fully rendered by backend or backend just does the fast generation. It pulls the template file and concatenates its content with the data to be replaced (represented in JSON). Usually this data is added to end of template as the JavaScript code inside the <script></script> tag.

On the next step JavaScript template engine (for example, RIOT.js) does the full page rendering right inside the client’s browser.

This approach could be effective in case we use Template::Mustache as the primary templating engine on backend. Template variables for server-side rendering are marked as {{ varname }} or {{{ varname }}}, client-side rendering variables are marked as { props.varname }. So, this is the way we get the consistency of template source code and basic semantic integrity.


Why does the latency matter?

Client’s point of view: nobody likes slow websites.

Developer’s point of view: if we will minimize latencies and bottlenecks at routine tasks — and template rendering is the such one, — we will free the space for resource-intensive or slow technologies.

For example, blockchain. This humble research was inspired by the development of Pheix web content management system with data storing on Ethereum blockchain. Some of the ideas outlined here are put into code of public β-version — it will be released by the end of this year.


All sources considered in this post are available at The final scores are (in seconds):

1. htmltmpl pre-parse render time: 0.0021661
2. xml render time:                0.1550534
3. htmltmpl native render time:    0.1911648
4. regexpr render time:            0.2721529
5. template6 render time:          0.5481035
6. mustache render time:           1.8555348

Raku driven web applications

I’m sure, we can use Raku as web programming language. We can create fast, reactive and scalable Raku driven backends. On other side it requires a little bit more practice and time: combining different techniques and approaches we can get more performance improvements. The sad things — the ecosystem is still raw and when we want to use some module in our project, we should profile it, compare with analogs, maybe fork and tweak. The optimistic things — we can get our Raku driven web application work fast, so it can be released to production.

3 thoughts on “Day 8: Raku web templating engines: boost up the parsing performance

  1. There is a little known feature of Template::Mustache, which can significantly speed up template processing on a multi-core systems. If a data structure to be rendered contains a long list of sub-structures (like rows of a table) the list could be represented as a lazy hyper/race sequence. I.e. something like %template_data<rows> = (gather { for { ...; take $row; } }).hyper. For example, I used it to render tracing data in Vikna project:

    Apparently, on a heavily loaded webserver this might not be as useful as in a single-script scenario. But, on the other hand, if a server is consuming all CPU power 24/7 then it’s certainly time to upgrade or add another one.

    Liked by 2 people

Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.

%d bloggers like this: