This is a template engine which is able to compile templates into the fast code.
Here is a quick example:
POFTHEDAY> (zenekindarl:compile-template-string
:string "Hello {{var name}}!")
#<FUNCTION (LAMBDA (&KEY :NAME)) {231D16FB}>
POFTHEDAY> (funcall * :name "Bob")
"Hello Bob!"
Interesting fact – it is named after a race of ancient people.
When searching this information, I’ve also found this page about the Dexador which is a dragon of Zenekindarl people and also a CL HTTP library :)
Documentation says, this library is faster than Python’s Jinja2 which known for it’s ability to compile templates into the bytecode.
Let’s make our own comparison! We’ll do the test more complex and extend
it to the cl-who
. Later I’ll add to this comparison other template
engines during the writing their review.
Let’s start with the baseline test for Jinja2:
Python 3.7.7 (default, Mar 10 2020, 15:43:33)
Type 'copyright', 'credits' or 'license' for more information
IPython 7.8.0 -- An enhanced Interactive Python. Type '?' for help.
In [1]: from jinja2 import Template
In [2]: template = Template("""
...: <h1>{{ title }}</h1>
...: <ul>
...: {% for item in items %}
...: <li>{{ item }}</li>
...: {% endfor %}
...: </ul>
...: """)
In [3]: %timeit template.render(title='Foo Bar', items=['One', 'Two', 'Tree'])
6.18 µs ± 37 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
As you see, each call to the render
takes 6.18 microseconds.
Now we’ll implement it in zenekindarl
:
POFTHEDAY> (let ((tmpl (zenekindarl:compile-template-string
:string "
<h1>{{ var title }}</h1>
<ul>
{{ loop items as item }}
<li>{{ var item }}</li>
{{ endloop }}
</ul>
")))
(time
(loop repeat 1000000
do (funcall tmpl
:title "Foo Bar"
:items '("One" "Two" "Three")))))
Evaluation took:
1.538 seconds of real time
1.546164 seconds of total run time (1.525024 user, 0.021140 system)
[ Run times consist of 0.173 seconds GC time, and 1.374 seconds non-GC time. ]
100.52% CPU
3,395,746,202 processor cycles
911,998,848 bytes consed
Hmm, with zenekindarl
it takes 1.54 microseconds. This is only 4 times
faster, not 25 times like promised by the documentation.
Now let’s compare it with the hand-written cl-who
code:
<#+begin_src lisp
POFTHEDAY> (defun render (title items) (cl-who:with-html-output-to-string (s) (:h1 (cl-who:esc title) (:ul (loop for item in items do (cl-who:htm (:li (cl-who:esc item))))))))
POFTHEDAY> (time (loop repeat 1000000 do (render “Foo Bar” ‘(“One” “Two” “Three”)))) Evaluation took: 1.644 seconds of real time 1.646934 seconds of total run time (1.619237 user, 0.027697 system) [ Run times consist of 0.130 seconds GC time, and 1.517 seconds non-GC time. ] 100.18% CPU 3,630,166,196 processor cycles 687,990,384 bytes consed
Well, we’ve received almost the same performance - 1.64 microseconds!
This library seems interesting to me. But lack of documentation does not allow to understand its limits. Probably it is very extensible. Hope, @blackenedgold will write more documentation or a tutorial someday.
This code will render us a chart:
;; Colors are taken from
;; https://www.html-color-names.com/
POFTHEDAY> (let* ((data '(("Jinja2 (Python)" 6.18 "Tomato")
("zenekindarl" 1.54 "MediumSeaGreen")
("cl-who" 1.64 "RoyalBlue")
("spinneret (pretty)" 4.94 "SandyBrown")
("spinneret" 1.08 "SandyBrown")
("cl-mustache" 5.21 "DeepPink")
("print-html" 3.26 "GreenYellow")
("djula" 4.48 "Gold")
("cl-emb" 1.44 "MediumSlateBlue")
("eco" 2.14 "Crimson")
("handwritten" 0.93 "DarkOrange")))
(data (sort data #'> :key #'second))
(baseline (second (first data)))
(base-width 600))
(cl-who:with-html-output-to-string (s)
(loop for (name value color) in data
for width = (* (/ base-width baseline)
value)
for style = (format nil "width: ~Apx; background-color: ~A; color: white; padding: 0.5em; display: inline-block;"
width
color)
do (cl-who:htm
(:div :style "margin-top: 1em;"
(:span :style style
(cl-who:esc name))
" – "
(:span (cl-who:esc (format nil "~A µs" value))))))))
This test was conducted on Macbook 2,2 GHz 6-Core Intel Core i7, with Python 3.7.7 and SBCL 2.0.8:
Shorter bar is better - it shows the library is faster:
<div style='margin-top: 1em;'><span style='width: 600.0px; background-color: Tomato; color: white; padding: 0.5em; display: inline-block;'>Jinja2 (Python)</span> – <span>6.18 µs</span></div><div style='margin-top: 1em;'><span style='width: 505.82526px; background-color: DeepPink; color: white; padding: 0.5em; display: inline-block;'>cl-mustache</span> – <span>5.21 µs</span></div><div style='margin-top: 1em;'><span style='width: 479.61166px; background-color: SandyBrown; color: white; padding: 0.5em; display: inline-block;'>spinneret (pretty)</span> – <span>4.94 µs</span></div><div style='margin-top: 1em;'><span style='width: 434.95148px; background-color: Gold; color: white; padding: 0.5em; display: inline-block;'>djula</span> – <span>4.48 µs</span></div><div style='margin-top: 1em;'><span style='width: 316.50485px; background-color: GreenYellow; color: white; padding: 0.5em; display: inline-block;'>print-html</span> – <span>3.26 µs</span></div><div style='margin-top: 1em;'><span style='width: 207.767px; background-color: Crimson; color: white; padding: 0.5em; display: inline-block;'>eco</span> – <span>2.14 µs</span></div><div style='margin-top: 1em;'><span style='width: 159.2233px; background-color: RoyalBlue; color: white; padding: 0.5em; display: inline-block;'>cl-who</span> – <span>1.64 µs</span></div><div style='margin-top: 1em;'><span style='width: 149.51456px; background-color: MediumSeaGreen; color: white; padding: 0.5em; display: inline-block;'>zenekindarl</span> – <span>1.54 µs</span></div><div style='margin-top: 1em;'><span style='width: 139.80583px; background-color: MediumSlateBlue; color: white; padding: 0.5em; display: inline-block;'>cl-emb</span> – <span>1.44 µs</span></div><div style='margin-top: 1em;'><span style='width: 104.85438px; background-color: SandyBrown; color: white; padding: 0.5em; display: inline-block;'>spinneret</span> – <span>1.08 µs</span></div><div style='margin-top: 1em;'><span style='width: 90.29127px; background-color: DarkOrange; color: white; padding: 0.5em; display: inline-block;'>handwritten</span> – <span>0.93 µs</span></div>
Performance results for Spinneret
were added to the chart. Code is in
the post #0189.
Performance results for cl-mustache
and print-html
were added to the chart. Code is in
the post #0190 and post #0049.
Added performance results for handwritten HTML generator and the djula
library. Also added the result of calling Spinneret
without pretty printing.
Added performance results for cl-emb.
Added performance results for eco.