Twig : accélérer la génération de ses templates
Agence web » Actualités du digital » Twig : accélérer la génération de ses templates

Twig : accélérer la génération de ses templates

Récemment, je me suis posé pour réfléchir autour des solutions que propose Twig pour accéder aux propriétés d’un objet ou d’un tableau.

Accéder à une propriété d’un objet

Twig a été conçu pour simplifier nos templates, aussi bien en terme de code qu’en terme de propreté. Il a également été conçu pour permettre aux intégrateurs, personnes n’ayant pas tous de connaissances en développement, un accès facile aux propriétés d’un objet ou autres. Cela grâce à une syntaxe simplifiée.

Seulement voilà, étant développeur avant-tout, je me suis posé la question de comment twig faisait pour déterminer quelle méthode d’un objet doit être appelé.

Syntaxe Twig

Dans mon code suivant, je partirai du principe que j’utilise une classe comme ci-dessous.

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

Voici comment j’appellerai mon template depuis un contrôleur Symfony.

1
2
3
public function indexAction() {
return array(« object » => $object);
}

Jusque là, tout est clair. Mon objet ressemble parfaitement aux objets que je peux utiliser dans mes projets.

Analyse des déclarations dans Twig

1
{{ object.name }}

À partir de maintenant nous allons voir comment Twig travaille avec nos appels. Ici, on demande à Twig de prendre la valeur name de mon objet, sauf que pour Twig qui n’est qu’un simple appel PHP au final, cette variable est inaccessible. Twig a donc ajouter un proxy permettant de faire abstraction et de regarder quelles sont les propriétés et méthodes disponibles.

Twig va donc demander dans l’ordre ci-dessous de faire des vérifications sur l’objet.

  1. Regarde si object est un tableau et si name est une clé ;
  2. Regarde si object est un objet et si name est un attribut accessible ;
  3. Regarde si object est un objet et si name est une méthode ;
  4. Regarde si object est un objet et si getName est une méthode ;
  5. Regarde si object est un objet et si isName est une méthode.
    Ici, avec la notation que nous avons utilisé, il nous a fallu attendre la 4ème condition pour parvenir à notre élément. Car name n’est pas un attribut accessible, ni une méthode.

    1
    {{ object.username }}

Dans ce cas, nous essayons d’accéder à username. Cet attribut est un attribut publique (dans Symfony, j’ai rarement rencontré ce cas). Il nous faut attendre la 2ème condition.

1
{{ object.getName() }}

Dans notre troisième cas, je tente de faire appel à ma méthode de manière direct. Je récupère mon résultat dès la troisième condition, soit une condition plus rapide.

1
{{ object.getUsername() }}

Dans notre quatrième et dernier cas, je tente de faire appel à ma méthode de manière direct. Je récupère mon résultat dès la troisième condition. Dans ce cas, je mets une condition supplémentaire pour accéder à ma valeur.

Interprétation des templates Twig

Lorsque je me suis penché sur ce détail, j’ai réfléchis à comment les développeurs de Twig avait pu réaliser la mise en cache et la compilation des templates. Il ne me fallut pas beaucoup de temps pour en venir à l’évidence, qu’avec le peu d’information et la liberté qu’offre le gestionnaire de template, nos fichiers sont simplement transcrit en PHP d’une manière similaire à ce que l’on pourrait tout à fait faire. Vous pouvez d’ailleurs constater par vous même en cherchant dans votre dossier de cache.

