Twig: accelerate the generation of its templates
Web agency » Digital news » Twig: accelerate the generation of its templates

Twig: accelerate the generation of its templates

Recently, I asked myself to reflect on the solutions offered by Twig to access the properties of an object or an array.

Accessing a property of an object

Twig was designed to simplify our templates, both in terms of code and in terms of cleanliness. It was also designed to allow integrators, people who do not all have development knowledge, easy access to the properties of an object or others. This thanks to a simplified syntax.

Only then, being a developer above all, I asked myself the question of how twig was doing to determine which method of an object should be called.

Twig Syntax

In my following code I will assume that I am using a class like below.

1
2
3
4
5
6
7
8
9
10
11
12
13
class Object
{
private $Name;
public insurance $username;
public insurance function getName() {
return $ this->name;
}
public insurance function getUsername() {
return $ this->username;
}
}

This is how I will call my template from a Symfony controller.

1
2
3
public insurance function indexAction() {
return array(“object” => $object);
}

So far, everything is clear. My object looks exactly like the objects I can use in my projects.

Parsing declarations in Twig

1
{{ object.name }}

From now on we will see how Twig works with our calls. Here, we ask Twig to take the value name of my object, except that for Twig which is only a simple PHP call in the end, this variable is inaccessible. Twig has therefore added a proxy to abstract and see what properties and methods are available.

Twig will therefore ask in the order below to make checks on the object.

  1. See if object is an array and if name is a key;
  2. See if object is an object and if name is an accessible attribute;
  3. See if object is an object and if name is a method;
  4. See if object is an object and if getName is a method;
  5. See if object is an object and if isName is a method.
    Here, with the notation we used, we had to wait for the 4th condition to arrive at our element. Because name is not an accessible attribute, nor a method.

    1
    {{ object.username }}

In this case, we are trying to access username. This attribute is a public attribute (in Symfony, I rarely encountered this case). We have to wait for the 2nd condition.

1
{{ object.getName() }}

In our third case, I try to call my method directly. I get my result from the third condition, which is a faster condition.

1
{{ object.getUsername() }}

In our fourth and last case, I try to use my method directly. I get my result from the third condition. In this case, I put an additional condition to access my value.

Interpreting Twig templates

When I looked at this detail, I thought about how the developers of Twig could have done the caching and the compilation of the templates. It didn't take long for me to come to the conclusion that with the little information and the freedom that the template manager offers, our files are simply transcribed into PHP in a similar way to what we could totally do. You can also see for yourself by looking in your cache folder.

1
2
{# Template branch #}
{{ object.name }}
Template interpreted in PHP
1
echo twig_escape_filter($this->env, $this->getAttribute((isset($context["object"]) ? $context["object"] : $this->getContext($context, "object")), “name”), “html”, null, true);

The first block matches what I noted in my twig template, the second block matches the translated version I found in the cache folder.

We note that the branch has been translated into PHP but that no element has allowed it to predict which method should be called exactly. The method branch_escape_filter could be compared to a proxy. It is in this method that we will determine how we access the attribute.

Conclusion

Although twig templates are automatically cached by Symfony, only the PHP version is kept but without interpretation of what you want to retrieve. In theory, there is therefore here, the means to optimize calls and the generation time of our templates because the verification is carried out at each call.

Benchmark

I still wanted to have an idea about the gains that can be made by calling the methods rather than " alias ».

In a first case, I call a template which will call 10 times the same object which in turn calls 25 aliases. This represents 250 calls. The results are magnified by the loop of 10 to allow accurate calculations as to the performance gain.

Secondly, I call a template which will call the same object 10 times and which in turn calls 25 methods (always via the same proxy as for the aliases). That's 250 again.

I made the call of these templates 5 times each.

Note that all calls to the template making calls to methods are faster. By taking the averages, we notice that the template using aliases is longer by 54,6 milliseconds (197.4 – 142.8).

By doing a quick calculation, we notice that if we reduce it to a general case, the template using calls to methods is faster on average on the same data by approximately 26.7%. This can be interesting when making many calls to objects.

This second article follows a first post giving quick solutions to optimize the generation of templates. It is the fruit of optimization already used on sites in production which had too high a latency rate.

We use all includes de Twig to shift or factor our views. But maybe you never understood why we had the ability to pass parameters, when a basic include inherits variables from the current context.

1
{% includes “ProjectMyBundle:Sub:template.html.twig” %}

Often we use this notation. In this case, the include will give a copy of all our variables to our template. This is not always necessary, and we are wrong to copy all of our variables.

Interest of the “only” option

I wondered for a long time what was the point of this option. Besides, it must be said that the documentation does not give much information. You can take a look at the Twig includes documentation. It gives little element but it gives especially a disadvantage and no advantage: to do without certain variables. Seen in this way, it is not profitable; why I should do without some variables.

Let's implement our includes! Let's say our base template has 5 variables and I include a template that only uses one of these variables, this is the notation that will be preferable.

1
{% includes "ProjectMyBundle:Sub:template.html.twig" with {"object": myVar} only %}

In this way only an "object" variable will be available in my template. This limits the copying of useless variables, and therefore of memory allocation (saving time and memory consumption).

Code and use cases

1
2
3
4
5
6
// Controller
// Here the generates a template by passing an imposing variable (a collection) containing all the objects for a given table
public insurance function testAction() {
$objects = $ this->container->get(“doctrine.orm.default_entity_manager”)->getRepository(“ProjectMyBundle:Test”)->findAll();
return array(“items” => $objects);
}

We loop over our collection and we give a template each iteration of the collection to the template. In this case, each time we call for the integration of a template, all the variables are copied as well as those passed with the "with" option. In our first case we could very well imagine accessing our collection (objects) as well as our current iteration (object).

1
2
3
4
5
{#Template 1#}
{% for object in objects %}
{% includes "ProjectMyBundle:Sub:template.html.twig" with {"object": object} %}
{% endfor %}

In our second case, I activate the option only which allows you to copy only the variables passed as parameters. Easy ?

1
2
3
4
5
{#Template 2#}
{% for object in objects %}
{% includes "ProjectMyBundle:Sub:template.html.twig" with {"object": object} only %}
{% endfor %}

Benchmark

I carried out the tests with the templates and the code given in the part above. I performed with 5 iterations of each template, remembering to empty the cache before each first test.

With this graph, we can see the loading is generally longer for those who do not use the option only. The difference tends to reduce after caching. This is due to the fact that my template is small and few variables are used. In more applied cases, there are gains of up to 30%.

Here, if we average the values, we reach an average difference of 9 ms (48,6 – 39,6 = 9). We can therefore calculate an approximate gain of 20% even if this is to be put into perspective in our case since the first shot is terribly long without the use of “ only ».

★ ★ ★ ★ ★