blob: 7d988cd94eb99e563c64bc8c539d5f554a0721d0 [file] [log] [blame]
Armin Ronacher32a910f2008-04-26 23:21:03 +02001"""\
David Lord57626a02019-10-23 12:29:46 -07002 This benchmark compares some python templating engines with Jinja so
3 that we get a picture of how fast Jinja is for a semi real world
Armin Ronacher32a910f2008-04-26 23:21:03 +02004 template. If a template engine is not installed the test is skipped.\
Armin Ronacher2feed1d2008-04-26 16:26:52 +02005"""
Armin Ronacher32a910f2008-04-26 23:21:03 +02006import cgi
David Lordd177eeb2020-01-09 12:03:07 -08007import sys
Armin Ronacher00d5d212008-04-13 01:10:18 +02008from timeit import Timer
David Lordd177eeb2020-01-09 12:03:07 -08009
Armin Ronacher2feed1d2008-04-26 16:26:52 +020010from jinja2 import Environment as JinjaEnvironment
Armin Ronacher449167d2008-04-11 17:55:05 +020011
Armin Ronacherde6bf712008-04-26 01:44:14 +020012context = {
13 'page_title': 'mitsuhiko\'s benchmark',
14 '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)]
15}
Armin Ronacher449167d2008-04-11 17:55:05 +020016
Armin Ronacher00d5d212008-04-13 01:10:18 +020017jinja_template = JinjaEnvironment(
18 line_statement_prefix='%',
19 variable_start_string="${",
20 variable_end_string="}"
21).from_string("""\
22<!doctype html>
23<html>
24 <head>
Armin Ronacherde6bf712008-04-26 01:44:14 +020025 <title>${page_title|e}</title>
Armin Ronacher00d5d212008-04-13 01:10:18 +020026 </head>
27 <body>
28 <div class="header">
29 <h1>${page_title|e}</h1>
30 </div>
31 <ul class="navigation">
32 % for href, caption in [
33 ('index.html', 'Index'),
34 ('downloads.html', 'Downloads'),
35 ('products.html', 'Products')
36 ]
37 <li><a href="${href|e}">${caption|e}</a></li>
38 % endfor
39 </ul>
40 <div class="table">
41 <table>
42 % for row in table
43 <tr>
44 % for cell in row
45 <td>${cell}</td>
46 % endfor
47 </tr>
48 % endfor
49 </table>
50 </div>
51 </body>
52</html>\
Armin Ronacher449167d2008-04-11 17:55:05 +020053""")
Armin Ronacher00d5d212008-04-13 01:10:18 +020054
Armin Ronacher2feed1d2008-04-26 16:26:52 +020055def test_jinja():
56 jinja_template.render(context)
57
58try:
Armin Ronacherb4da9be2009-09-10 13:58:52 -070059 from tornado.template import Template
60except ImportError:
61 test_tornado = None
62else:
63 tornado_template = Template("""\
64<!doctype html>
65<html>
66 <head>
67 <title>{{ page_title }}</title>
68 </head>
69 <body>
70 <div class="header">
71 <h1>{{ page_title }}</h1>
72 </div>
73 <ul class="navigation">
74 {% for href, caption in [ \
75 ('index.html', 'Index'), \
76 ('downloads.html', 'Downloads'), \
77 ('products.html', 'Products') \
78 ] %}
79 <li><a href="{{ href }}">{{ caption }}</a></li>
80 {% end %}
81 </ul>
82 <div class="table">
83 <table>
84 {% for row in table %}
85 <tr>
86 {% for cell in row %}
87 <td>{{ cell }}</td>
88 {% end %}
89 </tr>
90 {% end %}
91 </table>
92 </div>
93 </body>
94</html>\
95""")
96
97 def test_tornado():
98 tornado_template.generate(**context)
99
100try:
Armin Ronacher2feed1d2008-04-26 16:26:52 +0200101 from django.conf import settings
102 settings.configure()
103 from django.template import Template as DjangoTemplate, Context as DjangoContext
104except ImportError:
105 test_django = None
106else:
Armin Ronacherf60232d2010-06-05 14:31:27 +0200107 django_template = DjangoTemplate("""\
Armin Ronacher00d5d212008-04-13 01:10:18 +0200108<!doctype html>
109<html>
110 <head>
Armin Ronacherde6bf712008-04-26 01:44:14 +0200111 <title>{{ page_title }}</title>
Armin Ronacher00d5d212008-04-13 01:10:18 +0200112 </head>
113 <body>
114 <div class="header">
115 <h1>{{ page_title }}</h1>
116 </div>
117 <ul class="navigation">
118 {% for href, caption in navigation %}
119 <li><a href="{{ href }}">{{ caption }}</a></li>
120 {% endfor %}
121 </ul>
122 <div class="table">
123 <table>
124 {% for row in table %}
125 <tr>
126 {% for cell in row %}
127 <td>{{ cell }}</td>
128 {% endfor %}
129 </tr>
130 {% endfor %}
131 </table>
132 </div>
133 </body>
134</html>\
Armin Ronacherf60232d2010-06-05 14:31:27 +0200135""")
Armin Ronacher00d5d212008-04-13 01:10:18 +0200136
Armin Ronacher2feed1d2008-04-26 16:26:52 +0200137 def test_django():
138 c = DjangoContext(context)
139 c['navigation'] = [('index.html', 'Index'), ('downloads.html', 'Downloads'),
140 ('products.html', 'Products')]
Armin Ronacherf60232d2010-06-05 14:31:27 +0200141 django_template.render(c)
Armin Ronacher2feed1d2008-04-26 16:26:52 +0200142
143try:
144 from mako.template import Template as MakoTemplate
145except ImportError:
146 test_mako = None
147else:
148 mako_template = MakoTemplate("""\
Armin Ronacher00d5d212008-04-13 01:10:18 +0200149<!doctype html>
150<html>
151 <head>
Armin Ronacherde6bf712008-04-26 01:44:14 +0200152 <title>${page_title|h}</title>
Armin Ronacher00d5d212008-04-13 01:10:18 +0200153 </head>
154 <body>
155 <div class="header">
156 <h1>${page_title|h}</h1>
157 </div>
158 <ul class="navigation">
159 % for href, caption in [('index.html', 'Index'), ('downloads.html', 'Downloads'), ('products.html', 'Products')]:
160 <li><a href="${href|h}">${caption|h}</a></li>
161 % endfor
162 </ul>
163 <div class="table">
164 <table>
165 % for row in table:
166 <tr>
167 % for cell in row:
168 <td>${cell}</td>
169 % endfor
170 </tr>
171 % endfor
172 </table>
173 </div>
174 </body>
175</html>\
176""")
177
Armin Ronacher2feed1d2008-04-26 16:26:52 +0200178 def test_mako():
179 mako_template.render(**context)
180
181try:
182 from genshi.template import MarkupTemplate as GenshiTemplate
183except ImportError:
184 test_genshi = None
185else:
186 genshi_template = GenshiTemplate("""\
Armin Ronacherde6bf712008-04-26 01:44:14 +0200187<html xmlns="http://www.w3.org/1999/xhtml" xmlns:py="http://genshi.edgewall.org/">
188 <head>
189 <title>${page_title}</title>
190 </head>
191 <body>
192 <div class="header">
193 <h1>${page_title}</h1>
194 </div>
195 <ul class="navigation">
196 <li py:for="href, caption in [
197 ('index.html', 'Index'),
198 ('downloads.html', 'Downloads'),
199 ('products.html', 'Products')]"><a href="${href}">${caption}</a></li>
200 </ul>
201 <div class="table">
202 <table>
203 <tr py:for="row in table">
204 <td py:for="cell in row">${cell}</td>
205 </tr>
206 </table>
207 </div>
208 </body>
209</html>\
210""")
211
Armin Ronacher2feed1d2008-04-26 16:26:52 +0200212 def test_genshi():
213 genshi_template.generate(**context).render('html', strip_whitespace=False)
214
215try:
216 from Cheetah.Template import Template as CheetahTemplate
217except ImportError:
218 test_cheetah = None
219else:
220 cheetah_template = CheetahTemplate("""\
Armin Ronacherde6bf712008-04-26 01:44:14 +0200221#import cgi
222<!doctype html>
223<html>
224 <head>
225 <title>$cgi.escape($page_title)</title>
226 </head>
227 <body>
228 <div class="header">
229 <h1>$cgi.escape($page_title)</h1>
230 </div>
231 <ul class="navigation">
232 #for $href, $caption in [('index.html', 'Index'), ('downloads.html', 'Downloads'), ('products.html', 'Products')]:
233 <li><a href="$cgi.escape($href)">$cgi.escape($caption)</a></li>
234 #end for
235 </ul>
236 <div class="table">
237 <table>
238 #for $row in $table:
239 <tr>
240 #for $cell in $row:
241 <td>$cell</td>
242 #end for
243 </tr>
244 #end for
245 </table>
246 </div>
247 </body>
248</html>\
249""", searchList=[dict(context)])
Armin Ronacher00d5d212008-04-13 01:10:18 +0200250
Armin Ronacher2feed1d2008-04-26 16:26:52 +0200251 def test_cheetah():
252 unicode(cheetah_template)
Armin Ronacher00d5d212008-04-13 01:10:18 +0200253
Armin Ronacher2feed1d2008-04-26 16:26:52 +0200254try:
255 import tenjin
256except ImportError:
257 test_tenjin = None
258else:
259 tenjin_template = tenjin.Template()
260 tenjin_template.convert("""\
261<!doctype html>
262<html>
263 <head>
264 <title>${page_title}</title>
265 </head>
266 <body>
267 <div class="header">
268 <h1>${page_title}</h1>
269 </div>
270 <ul class="navigation">
271<?py for href, caption in [('index.html', 'Index'), ('downloads.html', 'Downloads'), ('products.html', 'Products')]: ?>
272 <li><a href="${href}">${caption}</a></li>
273<?py #end ?>
274 </ul>
275 <div class="table">
276 <table>
277<?py for row in table: ?>
278 <tr>
279<?py for cell in row: ?>
280 <td>#{cell}</td>
281<?py #end ?>
282 </tr>
283<?py #end ?>
284 </table>
285 </div>
286 </body>
287</html>\
288""")
Armin Ronacher00d5d212008-04-13 01:10:18 +0200289
Armin Ronacher2feed1d2008-04-26 16:26:52 +0200290 def test_tenjin():
291 from tenjin.helpers import escape, to_str
292 tenjin_template.render(context, locals())
Armin Ronacher00d5d212008-04-13 01:10:18 +0200293
Armin Ronacher32a910f2008-04-26 23:21:03 +0200294try:
295 from spitfire.compiler import util as SpitfireTemplate
296 from spitfire.compiler.analyzer import o2_options as spitfire_optimizer
297except ImportError:
298 test_spitfire = None
299else:
300 spitfire_template = SpitfireTemplate.load_template("""\
301<!doctype html>
302<html>
303 <head>
304 <title>$cgi.escape($page_title)</title>
305 </head>
306 <body>
307 <div class="header">
308 <h1>$cgi.escape($page_title)</h1>
309 </div>
310 <ul class="navigation">
311 #for $href, $caption in [('index.html', 'Index'), ('downloads.html', 'Downloads'), ('products.html', 'Products')]
312 <li><a href="$cgi.escape($href)">$cgi.escape($caption)</a></li>
313 #end for
314 </ul>
315 <div class="table">
316 <table>
317 #for $row in $table
318 <tr>
319 #for $cell in $row
320 <td>$cell</td>
321 #end for
322 </tr>
323 #end for
324 </table>
325 </div>
326 </body>
327</html>\
328""", 'spitfire_tmpl', spitfire_optimizer, {'enable_filters': False})
329 spitfire_context = dict(context, **{'cgi': cgi})
330
331 def test_spitfire():
332 spitfire_template(search_list=[spitfire_context]).main()
333
Rodrigo Moraes6cc2b232010-08-17 12:08:01 -0300334
335try:
336 from chameleon.zpt.template import PageTemplate
337except ImportError:
338 test_chameleon = None
339else:
340 chameleon_template = PageTemplate("""\
341<html xmlns:tal="http://xml.zope.org/namespaces/tal">
342 <head>
343 <title tal:content="page_title">Page Title</title>
344 </head>
345 <body>
346 <div class="header">
347 <h1 tal:content="page_title">Page Title</h1>
348 </div>
349 <ul class="navigation">
350 <li tal:repeat="item sections"><a tal:attributes="href item[0]" tal:content="item[1]">caption</a></li>
351 </ul>
352 <div class="table">
353 <table>
354 <tr tal:repeat="row table">
355 <td tal:repeat="cell row" tal:content="row[cell]">cell</td>
356 </tr>
357 </table>
358 </div>
359 </body>
360</html>\
361""")
362 chameleon_context = dict(context)
363 chameleon_context['sections'] = [
364 ('index.html', 'Index'),
365 ('downloads.html', 'Downloads'),
366 ('products.html', 'Products')
367 ]
368 def test_chameleon():
369 chameleon_template.render(**chameleon_context)
370
371try:
372 from chameleon.zpt.template import PageTemplate
373 from chameleon.genshi import language
374except ImportError:
375 test_chameleon_genshi = None
376else:
377 chameleon_genshi_template = PageTemplate("""\
378<html xmlns="http://www.w3.org/1999/xhtml" xmlns:py="http://genshi.edgewall.org/">
379 <head>
380 <title>${page_title}</title>
381 </head>
382 <body>
383 <div class="header">
384 <h1>${page_title}</h1>
385 </div>
386 <ul class="navigation">
387 <li py:for="info in sections"><a href="${info[0]}">${info[1]}</a></li>
388 </ul>
389 <div class="table">
390 <table>
391 <tr py:for="row in table">
392 <td py:for="cell in row">${row[cell]}</td>
393 </tr>
394 </table>
395 </div>
396 </body>
397</html>\
398""", parser=language.Parser())
399 chameleon_genshi_context = dict(context)
400 chameleon_genshi_context['sections'] = [
401 ('index.html', 'Index'),
402 ('downloads.html', 'Downloads'),
403 ('products.html', 'Products')
404 ]
405 def test_chameleon_genshi():
406 chameleon_genshi_template.render(**chameleon_genshi_context)
407
408
Armin Ronacher2feed1d2008-04-26 16:26:52 +0200409sys.stdout.write('\r' + '\n'.join((
Armin Ronacherde6bf712008-04-26 01:44:14 +0200410 '=' * 80,
411 'Template Engine BigTable Benchmark'.center(80),
Armin Ronacher32a910f2008-04-26 23:21:03 +0200412 '=' * 80,
Armin Ronacher2feed1d2008-04-26 16:26:52 +0200413 __doc__,
Armin Ronacherde6bf712008-04-26 01:44:14 +0200414 '-' * 80
Armin Ronacher2feed1d2008-04-26 16:26:52 +0200415)) + '\n')
Armin Ronacherc9705c22008-04-27 21:28:03 +0200416
417
Rodrigo Moraes6cc2b232010-08-17 12:08:01 -0300418for test in 'jinja', 'mako', 'tornado', 'tenjin', 'spitfire', 'django', 'genshi', 'cheetah', 'chameleon', 'chameleon_genshi':
Armin Ronacher2feed1d2008-04-26 16:26:52 +0200419 if locals()['test_' + test] is None:
Armin Ronacher32a910f2008-04-26 23:21:03 +0200420 sys.stdout.write(' %-20s*not installed*\n' % test)
Armin Ronacher2feed1d2008-04-26 16:26:52 +0200421 continue
Armin Ronacher00d5d212008-04-13 01:10:18 +0200422 t = Timer(setup='from __main__ import test_%s as bench' % test,
423 stmt='bench()')
Armin Ronacher32a910f2008-04-26 23:21:03 +0200424 sys.stdout.write(' >> %-20s<running>' % test)
Armin Ronacherde6bf712008-04-26 01:44:14 +0200425 sys.stdout.flush()
Armin Ronacher19cf9c22008-05-01 12:49:53 +0200426 sys.stdout.write('\r %-20s%.4f seconds\n' % (test, t.timeit(number=50) / 50))
Armin Ronacher32a910f2008-04-26 23:21:03 +0200427sys.stdout.write('-' * 80 + '\n')
428sys.stdout.write('''\
429 WARNING: The results of this benchmark are useless to compare the
430 performance of template engines and should not be taken seriously in any
431 way. It's testing the performance of simple loops and has no real-world
432 usefulnes. It only used to check if changes on the Jinja code affect
433 performance in a good or bad way and how it roughly compares to others.
434''' + '=' * 80 + '\n')