How does Shopify compile liquid?

With page load times being a major opportunity for increasing conversion rates on your Shopify store, it has never been more important to determine exactly how long it takes for your liquid code to execute. After all, the execution of your liquid code is blocking. In this article, we treat Shopify as a black box and run some timed code to reverse engineer the the basic mechanics of Shopify's compiler so you can better understand how to write faster and more efficient liquid code. TL;DR: Liquid comments slow down execution time which begs the question, does liquid even get compiled?

Benchmarking

To begin our experiment, we first need to establish a method for timing and benchmarking the code. This can be done using the date filter in liquid and a simple benchmark where we assign 1 to n.

{% assign startTime = 'now' | date: "%S%3N" %}
{% for i in (1..100000) %}
{% assign n = 1 %}
{% endfor %}
{% assign endTime =  'now' | date: "%S%3N" %}

{% assign executionTime =  endTime | minus: startTime %}
{% if executionTime < 0 %}
{% assign executionTime = 60000 | plus: executionTime %}
{% endif %}

Execution time: {{ executionTime }}ms
Execution time: 562 +/- 50ms

As you can see we capture the time before and after the a for loop that does 100000 iterations of a benchmark test which is "{% assign n = 1 %}". It is important to include code in the for loop as a good compiler will otherwise jump the for loop. To run the test we load the page containing the benchmark 8 times to allow and record the execution time. It is necessary to run the test multiple times to allow for variance on the machine and so we can calculate the mean and standard deviation of the result. The benchmark took 562 +/- 50ms to execute. Each time we run the experiment the code altered slightly so that we invalidate the Shopify's cache. This is done by simply adding or removing space characters after the benchmark code and clicking save.

The Experiment

With our benchmark setup we can now run some interesting tests on the liquid code and compare the execution time. In our first experiment, we test how long it takes to execute a liquid comment to determine if they are removed by a compiler. We run the same code as above, except the content of our for loop, which is our test, has been changed to include some commented liquid.

Test 1

{% assign n = 1 %}
{% comment %}
  This is a liquid comment
{% endcomment %}
Execution time: 777 +/- 48ms

The results of this test showed a definitive increase in the execution time to 777 +/- 48ms which is an increase of 215ms. To investigate this further, let's see what happens when the content of the liquid comment is increased as in the following test.

Test 2

{% assign n = 1 %}
{% comment %}
  This is a liquid comment
  This is a liquid comment
  This is a liquid comment
  This is a liquid comment
  This is a liquid comment
{% endcomment %}
Execution time: 740 +/- 31ms

The results of this test, were roughly the same with an execution time of 740 +/- 31ms. From this experiment we can see that the content of liquid comments will not be parsed during execution, which is good. Let's have a further look at what happens if we have 5 separate liquid comments.

Test 3

{% assign n = 1 %}
{% comment %}
  This is a liquid comment
{% endcomment %}
{% comment %}
  This is a liquid comment
{% endcomment %}
{% comment %}
  This is a liquid comment
{% endcomment %}
{% comment %}
  This is a liquid comment
{% endcomment %}
{% comment %}
  This is a liquid comment
{% endcomment %}
Execution time: 1459 +/- 94ms

This code took 1459 +/- 94ms to execute. That is 897ms longer than the original benchmark "{% assign n = 1 %}" and the average execution time per comment was 179ms. Let's leave the comments empty to see if the content of the comment has any effect.

TEST 4

{% assign n = 1 %}
{% comment %}{% endcomment %}
{% comment %}{% endcomment %}
{% comment %}{% endcomment %}
{% comment %}{% endcomment %}
{% comment %}{% endcomment %}
Execution time: 1572 +/- 276ms

This test took 1572 +/- 276ms to execute which is relatively the same as Test 4. It's clear now that while liquid will interpret the comments it, jumps the text within the comments. Let's have a look to see what happens then the content of the comment is some liquid code. Introducing...

TEST 5

{% assign n = 1 %}
{% comment %}
  {% if n == 1 %}
  n is one, which is true
  {% endif %}
{% endcomment %}
Execution time: 732 +/- 34ms

The execution time for this code was 732 +/- 34ms which is relatively close to the results from Test 1. While this is the expected result, it was an important test as we really don't know how the liquid gets executed and it is important to treat the black box with respect.

So what can we conclude from our little experiment?

From Test 1 it is immediately clear that Shopify does not remove liquid comments from the code that gets executed during run time. To determine whether or not the text inside the comment is parsed at runtime, Test 2 added more text to the comment. There was no significant difference in execution time, yet when we added 5 individual liquid comments as in Test 3 there was a significant increase in execution time. Test 4 further confirms that the text inside liquid comments does not get parsed. Finally we check to see if liquid code within a comment gets executed and it clearly does not as the execution time is invariant to the content of the comment be it liquid code or plaintext.

It is clear liquid comments are interpreted at run time and are not removed using a compiler. It looks like Shopify does not compile the liquid and that it is instead interpreted during runtime. But we have more work to do to know for sure!

TAGS

Experiment
Liquid
Optimization
Shopify