| 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: |
| 58 | from django.conf import settings |
| 59 | settings.configure() |
| 60 | from django.template import Template as DjangoTemplate, Context as DjangoContext |
| 61 | except ImportError: |
| 62 | test_django = None |
| 63 | else: |
| 64 | django_template = DjangoTemplate("""\ |
| Armin Ronacher | 00d5d21 | 2008-04-13 01:10:18 +0200 | [diff] [blame] | 65 | <!doctype html> |
| 66 | <html> |
| 67 | <head> |
| Armin Ronacher | de6bf71 | 2008-04-26 01:44:14 +0200 | [diff] [blame] | 68 | <title>{{ page_title }}</title> |
| Armin Ronacher | 00d5d21 | 2008-04-13 01:10:18 +0200 | [diff] [blame] | 69 | </head> |
| 70 | <body> |
| 71 | <div class="header"> |
| 72 | <h1>{{ page_title }}</h1> |
| 73 | </div> |
| 74 | <ul class="navigation"> |
| 75 | {% for href, caption in navigation %} |
| 76 | <li><a href="{{ href }}">{{ caption }}</a></li> |
| 77 | {% endfor %} |
| 78 | </ul> |
| 79 | <div class="table"> |
| 80 | <table> |
| 81 | {% for row in table %} |
| 82 | <tr> |
| 83 | {% for cell in row %} |
| 84 | <td>{{ cell }}</td> |
| 85 | {% endfor %} |
| 86 | </tr> |
| 87 | {% endfor %} |
| 88 | </table> |
| 89 | </div> |
| 90 | </body> |
| 91 | </html>\ |
| 92 | """) |
| 93 | |
| Armin Ronacher | 2feed1d | 2008-04-26 16:26:52 +0200 | [diff] [blame] | 94 | def test_django(): |
| 95 | c = DjangoContext(context) |
| 96 | c['navigation'] = [('index.html', 'Index'), ('downloads.html', 'Downloads'), |
| 97 | ('products.html', 'Products')] |
| 98 | django_template.render(c) |
| 99 | |
| 100 | try: |
| 101 | from mako.template import Template as MakoTemplate |
| 102 | except ImportError: |
| 103 | test_mako = None |
| 104 | else: |
| 105 | mako_template = MakoTemplate("""\ |
| Armin Ronacher | 00d5d21 | 2008-04-13 01:10:18 +0200 | [diff] [blame] | 106 | <!doctype html> |
| 107 | <html> |
| 108 | <head> |
| Armin Ronacher | de6bf71 | 2008-04-26 01:44:14 +0200 | [diff] [blame] | 109 | <title>${page_title|h}</title> |
| Armin Ronacher | 00d5d21 | 2008-04-13 01:10:18 +0200 | [diff] [blame] | 110 | </head> |
| 111 | <body> |
| 112 | <div class="header"> |
| 113 | <h1>${page_title|h}</h1> |
| 114 | </div> |
| 115 | <ul class="navigation"> |
| 116 | % for href, caption in [('index.html', 'Index'), ('downloads.html', 'Downloads'), ('products.html', 'Products')]: |
| 117 | <li><a href="${href|h}">${caption|h}</a></li> |
| 118 | % endfor |
| 119 | </ul> |
| 120 | <div class="table"> |
| 121 | <table> |
| 122 | % for row in table: |
| 123 | <tr> |
| 124 | % for cell in row: |
| 125 | <td>${cell}</td> |
| 126 | % endfor |
| 127 | </tr> |
| 128 | % endfor |
| 129 | </table> |
| 130 | </div> |
| 131 | </body> |
| 132 | </html>\ |
| 133 | """) |
| 134 | |
| Armin Ronacher | 2feed1d | 2008-04-26 16:26:52 +0200 | [diff] [blame] | 135 | def test_mako(): |
| 136 | mako_template.render(**context) |
| 137 | |
| 138 | try: |
| 139 | from genshi.template import MarkupTemplate as GenshiTemplate |
| 140 | except ImportError: |
| 141 | test_genshi = None |
| 142 | else: |
| 143 | genshi_template = GenshiTemplate("""\ |
| Armin Ronacher | de6bf71 | 2008-04-26 01:44:14 +0200 | [diff] [blame] | 144 | <html xmlns="http://www.w3.org/1999/xhtml" xmlns:py="http://genshi.edgewall.org/"> |
| 145 | <head> |
| 146 | <title>${page_title}</title> |
| 147 | </head> |
| 148 | <body> |
| 149 | <div class="header"> |
| 150 | <h1>${page_title}</h1> |
| 151 | </div> |
| 152 | <ul class="navigation"> |
| 153 | <li py:for="href, caption in [ |
| 154 | ('index.html', 'Index'), |
| 155 | ('downloads.html', 'Downloads'), |
| 156 | ('products.html', 'Products')]"><a href="${href}">${caption}</a></li> |
| 157 | </ul> |
| 158 | <div class="table"> |
| 159 | <table> |
| 160 | <tr py:for="row in table"> |
| 161 | <td py:for="cell in row">${cell}</td> |
| 162 | </tr> |
| 163 | </table> |
| 164 | </div> |
| 165 | </body> |
| 166 | </html>\ |
| 167 | """) |
| 168 | |
| Armin Ronacher | 2feed1d | 2008-04-26 16:26:52 +0200 | [diff] [blame] | 169 | def test_genshi(): |
| 170 | genshi_template.generate(**context).render('html', strip_whitespace=False) |
| 171 | |
| 172 | try: |
| 173 | from Cheetah.Template import Template as CheetahTemplate |
| 174 | except ImportError: |
| 175 | test_cheetah = None |
| 176 | else: |
| 177 | cheetah_template = CheetahTemplate("""\ |
| Armin Ronacher | de6bf71 | 2008-04-26 01:44:14 +0200 | [diff] [blame] | 178 | #import cgi |
| 179 | <!doctype html> |
| 180 | <html> |
| 181 | <head> |
| 182 | <title>$cgi.escape($page_title)</title> |
| 183 | </head> |
| 184 | <body> |
| 185 | <div class="header"> |
| 186 | <h1>$cgi.escape($page_title)</h1> |
| 187 | </div> |
| 188 | <ul class="navigation"> |
| 189 | #for $href, $caption in [('index.html', 'Index'), ('downloads.html', 'Downloads'), ('products.html', 'Products')]: |
| 190 | <li><a href="$cgi.escape($href)">$cgi.escape($caption)</a></li> |
| 191 | #end for |
| 192 | </ul> |
| 193 | <div class="table"> |
| 194 | <table> |
| 195 | #for $row in $table: |
| 196 | <tr> |
| 197 | #for $cell in $row: |
| 198 | <td>$cell</td> |
| 199 | #end for |
| 200 | </tr> |
| 201 | #end for |
| 202 | </table> |
| 203 | </div> |
| 204 | </body> |
| 205 | </html>\ |
| 206 | """, searchList=[dict(context)]) |
| Armin Ronacher | 00d5d21 | 2008-04-13 01:10:18 +0200 | [diff] [blame] | 207 | |
| Armin Ronacher | 2feed1d | 2008-04-26 16:26:52 +0200 | [diff] [blame] | 208 | def test_cheetah(): |
| 209 | unicode(cheetah_template) |
| Armin Ronacher | 00d5d21 | 2008-04-13 01:10:18 +0200 | [diff] [blame] | 210 | |
| Armin Ronacher | 2feed1d | 2008-04-26 16:26:52 +0200 | [diff] [blame] | 211 | try: |
| 212 | import tenjin |
| 213 | except ImportError: |
| 214 | test_tenjin = None |
| 215 | else: |
| 216 | tenjin_template = tenjin.Template() |
| 217 | tenjin_template.convert("""\ |
| 218 | <!doctype html> |
| 219 | <html> |
| 220 | <head> |
| 221 | <title>${page_title}</title> |
| 222 | </head> |
| 223 | <body> |
| 224 | <div class="header"> |
| 225 | <h1>${page_title}</h1> |
| 226 | </div> |
| 227 | <ul class="navigation"> |
| 228 | <?py for href, caption in [('index.html', 'Index'), ('downloads.html', 'Downloads'), ('products.html', 'Products')]: ?> |
| 229 | <li><a href="${href}">${caption}</a></li> |
| 230 | <?py #end ?> |
| 231 | </ul> |
| 232 | <div class="table"> |
| 233 | <table> |
| 234 | <?py for row in table: ?> |
| 235 | <tr> |
| 236 | <?py for cell in row: ?> |
| 237 | <td>#{cell}</td> |
| 238 | <?py #end ?> |
| 239 | </tr> |
| 240 | <?py #end ?> |
| 241 | </table> |
| 242 | </div> |
| 243 | </body> |
| 244 | </html>\ |
| 245 | """) |
| Armin Ronacher | 00d5d21 | 2008-04-13 01:10:18 +0200 | [diff] [blame] | 246 | |
| Armin Ronacher | 2feed1d | 2008-04-26 16:26:52 +0200 | [diff] [blame] | 247 | def test_tenjin(): |
| 248 | from tenjin.helpers import escape, to_str |
| 249 | tenjin_template.render(context, locals()) |
| Armin Ronacher | 00d5d21 | 2008-04-13 01:10:18 +0200 | [diff] [blame] | 250 | |
| Armin Ronacher | 32a910f | 2008-04-26 23:21:03 +0200 | [diff] [blame^] | 251 | try: |
| 252 | from spitfire.compiler import util as SpitfireTemplate |
| 253 | from spitfire.compiler.analyzer import o2_options as spitfire_optimizer |
| 254 | except ImportError: |
| 255 | test_spitfire = None |
| 256 | else: |
| 257 | spitfire_template = SpitfireTemplate.load_template("""\ |
| 258 | <!doctype html> |
| 259 | <html> |
| 260 | <head> |
| 261 | <title>$cgi.escape($page_title)</title> |
| 262 | </head> |
| 263 | <body> |
| 264 | <div class="header"> |
| 265 | <h1>$cgi.escape($page_title)</h1> |
| 266 | </div> |
| 267 | <ul class="navigation"> |
| 268 | #for $href, $caption in [('index.html', 'Index'), ('downloads.html', 'Downloads'), ('products.html', 'Products')] |
| 269 | <li><a href="$cgi.escape($href)">$cgi.escape($caption)</a></li> |
| 270 | #end for |
| 271 | </ul> |
| 272 | <div class="table"> |
| 273 | <table> |
| 274 | #for $row in $table |
| 275 | <tr> |
| 276 | #for $cell in $row |
| 277 | <td>$cell</td> |
| 278 | #end for |
| 279 | </tr> |
| 280 | #end for |
| 281 | </table> |
| 282 | </div> |
| 283 | </body> |
| 284 | </html>\ |
| 285 | """, 'spitfire_tmpl', spitfire_optimizer, {'enable_filters': False}) |
| 286 | spitfire_context = dict(context, **{'cgi': cgi}) |
| 287 | |
| 288 | def test_spitfire(): |
| 289 | spitfire_template(search_list=[spitfire_context]).main() |
| 290 | |
| Armin Ronacher | 2feed1d | 2008-04-26 16:26:52 +0200 | [diff] [blame] | 291 | sys.stdout.write('\r' + '\n'.join(( |
| Armin Ronacher | de6bf71 | 2008-04-26 01:44:14 +0200 | [diff] [blame] | 292 | '=' * 80, |
| 293 | 'Template Engine BigTable Benchmark'.center(80), |
| Armin Ronacher | 32a910f | 2008-04-26 23:21:03 +0200 | [diff] [blame^] | 294 | '=' * 80, |
| Armin Ronacher | 2feed1d | 2008-04-26 16:26:52 +0200 | [diff] [blame] | 295 | __doc__, |
| Armin Ronacher | de6bf71 | 2008-04-26 01:44:14 +0200 | [diff] [blame] | 296 | '-' * 80 |
| Armin Ronacher | 2feed1d | 2008-04-26 16:26:52 +0200 | [diff] [blame] | 297 | )) + '\n') |
| Armin Ronacher | 32a910f | 2008-04-26 23:21:03 +0200 | [diff] [blame^] | 298 | for test in 'jinja', 'tenjin', 'mako', 'spitfire', 'django', 'genshi', 'cheetah': |
| Armin Ronacher | 2feed1d | 2008-04-26 16:26:52 +0200 | [diff] [blame] | 299 | if locals()['test_' + test] is None: |
| Armin Ronacher | 32a910f | 2008-04-26 23:21:03 +0200 | [diff] [blame^] | 300 | sys.stdout.write(' %-20s*not installed*\n' % test) |
| Armin Ronacher | 2feed1d | 2008-04-26 16:26:52 +0200 | [diff] [blame] | 301 | continue |
| Armin Ronacher | 00d5d21 | 2008-04-13 01:10:18 +0200 | [diff] [blame] | 302 | t = Timer(setup='from __main__ import test_%s as bench' % test, |
| 303 | stmt='bench()') |
| Armin Ronacher | 32a910f | 2008-04-26 23:21:03 +0200 | [diff] [blame^] | 304 | sys.stdout.write(' >> %-20s<running>' % test) |
| Armin Ronacher | de6bf71 | 2008-04-26 01:44:14 +0200 | [diff] [blame] | 305 | sys.stdout.flush() |
| Armin Ronacher | 32a910f | 2008-04-26 23:21:03 +0200 | [diff] [blame^] | 306 | sys.stdout.write('\r %-20s%.4f seconds\n' % (test, t.timeit(number=20) / 20)) |
| 307 | sys.stdout.write('-' * 80 + '\n') |
| 308 | sys.stdout.write('''\ |
| 309 | WARNING: The results of this benchmark are useless to compare the |
| 310 | performance of template engines and should not be taken seriously in any |
| 311 | way. It's testing the performance of simple loops and has no real-world |
| 312 | usefulnes. It only used to check if changes on the Jinja code affect |
| 313 | performance in a good or bad way and how it roughly compares to others. |
| 314 | ''' + '=' * 80 + '\n') |