Blackfire on Blackfire: Twig & JSON

At Blackfire, we continuously profile and monitor our code behavior. As I wrote in a previous post, I consider Blackfire as a wonderful pair of glasses that helps me to understand how third party libraries works behind the scene. Today we’ll explore the world of serialization. As web developers we serialize data every day. We […]

By Romain Neutron, on Jan 07, 2016

At Blackfire, we continuously profile and monitor our code behavior. As I wrote in a previous post, I consider Blackfire as a wonderful pair of glasses that helps me to understand how third party libraries works behind the scene.

Today we’ll explore the world of serialization.

As web developers we serialize data every day. We serve JSON entities through APIs, we embed JSON payload in javascript blocks to draw nice web interfaces. Serialization ease the transfer of data to Javascript and the communication between clients and servers.

To draw our graph interface we embed JSON in the HTML. We could rely on async load to perform this but the way we do it fasten the rendering by eliminating an extra request. We won’t discuss this choice here, and I’m pretty sure you already did it.

To achieve this goal, we use the Twig json_encode filter. Don’t know this one yet? It’s pretty neat. Here is a usage example that displays JSON encoded data in an HTML <pre> tag:

{# App/Resources/views/index.html.twig #}

{% extends 'base.html.twig' %}

{% block body %}
<pre>
    {{ data | json_encode }}
</pre>
{% endblock %}

If you profile such code, you’ll realise that this filter results in a call to the twig_jsonencode_filter function calling the native PHP json_encode function (as expected) and _twig_markup2string (more surprising).

In my example, this later function is called nearly 18000 times, that’s impressive and related to the array I passed for serialization. It was a huge array of arrays. here’s the source code of the underlying Twig functions:

function twig_jsonencode_filter($value, $options = 0)
{
    if ($value instanceof Twig_Markup) {
        $value = (string) $value;
    } elseif (is_array($value)) {
        array_walk_recursive($value, '_twig_markup2string');
    }

    return json_encode($value, $options);
}

function _twig_markup2string(&$value)
{
    if ($value instanceof Twig_Markup) {
        $value = (string) $value;
    }
}

What’s a Twig_Markup? I recommend you to grep Twig’s source code for “new Twig_Markup(” to understand what’s happening here, but basically it happens that the banane and pomme variables in the compiled template below result in Twig_Markup:

{% set banane %}banana{% endset %}
{% set pomme %}apple{% endset %}

{{ [banane, pomme] | json_encode }}

So, we have suprising extra calls to _twig_markup2string and we know in our case that the PHP array only contains regular strings and integers that are fully compatible with a regular json_encode.

Let’s create a basic_json_encode filter:

<?php

namespace AppBundleTwig;

class JsonExtension extends Twig_Extension
{
    public function getFilters()
    {
        return array(
            new Twig_SimpleFilter('basic_json_encode', array($this, 'jsonEncode')),
        );
    }

    public function jsonEncode($data, $options = 0)
    {
        return json_encode($data, JSON_UNESCAPED_SLASHES | JSON_HEX_TAG | $options);
    }

    public function getName()
    {
        return 'json_extension';
    }
}

And use it in our template instead of the regular json_encode filter:

 {% block body %}
 <pre>
-    {{ data | json_encode }}
+    {{ data | basic_json_encode }}
 </pre>
 {% endblock %}

Let’s profile and compare with the previous one; result is final: on large arrays we save lots of CPU.

When using large arrays coming from your controller, you can use your own json_encode filter that will save CPU and memory usage.

I hope you enjoy saving CPU as me 🙂

Happy profiling!

Romain Neutron

Romain is a developer at Blackfire.io. He started programming years ago while he was studying physics. He loved it so much he stopped his studies to be a programmer and contributed to various open source projects. He joined SensioLabs in 2014 to work on Blackfire and discovered a new love in application performance.