| Armin Ronacher | 32a910f | 2008-04-26 23:21:03 +0200 | [diff] [blame] | 1 | """\ |
| Armin Ronacher | 2feed1d | 2008-04-26 16:26:52 +0200 | [diff] [blame] | 2 | This benchmark compares some python templating engines with Jinja 2 so |
| 3 | that we get a picture of how fast Jinja 2 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 | de6bf71 | 2008-04-26 01:44:14 +0200 | [diff] [blame] | 6 | import sys |
| Armin Ronacher | 32a910f | 2008-04-26 23:21:03 +0200 | [diff] [blame] | 7 | import cgi |
| Armin Ronacher | 00d5d21 | 2008-04-13 01:10:18 +0200 | [diff] [blame] | 8 | from timeit import Timer |
| Armin Ronacher | 2feed1d | 2008-04-26 16:26:52 +0200 | [diff] [blame] | 9 | from jinja2 import Environment as JinjaEnvironment |
| Armin Ronacher | 449167d | 2008-04-11 17:55:05 +0200 | [diff] [blame] | 10 | |
| Armin Ronacher | de6bf71 | 2008-04-26 01:44:14 +0200 | [diff] [blame] | 11 | context = { |
| 12 | 'page_title': 'mitsuhiko\'s benchmark', |
| 13 | '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)] |
| 14 | } |
| Armin Ronacher | 449167d | 2008-04-11 17:55:05 +0200 | [diff] [blame] | 15 | |
| Armin Ronacher | 00d5d21 | 2008-04-13 01:10:18 +0200 | [diff] [blame] | 16 | jinja_template = JinjaEnvironment( |
| 17 | line_statement_prefix='%', |
| 18 | variable_start_string="${", |
| 19 | variable_end_string="}" |
| 20 | ).from_string("""\ |
| 21 | <!doctype html> |
| 22 | <html> |
| 23 | <head> |
| Armin Ronacher | de6bf71 | 2008-04-26 01:44:14 +0200 | [diff] [blame] | 24 | <title>${page_title|e}</title> |
| Armin Ronacher | 00d5d21 | 2008-04-13 01:10:18 +0200 | [diff] [blame] | 25 | </head> |
| 26 | <body> |
| 27 | <div class="header"> |
| 28 | <h1>${page_title|e}</h1> |
| 29 | </div> |
| 30 | <ul class="navigation"> |
| 31 | % for href, caption in [ |
| 32 | ('index.html', 'Index'), |
| 33 | ('downloads.html', 'Downloads'), |
| 34 | ('products.html', 'Products') |
| 35 | ] |
| 36 | <li><a href="${href|e}">${caption|e}</a></li> |
| 37 | % endfor |
| 38 | </ul> |
| 39 | <div class="table"> |
| 40 | <table> |
| 41 | % for row in table |
| 42 | <tr> |
| 43 | % for cell in row |
| 44 | <td>${cell}</td> |
| 45 | % endfor |
| 46 | </tr> |
| 47 | % endfor |
| 48 | </table> |
| 49 | </div> |
| 50 | </body> |
| 51 | </html>\ |
| Armin Ronacher | 449167d | 2008-04-11 17:55:05 +0200 | [diff] [blame] | 52 | """) |
| Armin Ronacher | 00d5d21 | 2008-04-13 01:10:18 +0200 | [diff] [blame] | 53 | |
| Armin Ronacher | 2feed1d | 2008-04-26 16:26:52 +0200 | [diff] [blame] | 54 | def test_jinja(): |
| 55 | jinja_template.render(context) |
| 56 | |
| 57 | try: |
| Armin Ronacher | b4da9be | 2009-09-10 13:58:52 -0700 | [diff] [blame^] | 58 | from tornado.template import Template |
| 59 | except ImportError: |
| 60 | test_tornado = None |
| 61 | else: |
| 62 | tornado_template = Template("""\ |
| 63 | <!doctype html> |
| 64 | <html> |
| 65 | <head> |
| 66 | <title>{{ page_title }}</title> |
| 67 | </head> |
| 68 | <body> |
| 69 | <div class="header"> |
| 70 | <h1>{{ page_title }}</h1> |
| 71 | </div> |
| 72 | <ul class="navigation"> |
| 73 | {% for href, caption in [ \ |
| 74 | ('index.html', 'Index'), \ |
| 75 | ('downloads.html', 'Downloads'), \ |
| 76 | ('products.html', 'Products') \ |
| 77 | ] %} |
| 78 | <li><a href="{{ href }}">{{ caption }}</a></li> |
| 79 | {% end %} |
| 80 | </ul> |
| 81 | <div class="table"> |
| 82 | <table> |
| 83 | {% for row in table %} |
| 84 | <tr> |
| 85 | {% for cell in row %} |
| 86 | <td>{{ cell }}</td> |
| 87 | {% end %} |
| 88 | </tr> |
| 89 | {% end %} |
| 90 | </table> |
| 91 | </div> |
| 92 | </body> |
| 93 | </html>\ |
| 94 | """) |
| 95 | |
| 96 | def test_tornado(): |
| 97 | tornado_template.generate(**context) |
| 98 | |
| 99 | try: |
| Armin Ronacher | 2feed1d | 2008-04-26 16:26:52 +0200 | [diff] [blame] | 100 | from django.conf import settings |
| 101 | settings.configure() |
| 102 | from django.template import Template as DjangoTemplate, Context as DjangoContext |
| 103 | except ImportError: |
| 104 | test_django = None |
| 105 | else: |
| Armin Ronacher | c347ed0 | 2008-09-20 12:04:53 +0200 | [diff] [blame] | 106 | django_template = """\ |
| Armin Ronacher | 00d5d21 | 2008-04-13 01:10:18 +0200 | [diff] [blame] | 107 | <!doctype html> |
| 108 | <html> |
| 109 | <head> |
| Armin Ronacher | de6bf71 | 2008-04-26 01:44:14 +0200 | [diff] [blame] | 110 | <title>{{ page_title }}</title> |
| Armin Ronacher | 00d5d21 | 2008-04-13 01:10:18 +0200 | [diff] [blame] | 111 | </head> |
| 112 | <body> |
| 113 | <div class="header"> |
| 114 | <h1>{{ page_title }}</h1> |
| 115 | </div> |
| 116 | <ul class="navigation"> |
| 117 | {% for href, caption in navigation %} |
| 118 | <li><a href="{{ href }}">{{ caption }}</a></li> |
| 119 | {% endfor %} |
| 120 | </ul> |
| 121 | <div class="table"> |
| 122 | <table> |
| 123 | {% for row in table %} |
| 124 | <tr> |
| 125 | {% for cell in row %} |
| 126 | <td>{{ cell }}</td> |
| 127 | {% endfor %} |
| 128 | </tr> |
| 129 | {% endfor %} |
| 130 | </table> |
| 131 | </div> |
| 132 | </body> |
| 133 | </html>\ |
| Armin Ronacher | c347ed0 | 2008-09-20 12:04:53 +0200 | [diff] [blame] | 134 | """ |
| Armin Ronacher | 00d5d21 | 2008-04-13 01:10:18 +0200 | [diff] [blame] | 135 | |
| Armin Ronacher | 2feed1d | 2008-04-26 16:26:52 +0200 | [diff] [blame] | 136 | def test_django(): |
| 137 | c = DjangoContext(context) |
| 138 | c['navigation'] = [('index.html', 'Index'), ('downloads.html', 'Downloads'), |
| 139 | ('products.html', 'Products')] |
| Armin Ronacher | c347ed0 | 2008-09-20 12:04:53 +0200 | [diff] [blame] | 140 | # recompile template each rendering because that's what django |
| 141 | # is doing in normal situations too. Django is not thread safe |
| 142 | # so we can't cache it in regular apps either. |
| 143 | DjangoTemplate(django_template).render(c) |
| Armin Ronacher | 2feed1d | 2008-04-26 16:26:52 +0200 | [diff] [blame] | 144 | |
| 145 | try: |
| 146 | from mako.template import Template as MakoTemplate |
| 147 | except ImportError: |
| 148 | test_mako = None |
| 149 | else: |
| 150 | mako_template = MakoTemplate("""\ |
| Armin Ronacher | 00d5d21 | 2008-04-13 01:10:18 +0200 | [diff] [blame] | 151 | <!doctype html> |
| 152 | <html> |
| 153 | <head> |
| Armin Ronacher | de6bf71 | 2008-04-26 01:44:14 +0200 | [diff] [blame] | 154 | <title>${page_title|h}</title> |
| Armin Ronacher | 00d5d21 | 2008-04-13 01:10:18 +0200 | [diff] [blame] | 155 | </head> |
| 156 | <body> |
| 157 | <div class="header"> |
| 158 | <h1>${page_title|h}</h1> |
| 159 | </div> |
| 160 | <ul class="navigation"> |
| 161 | % for href, caption in [('index.html', 'Index'), ('downloads.html', 'Downloads'), ('products.html', 'Products')]: |
| 162 | <li><a href="${href|h}">${caption|h}</a></li> |
| 163 | % endfor |
| 164 | </ul> |
| 165 | <div class="table"> |
| 166 | <table> |
| 167 | % for row in table: |
| 168 | <tr> |
| 169 | % for cell in row: |
| 170 | <td>${cell}</td> |
| 171 | % endfor |
| 172 | </tr> |
| 173 | % endfor |
| 174 | </table> |
| 175 | </div> |
| 176 | </body> |
| 177 | </html>\ |
| 178 | """) |
| 179 | |
| Armin Ronacher | 2feed1d | 2008-04-26 16:26:52 +0200 | [diff] [blame] | 180 | def test_mako(): |
| 181 | mako_template.render(**context) |
| 182 | |
| 183 | try: |
| 184 | from genshi.template import MarkupTemplate as GenshiTemplate |
| 185 | except ImportError: |
| 186 | test_genshi = None |
| 187 | else: |
| 188 | genshi_template = GenshiTemplate("""\ |
| Armin Ronacher | de6bf71 | 2008-04-26 01:44:14 +0200 | [diff] [blame] | 189 | <html xmlns="http://www.w3.org/1999/xhtml" xmlns:py="http://genshi.edgewall.org/"> |
| 190 | <head> |
| 191 | <title>${page_title}</title> |
| 192 | </head> |
| 193 | <body> |
| 194 | <div class="header"> |
| 195 | <h1>${page_title}</h1> |
| 196 | </div> |
| 197 | <ul class="navigation"> |
| 198 | <li py:for="href, caption in [ |
| 199 | ('index.html', 'Index'), |
| 200 | ('downloads.html', 'Downloads'), |
| 201 | ('products.html', 'Products')]"><a href="${href}">${caption}</a></li> |
| 202 | </ul> |
| 203 | <div class="table"> |
| 204 | <table> |
| 205 | <tr py:for="row in table"> |
| 206 | <td py:for="cell in row">${cell}</td> |
| 207 | </tr> |
| 208 | </table> |
| 209 | </div> |
| 210 | </body> |
| 211 | </html>\ |
| 212 | """) |
| 213 | |
| Armin Ronacher | 2feed1d | 2008-04-26 16:26:52 +0200 | [diff] [blame] | 214 | def test_genshi(): |
| 215 | genshi_template.generate(**context).render('html', strip_whitespace=False) |
| 216 | |
| 217 | try: |
| 218 | from Cheetah.Template import Template as CheetahTemplate |
| 219 | except ImportError: |
| 220 | test_cheetah = None |
| 221 | else: |
| 222 | cheetah_template = CheetahTemplate("""\ |
| Armin Ronacher | de6bf71 | 2008-04-26 01:44:14 +0200 | [diff] [blame] | 223 | #import cgi |
| 224 | <!doctype html> |
| 225 | <html> |
| 226 | <head> |
| 227 | <title>$cgi.escape($page_title)</title> |
| 228 | </head> |
| 229 | <body> |
| 230 | <div class="header"> |
| 231 | <h1>$cgi.escape($page_title)</h1> |
| 232 | </div> |
| 233 | <ul class="navigation"> |
| 234 | #for $href, $caption in [('index.html', 'Index'), ('downloads.html', 'Downloads'), ('products.html', 'Products')]: |
| 235 | <li><a href="$cgi.escape($href)">$cgi.escape($caption)</a></li> |
| 236 | #end for |
| 237 | </ul> |
| 238 | <div class="table"> |
| 239 | <table> |
| 240 | #for $row in $table: |
| 241 | <tr> |
| 242 | #for $cell in $row: |
| 243 | <td>$cell</td> |
| 244 | #end for |
| 245 | </tr> |
| 246 | #end for |
| 247 | </table> |
| 248 | </div> |
| 249 | </body> |
| 250 | </html>\ |
| 251 | """, searchList=[dict(context)]) |
| Armin Ronacher | 00d5d21 | 2008-04-13 01:10:18 +0200 | [diff] [blame] | 252 | |
| Armin Ronacher | 2feed1d | 2008-04-26 16:26:52 +0200 | [diff] [blame] | 253 | def test_cheetah(): |
| 254 | unicode(cheetah_template) |
| Armin Ronacher | 00d5d21 | 2008-04-13 01:10:18 +0200 | [diff] [blame] | 255 | |
| Armin Ronacher | 2feed1d | 2008-04-26 16:26:52 +0200 | [diff] [blame] | 256 | try: |
| 257 | import tenjin |
| 258 | except ImportError: |
| 259 | test_tenjin = None |
| 260 | else: |
| 261 | tenjin_template = tenjin.Template() |
| 262 | tenjin_template.convert("""\ |
| 263 | <!doctype html> |
| 264 | <html> |
| 265 | <head> |
| 266 | <title>${page_title}</title> |
| 267 | </head> |
| 268 | <body> |
| 269 | <div class="header"> |
| 270 | <h1>${page_title}</h1> |
| 271 | </div> |
| 272 | <ul class="navigation"> |
| 273 | <?py for href, caption in [('index.html', 'Index'), ('downloads.html', 'Downloads'), ('products.html', 'Products')]: ?> |
| 274 | <li><a href="${href}">${caption}</a></li> |
| 275 | <?py #end ?> |
| 276 | </ul> |
| 277 | <div class="table"> |
| 278 | <table> |
| 279 | <?py for row in table: ?> |
| 280 | <tr> |
| 281 | <?py for cell in row: ?> |
| 282 | <td>#{cell}</td> |
| 283 | <?py #end ?> |
| 284 | </tr> |
| 285 | <?py #end ?> |
| 286 | </table> |
| 287 | </div> |
| 288 | </body> |
| 289 | </html>\ |
| 290 | """) |
| Armin Ronacher | 00d5d21 | 2008-04-13 01:10:18 +0200 | [diff] [blame] | 291 | |
| Armin Ronacher | 2feed1d | 2008-04-26 16:26:52 +0200 | [diff] [blame] | 292 | def test_tenjin(): |
| 293 | from tenjin.helpers import escape, to_str |
| 294 | tenjin_template.render(context, locals()) |
| Armin Ronacher | 00d5d21 | 2008-04-13 01:10:18 +0200 | [diff] [blame] | 295 | |
| Armin Ronacher | 32a910f | 2008-04-26 23:21:03 +0200 | [diff] [blame] | 296 | try: |
| 297 | from spitfire.compiler import util as SpitfireTemplate |
| 298 | from spitfire.compiler.analyzer import o2_options as spitfire_optimizer |
| 299 | except ImportError: |
| 300 | test_spitfire = None |
| 301 | else: |
| 302 | spitfire_template = SpitfireTemplate.load_template("""\ |
| 303 | <!doctype html> |
| 304 | <html> |
| 305 | <head> |
| 306 | <title>$cgi.escape($page_title)</title> |
| 307 | </head> |
| 308 | <body> |
| 309 | <div class="header"> |
| 310 | <h1>$cgi.escape($page_title)</h1> |
| 311 | </div> |
| 312 | <ul class="navigation"> |
| 313 | #for $href, $caption in [('index.html', 'Index'), ('downloads.html', 'Downloads'), ('products.html', 'Products')] |
| 314 | <li><a href="$cgi.escape($href)">$cgi.escape($caption)</a></li> |
| 315 | #end for |
| 316 | </ul> |
| 317 | <div class="table"> |
| 318 | <table> |
| 319 | #for $row in $table |
| 320 | <tr> |
| 321 | #for $cell in $row |
| 322 | <td>$cell</td> |
| 323 | #end for |
| 324 | </tr> |
| 325 | #end for |
| 326 | </table> |
| 327 | </div> |
| 328 | </body> |
| 329 | </html>\ |
| 330 | """, 'spitfire_tmpl', spitfire_optimizer, {'enable_filters': False}) |
| 331 | spitfire_context = dict(context, **{'cgi': cgi}) |
| 332 | |
| 333 | def test_spitfire(): |
| 334 | spitfire_template(search_list=[spitfire_context]).main() |
| 335 | |
| Armin Ronacher | 2feed1d | 2008-04-26 16:26:52 +0200 | [diff] [blame] | 336 | sys.stdout.write('\r' + '\n'.join(( |
| Armin Ronacher | de6bf71 | 2008-04-26 01:44:14 +0200 | [diff] [blame] | 337 | '=' * 80, |
| 338 | 'Template Engine BigTable Benchmark'.center(80), |
| Armin Ronacher | 32a910f | 2008-04-26 23:21:03 +0200 | [diff] [blame] | 339 | '=' * 80, |
| Armin Ronacher | 2feed1d | 2008-04-26 16:26:52 +0200 | [diff] [blame] | 340 | __doc__, |
| Armin Ronacher | de6bf71 | 2008-04-26 01:44:14 +0200 | [diff] [blame] | 341 | '-' * 80 |
| Armin Ronacher | 2feed1d | 2008-04-26 16:26:52 +0200 | [diff] [blame] | 342 | )) + '\n') |
| Armin Ronacher | c9705c2 | 2008-04-27 21:28:03 +0200 | [diff] [blame] | 343 | |
| 344 | |
| Armin Ronacher | b4da9be | 2009-09-10 13:58:52 -0700 | [diff] [blame^] | 345 | for test in 'jinja', 'mako', 'tornado', 'tenjin', 'spitfire', 'django', 'genshi', 'cheetah': |
| Armin Ronacher | 2feed1d | 2008-04-26 16:26:52 +0200 | [diff] [blame] | 346 | if locals()['test_' + test] is None: |
| Armin Ronacher | 32a910f | 2008-04-26 23:21:03 +0200 | [diff] [blame] | 347 | sys.stdout.write(' %-20s*not installed*\n' % test) |
| Armin Ronacher | 2feed1d | 2008-04-26 16:26:52 +0200 | [diff] [blame] | 348 | continue |
| Armin Ronacher | 00d5d21 | 2008-04-13 01:10:18 +0200 | [diff] [blame] | 349 | t = Timer(setup='from __main__ import test_%s as bench' % test, |
| 350 | stmt='bench()') |
| Armin Ronacher | 32a910f | 2008-04-26 23:21:03 +0200 | [diff] [blame] | 351 | sys.stdout.write(' >> %-20s<running>' % test) |
| Armin Ronacher | de6bf71 | 2008-04-26 01:44:14 +0200 | [diff] [blame] | 352 | sys.stdout.flush() |
| Armin Ronacher | 19cf9c2 | 2008-05-01 12:49:53 +0200 | [diff] [blame] | 353 | 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] | 354 | sys.stdout.write('-' * 80 + '\n') |
| 355 | sys.stdout.write('''\ |
| 356 | WARNING: The results of this benchmark are useless to compare the |
| 357 | performance of template engines and should not be taken seriously in any |
| 358 | way. It's testing the performance of simple loops and has no real-world |
| 359 | usefulnes. It only used to check if changes on the Jinja code affect |
| 360 | performance in a good or bad way and how it roughly compares to others. |
| 361 | ''' + '=' * 80 + '\n') |