EEx performance

I’ve been playing with EEx, and I’ve noticed it doesn’t seem to optimize the templates during compilation. For example:

iex(32)> EEx.compile_string("ab")
{:<>, [context: EEx.Engine, import: Kernel], ["", "ab"]}

iex(33)> EEx.compile_string("a<%\n%>b")
{:<>, [context: EEx.Engine, import: Kernel],
 [{:__block__, [],
   [{:=, [],
     [{:tmp2, [], EEx.Engine},
      {:<>, [context: EEx.Engine, import: Kernel], ["", "a"]}]}, nil,
    {:tmp2, [], EEx.Engine}]}, "b"]}

The above templates result in an identical constant expression. In none of the cases above it’s actually compiled to a constant, and in the second case it compiles into something with even more binary concatenations than the first.

Can I trust Elixir to optimize the empty <% %> blocks away later on?

Now, my motivation. I’m writing a template engine that compiles to EEx. It’s pretty much a clone of ExPug (the working name is Husky), but with a slightly different syntax and a different architecture. Just like ExPug, I’d like to keep the same line numbers so that the user gets the right line number in case of errors, while not introducing any newlines in the HTML that may be significant (https://hexdocs.pm/expug/line_number_preservation.html)

One way of doing this is to introduce EEx blocks with a newline ("<%\n%>"). This is a clean solution, (and what ExPug does. I’d like to copy it, but only if this doesn’t cause any significant overhead.

For example, this template:

div.post-list
      = for post <- posts do
      div.panel.panel-default
        div.panel-header
          = post.header
        div.panel-body
          = post.content
        div.panel-footer
          = post.author
      - end

currently compiles to this:

<div class="post-list"><%
%><%= for post <- posts do %><%
%><div class="panel panel-default"><%
%><div class="panel-header"><%
%><%= post.header %></div><%
%><div class="panel-body"><%
%><%= post.content %></div><%
%><div class="panel-footer"><%
%><%= post.author %></div></div><%
%><% end %></div>

This keeps the line numbers without introducing any newlines. But if this causes a lot of overhead, then I might change it to include actual newlines when possible:

<div class="post-list"
><%= for post <- posts do %><%
%><div class="panel panel-default"
><div class="panel-header"
><%= post.header %></div
><div class="panel-body"
><%= post.content %></div
><div class="panel-footer"
><%= post.author %></div></div
><% end %></div>

This adds newlines that have no semantic meaning when possible and defaults to adding <%\n%> when needed (here it was only needed once). The EEx output is actually cleaner, but it generates larger files with extra newlines for no good reason. I know the overhead here is negligible, but every bit you save can make a different when sending stuff through the wire (yes, I know it can be gzipped and all that). If Elixir optimizes away the empty blocks, then I’ll go with the first approach. If it doesn’t, I’m not really sure what to do… I’d be trading extra string concatenations for larger transfer volumes.

Does anyone here have any tips?

I would seriously consider not compiling down to EEx, regardless if EEx optimizes it or not. IMO you should just emit Elixir AST. is there a reason why you don’t?

No, there isn’t any particular reason, actually… I just wanted something like ExPug with a few tweaks, and decided to do the parsing myself but copy their approach to compilation. It’s probably easy to compile it down to a series of <> operations, like EEx does…

Yes, but don’t compile to <>. Compile to an iolist. Instead of foo <> bar, do [foo | bar] or at least [foo, bar]. It is going to perform much better. That’s what Phoenix does for its HTML safe engine. At the end you can get a binary by calling IO.iodata_to_binary.

Actually, there is a catch I’ve just thought about… Look at this example:

div.post-list
      = for post <- posts do
      div.panel.panel-default
        div.panel-header
          = post.header
        div.panel-body
          = post.content
        div.panel-footer
          = post.author
      - end

This mixing between a do block and literal HTML (in this case, Pug), doesn’t seem to be the easiest thing to make sense of… I’ll have to take a look at how an EEx engine does it.

But if I compile to EEx (the language, not the library), and use the Phoenix Engine to compile it to Elixir if shouldn’t be too bad, right?

It certainly won’t be bad, no.

1 Like

And regarding the original question, if I were to compile to an EEx engine, like Phoenix’s, would you add the newlines or the empty blocks?

I would go with new lines as the empty blocks will have at least some effect.

1 Like