1
2
{# Template twig #}
{{ object.name }}
Template interprété en PHP
1
echo twig_escape_filter($this->env, $this->getAttribute((isset($context[« object »]) ? $context[« object »] : $this->getContext($context, « object »)), « name »), « html », null, true);

Le premier bloque correspond à ce que j’ai noté dans mon template twig, le deuxième bloque à la version traduite que j’ai trouvé dans le dossier de cache.

On remarque bien que le twig a été traduit en PHP mais qu’aucun élément ne lui a permis de prédire quelle méthode devrait exactement être appelée. La méthode twig_escape_filter pourrait être comparé à un proxy. C’est dans cette méthode qu’on va déterminer comment on accède à l’attribut.

Conclusion

Malgré que les templates twig soit mis en cache automatiquement par Symfony, seul la version PHP est gardé mais sans interprétation de ce que l’on souhaite récupérer. En théorie, il y a donc ici, le moyen d’optimiser des appels et le temps de génération de nos templates car la vérification est effectuée à chaque appel.

Benchmark

J’ai quand même voulu avoir une idée concernant les gains qui peuvent être fait en appelant les méthodes plutôt que des « alias ».

Dans un premier cas, j’appelle un template qui va appeler 10 fois un même objet qui appelle à son tour 25 alias. Cela représente 250 appels. Les résultats sont grossis par la boucle de 10 pour permettre de faire des calculs juste quant au gain de performance.

Dans un second temps j’appelle un template qui va appeler 10 fois le même objet et qui appelle à son tour 25 méthodes (toujours via le même proxy que pour les alias). Cela représente 250 encore une fois.

J’ai réalisé l’appel de ces templates 5 fois chacun.

On remarque que l’intégralité des appels au template faisant appels à des méthodes est plus rapide. En faisant les moyennes, on remarque que le template faisant appels aux alias est plus long de 54,6 millisecondes (197.4 – 142.8).

En faisant un rapide calcul, on remarque que si on ramenait ça à un cas général, le template utilisant des appels à des méthodes est plus rapide en moyenne sur des mêmes données de approximativement 26.7%. Cela peut- être intéressant lorsqu’on réalise de nombreux appels à des objets.

Ce deuxième article fais suite à un premier post donnant des solutions rapides pour optimiser la génération des templates. Il est le fruit d’optimisation déjà utilisé sur des sites en production qui possédait un taux de latence trop important.

Nous utilisons tous les includes de Twig pour déporter ou factoriser nos vues. Mais peut-être n’avez-vous jamais compris pourquoi nous avions la possibilité de passer des paramètres, alors qu’un include de base hérite des variables du contexte en cours.

1
{% include « ProjectMyBundle:Sub:template.html.twig » %}

Souvent nous utilisons cette notation. Dans ce cas, l’include donnera une copie de toutes nos variables à notre template. Cela n’est pas toujours nécessaire, et nous avons tort de copier l’intégralité de nos variables.

Intérêt de l’option « only »

Je me suis longtemps demandé qu’elle était l’intérêt de cette option. D’ailleurs il faut dire que la documentation ne donne pas beaucoup d’élément. Vous pouvez aller jeter un coup d’œil sur la documentation des includes Twig. Elle donne peu d’élément mais elle donne surtout un inconvénient et aucun avantage : se passer de certaines variables. Vue de cette manière, cela n’est pas rentable ; pourquoi je devrais me passer de certaines variables.

Mettons en œuvre nos includes ! Admettons que notre template de base ait 5 variables et que j’inclue un template qui n’utilise qu’une seule de ces variable, voilà la notation qui sera préférable.

1
{% include « ProjectMyBundle:Sub:template.html.twig » with {« object »: myVar} only %}

De cette manière seul une variable « object » sera disponible dans mon template. Cela limite la copie de variable inutile, et donc d’allocation de mémoire (gain de temps et de consommation mémoire).

Code et cas d’utilisation

1
2
3
4
5
6
// Controller
// Ici le génère un template en passant un variable imposante (une collection) contenant tous les objets pour une table donnée
public function testAction() {
$objects = $this->container->get(« doctrine.orm.default_entity_manager »)->getRepository(« ProjectMyBundle:Test »)->findAll();
return array(« objects » => $objects);
}

Nous effectuons une boucle sur notre collection et nous donnons un template chaque itération de la collection au template. Dans ce cas, chaque fois que l’on fait appelle à l’intégration d’un template, l’intégralité des variables sont copiées ainsi que celles qui sont passées avec l’option « with ». Dans notre premier cas on pourrait très bien imaginer accéder à notre collection (objects) ainsi que notre itération en cours (object).

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

Dans notre deuxième cas, j’active l’option only ce qui permet de copier uniquement les variables passés en paramètres. Facile ?

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

Benchmark

J’ai réalisé les tests avec les templates et le code donnés dans la partie ci-dessus. J’ai effectuer avec 5 itérations de chaque template, en pensant à vider le cache avant chaque premier test.

Avec ce graphe, on voit bien le chargement est globalement plus long pour ceux qui n’utilisent pas l’option only. La différence a tendance à se réduire après la mise en cache. Cela est dû au fait que mon template est petit et que peu de variable sont utilisés. Dans des cas plus appliqués on note des gains pouvant aller jusqu’à 30%.

Ici, si on fait une moyenne des valeurs, on atteint une différence moyenne de 9 ms (48,6 – 39,6 = 9). On peut donc calculer un gain approximatif de 20% même si cela est à relativiser dans notre cas puisque le premier shot est terriblement long sans l’utilisation de « only ».

★★★★★