| Armin Ronacher | 32a910f | 2008-04-26 23:21:03 +0200 | [diff] [blame] | 1 | """\ |
| David Lord | 57626a0 | 2019-10-23 12:29:46 -0700 | [diff] [blame] | 2 | 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 Ronacher | 32a910f | 2008-04-26 23:21:03 +0200 | [diff] [blame] | 4 | template. If a template engine is not installed the test is skipped.\ |
| Armin Ronacher | 2feed1d | 2008-04-26 16:26:52 +0200 | [diff] [blame] | 5 | """ |
| Armin Ronacher | 32a910f | 2008-04-26 23:21:03 +0200 | [diff] [blame] | 6 | import cgi |
| David Lord | d177eeb | 2020-01-09 12:03:07 -0800 | [diff] [blame^] | 7 | import sys |
| Armin Ronacher | 00d5d21 | 2008-04-13 01:10:18 +0200 | [diff] [blame] | 8 | from timeit import Timer |
| David Lord | d177eeb | 2020-01-09 12:03:07 -0800 | [diff] [blame^] | 9 | |
| Armin Ronacher | 2feed1d | 2008-04-26 16:26:52 +0200 | [diff] [blame] | 10 | from jinja2 import Environment as JinjaEnvironment |
| Armin Ronacher | 449167d | 2008-04-11 17:55:05 +0200 | [diff] [blame] | 11 | |
| Armin Ronacher | de6bf71 | 2008-04-26 01:44:14 +0200 | [diff] [blame] | 12 | context = { |
| 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 Ronacher | 449167d | 2008-04-11 17:55:05 +0200 | [diff] [blame] | 16 | |
| Armin Ronacher | 00d5d21 | 2008-04-13 01:10:18 +0200 | [diff] [blame] | 17 | jinja_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 Ronacher | de6bf71 | 2008-04-26 01:44:14 +0200 | [diff] [blame] | 25 | <title>${page_title|e}</title> |
| Armin Ronacher | 00d5d21 | 2008-04-13 01:10:18 +0200 | [diff] [blame] | 26 | </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 Ronacher | 449167d | 2008-04-11 17:55:05 +0200 | [diff] [blame] | 53 | """) |
| Armin Ronacher | 00d5d21 | 2008-04-13 01:10:18 +0200 | [diff] [blame] | 54 | |
| Armin Ronacher | 2feed1d | 2008-04-26 16:26:52 +0200 | [diff] [blame] | 55 | def test_jinja(): |
| 56 | jinja_template.render(context) |
| 57 | |
| 58 | try: |
| Armin Ronacher | b4da9be | 2009-09-10 13:58:52 -0700 | [diff] [blame] | 59 | from tornado.template import Template |
| 60 | except ImportError: |
| 61 | test_tornado = None |
| 62 | else: |
| 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 | |
| 100 | try: |
| Armin Ronacher | 2feed1d | 2008-04-26 16:26:52 +0200 | [diff] [blame] | 101 | from django.conf import settings |
| 102 | settings.configure() |
| 103 | from django.template import Template as DjangoTemplate, Context as DjangoContext |
| 104 | except ImportError: |
| 105 | test_django = None |
| 106 | else: |
| Armin Ronacher | f60232d | 2010-06-05 14:31:27 +0200 | [diff] [blame] | 107 | django_template = DjangoTemplate("""\ |
| Armin Ronacher | 00d5d21 | 2008-04-13 01:10:18 +0200 | [diff] [blame] | 108 | <!doctype html> |
| 109 | <html> |
| 110 | <head> |
| Armin Ronacher | de6bf71 | 2008-04-26 01:44:14 +0200 | [diff] [blame] | 111 | <title>{{ page_title }}</title> |
| Armin Ronacher | 00d5d21 | 2008-04-13 01:10:18 +0200 | [diff] [blame] | 112 | </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 Ronacher | f60232d | 2010-06-05 14:31:27 +0200 | [diff] [blame] | 135 | """) |
| Armin Ronacher | 00d5d21 | 2008-04-13 01:10:18 +0200 | [diff] [blame] | 136 | |
| Armin Ronacher | 2feed1d | 2008-04-26 16:26:52 +0200 | [diff] [blame] | 137 | def test_django(): |
| 138 | c = DjangoContext(context) |
| 139 | c['navigation'] = [('index.html', 'Index'), ('downloads.html', 'Downloads'), |
| 140 | ('products.html', 'Products')] |
| Armin Ronacher | f60232d | 2010-06-05 14:31:27 +0200 | [diff] [blame] | 141 | django_template.render(c) |
| Armin Ronacher | 2feed1d | 2008-04-26 16:26:52 +0200 | [diff] [blame] | 142 | |
| 143 | try: |
| 144 | from mako.template import Template as MakoTemplate |
| 145 | except ImportError: |
| 146 | test_mako = None |
| 147 | else: |
| 148 | mako_template = MakoTemplate("""\ |
| Armin Ronacher | 00d5d21 | 2008-04-13 01:10:18 +0200 | [diff] [blame] | 149 | <!doctype html> |
| 150 | <html> |
| 151 | <head> |
| Armin Ronacher | de6bf71 | 2008-04-26 01:44:14 +0200 | [diff] [blame] | 152 | <title>${page_title|h}</title> |
| Armin Ronacher | 00d5d21 | 2008-04-13 01:10:18 +0200 | [diff] [blame] | 153 | </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 Ronacher | 2feed1d | 2008-04-26 16:26:52 +0200 | [diff] [blame] | 178 | def test_mako(): |
| 179 | mako_template.render(**context) |
| 180 | |
| 181 | try: |
| 182 | from genshi.template import MarkupTemplate as GenshiTemplate |
| 183 | except ImportError: |
| 184 | test_genshi = None |
| 185 | else: |
| 186 | genshi_template = GenshiTemplate("""\ |
| Armin Ronacher | de6bf71 | 2008-04-26 01:44:14 +0200 | [diff] [blame] | 187 | <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 Ronacher | 2feed1d | 2008-04-26 16:26:52 +0200 | [diff] [blame] | 212 | def test_genshi(): |
| 213 | genshi_template.generate(**context).render('html', strip_whitespace=False) |
| 214 | |
| 215 | try: |
| 216 | from Cheetah.Template import Template as CheetahTemplate |
| 217 | except ImportError: |
| 218 | test_cheetah = None |
| 219 | else: |
| 220 | cheetah_template = CheetahTemplate("""\ |
| Armin Ronacher | de6bf71 | 2008-04-26 01:44:14 +0200 | [diff] [blame] | 221 | #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 Ronacher | 00d5d21 | 2008-04-13 01:10:18 +0200 | [diff] [blame] | 250 | |
| Armin Ronacher | 2feed1d | 2008-04-26 16:26:52 +0200 | [diff] [blame] | 251 | def test_cheetah(): |
| 252 | unicode(cheetah_template) |
| Armin Ronacher | 00d5d21 | 2008-04-13 01:10:18 +0200 | [diff] [blame] | 253 | |
| Armin Ronacher | 2feed1d | 2008-04-26 16:26:52 +0200 | [diff] [blame] | 254 | try: |
| 255 | import tenjin |
| 256 | except ImportError: |
| 257 | test_tenjin = None |
| 258 | else: |
| 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 Ronacher | 00d5d21 | 2008-04-13 01:10:18 +0200 | [diff] [blame] | 289 | |
| Armin Ronacher | 2feed1d | 2008-04-26 16:26:52 +0200 | [diff] [blame] | 290 | def test_tenjin(): |
| 291 | from tenjin.helpers import escape, to_str |
| 292 | tenjin_template.render(context, locals()) |
| Armin Ronacher | 00d5d21 | 2008-04-13 01:10:18 +0200 | [diff] [blame] | 293 | |
| Armin Ronacher | 32a910f | 2008-04-26 23:21:03 +0200 | [diff] [blame] | 294 | try: |
| 295 | from spitfire.compiler import util as SpitfireTemplate |
| 296 | from spitfire.compiler.analyzer import o2_options as spitfire_optimizer |
| 297 | except ImportError: |
| 298 | test_spitfire = None |
| 299 | else: |
| 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 Moraes | 6cc2b23 | 2010-08-17 12:08:01 -0300 | [diff] [blame] | 334 | |
| 335 | try: |
| 336 | from chameleon.zpt.template import PageTemplate |
| 337 | except ImportError: |
| 338 | test_chameleon = None |
| 339 | else: |
| 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 | |
| 371 | try: |
| 372 | from chameleon.zpt.template import PageTemplate |
| 373 | from chameleon.genshi import language |
| 374 | except ImportError: |
| 375 | test_chameleon_genshi = None |
| 376 | else: |
| 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 Ronacher | 2feed1d | 2008-04-26 16:26:52 +0200 | [diff] [blame] | 409 | sys.stdout.write('\r' + '\n'.join(( |
| Armin Ronacher | de6bf71 | 2008-04-26 01:44:14 +0200 | [diff] [blame] | 410 | '=' * 80, |
| 411 | 'Template Engine BigTable Benchmark'.center(80), |
| Armin Ronacher | 32a910f | 2008-04-26 23:21:03 +0200 | [diff] [blame] | 412 | '=' * 80, |
| Armin Ronacher | 2feed1d | 2008-04-26 16:26:52 +0200 | [diff] [blame] | 413 | __doc__, |
| Armin Ronacher | de6bf71 | 2008-04-26 01:44:14 +0200 | [diff] [blame] | 414 | '-' * 80 |
| Armin Ronacher | 2feed1d | 2008-04-26 16:26:52 +0200 | [diff] [blame] | 415 | )) + '\n') |
| Armin Ronacher | c9705c2 | 2008-04-27 21:28:03 +0200 | [diff] [blame] | 416 | |
| 417 | |
| Rodrigo Moraes | 6cc2b23 | 2010-08-17 12:08:01 -0300 | [diff] [blame] | 418 | for test in 'jinja', 'mako', 'tornado', 'tenjin', 'spitfire', 'django', 'genshi', 'cheetah', 'chameleon', 'chameleon_genshi': |
| Armin Ronacher | 2feed1d | 2008-04-26 16:26:52 +0200 | [diff] [blame] | 419 | if locals()['test_' + test] is None: |
| Armin Ronacher | 32a910f | 2008-04-26 23:21:03 +0200 | [diff] [blame] | 420 | sys.stdout.write(' %-20s*not installed*\n' % test) |
| Armin Ronacher | 2feed1d | 2008-04-26 16:26:52 +0200 | [diff] [blame] | 421 | continue |
| Armin Ronacher | 00d5d21 | 2008-04-13 01:10:18 +0200 | [diff] [blame] | 422 | t = Timer(setup='from __main__ import test_%s as bench' % test, |
| 423 | stmt='bench()') |
| Armin Ronacher | 32a910f | 2008-04-26 23:21:03 +0200 | [diff] [blame] | 424 | sys.stdout.write(' >> %-20s<running>' % test) |
| Armin Ronacher | de6bf71 | 2008-04-26 01:44:14 +0200 | [diff] [blame] | 425 | sys.stdout.flush() |
| Armin Ronacher | 19cf9c2 | 2008-05-01 12:49:53 +0200 | [diff] [blame] | 426 | sys.stdout.write('\r %-20s%.4f seconds\n' % (test, t.timeit(number=50) / 50)) |
| Armin Ronacher | 32a910f | 2008-04-26 23:21:03 +0200 | [diff] [blame] | 427 | sys.stdout.write('-' * 80 + '\n') |
| 428 | sys.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') |