Python 3.0: Templating Observations
Stumbling through Python-related posts on Planet Python led me to a curious Python 3.0 discovery.
First I noted phililon wrote Chameleon: byte compiler for ZPT and Genshi ("Fricken fast", apparently), which led me to Google Spitfire (supposed to be even faster - it's certainly very fast on Python 2.5), which led me to Google's implementation of the Genshi bigtable.py benchmark.
As I'm currently beta testing / porting my code base to be compatible with Python 3.0, and never having seen a performance test I didn't want to try or mangle the interpretation of, this naturally led me to check out my choice of web framework QP's default template package Qpy on Python 3.0.
Using the Google adaptation of the Genshi bigtable.py benchmark, I added an entry for QP; the benchmark results for packages which are included in or run on both Python 2.5 and Python 3.0 are interesting (update now including all the packages I have installed, Python 3.0 timing data derived from a run on RC3):
python2.5 test/bigtable.py
Genshi tag builder 1465.24 ms
Genshi template 965.37 ms
Genshi text template 579.29 ms
Genshi template + tag builder 1558.15 ms
Mako Template 250.65 ms
ElementTree 777.58 ms
Djange template 1175.98 ms
Spitfire template 112.41 ms
Spitfire template -O1 61.18 ms
Spitfire template -O2 28.34 ms
Spitfire template -O3 28.43 ms
cStringIO 44.26 ms
StringIO 151.16 ms
list concat 24.10 ms
QPY Template 136.12 ms
% python3.0 test/bigtable.py
ElementTree 449.98 ms
StringIO 152.47 ms
list concat 22.56 ms
QPY Template 37.89 ms
Sadly there isn't much to compare Qpy to on py3k at present. Hopefully other web frameworks, including the big ones, are busy at work producing code compatible with Python 2.x and 3.0. My purpose for doing the comparison was to see if Python 3.0 had a material effect on other templating packages, not so much to compare functionality of say Django templates to Qpy as their intended audience and feature set are so very different.
Incidentally the py3k improvement for Qpy represents a 71.3% speed-up for Qpy on Python 3.0rc1 over what was already amply quick on Python 2.5.1. Whatever the difference is, its not unique to Qpy: ElementTree also benefits greatly, turning in a 44.6% better performance.
These timings are done on an old desktop Unix workstation; more modern machines will turn in much better numbers but relatively speaking I believe these improvements will generally hold up.
On David Binger's Macbook Pro:
# py3k 22 ms
# 2.5.1 55 ms
from timeit import Timer
print(Timer('qpy_template()', 'from qpy.example.example2 import
qpy_template').timeit(1000))
Using timeit to get time for qpy_template() call:
25 ms Python2.5.1 with quote.so 50 ms Python2.5.1 without quote.so 11.7 ms Python3.0 with quote.so 44 ms Python 3.0 without quote.so Compiled speedup is 25/11.7 = 2.1 Uncompiled speedup is 50/44 = 1.1
David also notes:
When using profile to test template execution, the it looks like we get a speedup from 254 ms to 199 ms per qpy_template() call between Python 2.5.1 and Python 3.0. Nothing in the profile jumps out at me as being responsible for the change. The C code does, though, do some extra work for Python 2 to force the default encoding to be utf8. Maybe that adds up to a significant amount of time.
** Qpy?** The Qpy template model is going to look upside down if your experience or preference is with any of the many code-in-content template packages out there. Here's Qpy-enabled Python source compared to a couple of other templating approaches:
table = [dict(a=1,b=2,c=3,d=4,e=5,f=6,g=7,h=8,i=9,j=10)
for x in range(1000)]
# Qpy
def qpy_template:xml():
'<table>'
for row in table:
'<tr>'
for col in row.values():
'<td>%s</td>' % col
'</tr>'
'</table>'
# Genshi
def test_genshi():
stream = MarkupTemplate("""
<table xmlns:py="http://genshi.edgewall.org/">
<tr py:for="row in table">
<td py:for="c in row.values()" py:content="c"/>
</tr>
</table>""").generate(table=table)
stream.render('html', strip_whitespace=False)
# Django
def test_django():
context = DjangoContext({'table': table})
DjangoTemplate("""
<table>
{% for row in table %}
<tr>{% for col in row.values %}{{ col|escape }}{% endfor %}</tr>
{% endfor %}
</table>""").render(context)
Qpy makes writing quote-safe 'templates' as easy as writing Python code itself.
One of these days I'm sure to need a code-in-content solution and for raw performance alone the Cheetah inspired Google Spitfire is sure to be on my list of packages to look at. But I hope that day doesn't come soon as I've grown rather fond of the content-in-code approach of Qpy which allows me to write Python code that looks like Python code.