| [102] | 1 | # -*- encoding: utf-8 -*- |
|---|
| 2 | # Template language benchmarks |
|---|
| 3 | # |
|---|
| 4 | # Objective: Generate a 1000x10 HTML table as fast as possible. |
|---|
| 5 | # |
|---|
| 6 | # Author: Jonas Borgström <jonas@edgewall.com> |
|---|
| 7 | |
|---|
| [142] | 8 | import cgi |
|---|
| [102] | 9 | import sys |
|---|
| 10 | import timeit |
|---|
| [393] | 11 | from StringIO import StringIO |
|---|
| [287] | 12 | from genshi.builder import tag |
|---|
| [706] | 13 | from genshi.template import MarkupTemplate, NewTextTemplate |
|---|
| [102] | 14 | |
|---|
| [165] | 15 | try: |
|---|
| [409] | 16 | from elementtree import ElementTree as et |
|---|
| 17 | except ImportError: |
|---|
| 18 | et = None |
|---|
| 19 | |
|---|
| 20 | try: |
|---|
| 21 | import cElementTree as cet |
|---|
| 22 | except ImportError: |
|---|
| 23 | cet = None |
|---|
| 24 | |
|---|
| 25 | try: |
|---|
| [165] | 26 | import neo_cgi, neo_cs, neo_util |
|---|
| 27 | except ImportError: |
|---|
| 28 | neo_cgi = None |
|---|
| 29 | |
|---|
| 30 | try: |
|---|
| 31 | import kid |
|---|
| 32 | except ImportError: |
|---|
| 33 | kid = None |
|---|
| 34 | |
|---|
| 35 | try: |
|---|
| 36 | from django.conf import settings |
|---|
| 37 | settings.configure() |
|---|
| 38 | from django.template import Context as DjangoContext |
|---|
| 39 | from django.template import Template as DjangoTemplate |
|---|
| 40 | except ImportError: |
|---|
| 41 | DjangoContext = DjangoTemplate = None |
|---|
| 42 | |
|---|
| [393] | 43 | try: |
|---|
| [652] | 44 | from mako.template import Template as MakoTemplate |
|---|
| [393] | 45 | except ImportError: |
|---|
| [652] | 46 | MakoTemplate = None |
|---|
| [393] | 47 | |
|---|
| [102] | 48 | table = [dict(a=1,b=2,c=3,d=4,e=5,f=6,g=7,h=8,i=9,j=10) |
|---|
| 49 | for x in range(1000)] |
|---|
| 50 | |
|---|
| [292] | 51 | genshi_tmpl = MarkupTemplate(""" |
|---|
| [287] | 52 | <table xmlns:py="http://genshi.edgewall.org/"> |
|---|
| [102] | 53 | <tr py:for="row in table"> |
|---|
| 54 | <td py:for="c in row.values()" py:content="c"/> |
|---|
| 55 | </tr> |
|---|
| 56 | </table> |
|---|
| 57 | """) |
|---|
| 58 | |
|---|
| [292] | 59 | genshi_tmpl2 = MarkupTemplate(""" |
|---|
| [287] | 60 | <table xmlns:py="http://genshi.edgewall.org/">$table</table> |
|---|
| [102] | 61 | """) |
|---|
| 62 | |
|---|
| [706] | 63 | genshi_text_tmpl = NewTextTemplate(""" |
|---|
| 64 | <table> |
|---|
| 65 | {% for row in table %}<tr> |
|---|
| 66 | {% for c in row.values() %}<td>$c</td>{% end %} |
|---|
| 67 | </tr>{% end %} |
|---|
| 68 | </table> |
|---|
| 69 | """) |
|---|
| 70 | |
|---|
| [165] | 71 | if DjangoTemplate: |
|---|
| 72 | django_tmpl = DjangoTemplate(""" |
|---|
| 73 | <table> |
|---|
| 74 | {% for row in table %} |
|---|
| 75 | <tr>{% for col in row.values %}{{ col|escape }}{% endfor %}</tr> |
|---|
| 76 | {% endfor %} |
|---|
| 77 | </table> |
|---|
| 78 | """) |
|---|
| [102] | 79 | |
|---|
| [165] | 80 | def test_django(): |
|---|
| 81 | """Djange template""" |
|---|
| 82 | context = DjangoContext({'table': table}) |
|---|
| 83 | django_tmpl.render(context) |
|---|
| [102] | 84 | |
|---|
| [652] | 85 | if MakoTemplate: |
|---|
| 86 | mako_tmpl = MakoTemplate(""" |
|---|
| [393] | 87 | <table> |
|---|
| [652] | 88 | % for row in table: |
|---|
| 89 | <tr> |
|---|
| 90 | % for col in row.values(): |
|---|
| 91 | <td>${ col | h }</td> |
|---|
| 92 | % endfor |
|---|
| 93 | </tr> |
|---|
| 94 | % endfor |
|---|
| [393] | 95 | </table> |
|---|
| 96 | """) |
|---|
| [652] | 97 | def test_mako(): |
|---|
| 98 | """Mako Template""" |
|---|
| 99 | mako_tmpl.render(table=table) |
|---|
| [393] | 100 | |
|---|
| [287] | 101 | def test_genshi(): |
|---|
| 102 | """Genshi template""" |
|---|
| 103 | stream = genshi_tmpl.generate(table=table) |
|---|
| [154] | 104 | stream.render('html', strip_whitespace=False) |
|---|
| [102] | 105 | |
|---|
| [706] | 106 | def test_genshi_text(): |
|---|
| 107 | """Genshi text template""" |
|---|
| 108 | stream = genshi_text_tmpl.generate(table=table) |
|---|
| 109 | stream.render('text') |
|---|
| 110 | |
|---|
| [287] | 111 | def test_genshi_builder(): |
|---|
| 112 | """Genshi template + tag builder""" |
|---|
| [102] | 113 | stream = tag.TABLE([ |
|---|
| 114 | tag.tr([tag.td(c) for c in row.values()]) |
|---|
| 115 | for row in table |
|---|
| 116 | ]).generate() |
|---|
| [287] | 117 | stream = genshi_tmpl2.generate(table=stream) |
|---|
| [154] | 118 | stream.render('html', strip_whitespace=False) |
|---|
| [102] | 119 | |
|---|
| 120 | def test_builder(): |
|---|
| [287] | 121 | """Genshi tag builder""" |
|---|
| [102] | 122 | stream = tag.TABLE([ |
|---|
| 123 | tag.tr([ |
|---|
| 124 | tag.td(c) for c in row.values() |
|---|
| 125 | ]) |
|---|
| 126 | for row in table |
|---|
| 127 | ]).generate() |
|---|
| [154] | 128 | stream.render('html', strip_whitespace=False) |
|---|
| [102] | 129 | |
|---|
| [165] | 130 | if kid: |
|---|
| 131 | kid_tmpl = kid.Template(""" |
|---|
| 132 | <table xmlns:py="http://purl.org/kid/ns#"> |
|---|
| 133 | <tr py:for="row in table"> |
|---|
| 134 | <td py:for="c in row.values()" py:content="c"/> |
|---|
| 135 | </tr> |
|---|
| 136 | </table> |
|---|
| 137 | """) |
|---|
| [102] | 138 | |
|---|
| [165] | 139 | kid_tmpl2 = kid.Template(""" |
|---|
| 140 | <html xmlns:py="http://purl.org/kid/ns#">$table</html> |
|---|
| 141 | """) |
|---|
| [102] | 142 | |
|---|
| [165] | 143 | def test_kid(): |
|---|
| 144 | """Kid template""" |
|---|
| 145 | kid_tmpl.table = table |
|---|
| 146 | kid_tmpl.serialize(output='html') |
|---|
| 147 | |
|---|
| [409] | 148 | if cet: |
|---|
| 149 | def test_kid_et(): |
|---|
| 150 | """Kid template + cElementTree""" |
|---|
| 151 | _table = cet.Element('table') |
|---|
| 152 | for row in table: |
|---|
| 153 | td = cet.SubElement(_table, 'tr') |
|---|
| 154 | for c in row.values(): |
|---|
| 155 | cet.SubElement(td, 'td').text=str(c) |
|---|
| 156 | kid_tmpl2.table = _table |
|---|
| 157 | kid_tmpl2.serialize(output='html') |
|---|
| 158 | |
|---|
| 159 | if et: |
|---|
| 160 | def test_et(): |
|---|
| 161 | """ElementTree""" |
|---|
| 162 | _table = et.Element('table') |
|---|
| 163 | for row in table: |
|---|
| 164 | tr = et.SubElement(_table, 'tr') |
|---|
| 165 | for c in row.values(): |
|---|
| 166 | et.SubElement(tr, 'td').text=str(c) |
|---|
| 167 | et.tostring(_table) |
|---|
| 168 | |
|---|
| 169 | if cet: |
|---|
| 170 | def test_cet(): |
|---|
| 171 | """cElementTree""" |
|---|
| [165] | 172 | _table = cet.Element('table') |
|---|
| 173 | for row in table: |
|---|
| [409] | 174 | tr = cet.SubElement(_table, 'tr') |
|---|
| [165] | 175 | for c in row.values(): |
|---|
| [409] | 176 | cet.SubElement(tr, 'td').text=str(c) |
|---|
| 177 | cet.tostring(_table) |
|---|
| [165] | 178 | |
|---|
| 179 | if neo_cgi: |
|---|
| 180 | def test_clearsilver(): |
|---|
| 181 | """ClearSilver""" |
|---|
| 182 | hdf = neo_util.HDF() |
|---|
| 183 | for i, row in enumerate(table): |
|---|
| 184 | for j, c in enumerate(row.values()): |
|---|
| 185 | hdf.setValue("rows.%d.cell.%d" % (i, j), cgi.escape(str(c))) |
|---|
| [102] | 186 | |
|---|
| [165] | 187 | cs = neo_cs.CS(hdf) |
|---|
| 188 | cs.parseStr(""" |
|---|
| [102] | 189 | <table><?cs |
|---|
| 190 | each:row=rows |
|---|
| 191 | ?><tr><?cs each:c=row.cell |
|---|
| 192 | ?><td><?cs var:c ?></td><?cs /each |
|---|
| 193 | ?></tr><?cs /each?> |
|---|
| 194 | </table>""") |
|---|
| [165] | 195 | cs.render() |
|---|
| [102] | 196 | |
|---|
| 197 | |
|---|
| 198 | def run(which=None, number=10): |
|---|
| [706] | 199 | tests = ['test_builder', 'test_genshi', 'test_genshi_text', |
|---|
| 200 | 'test_genshi_builder', 'test_mako', 'test_kid', 'test_kid_et', |
|---|
| 201 | 'test_et', 'test_cet', 'test_clearsilver', 'test_django'] |
|---|
| [393] | 202 | |
|---|
| [102] | 203 | if which: |
|---|
| 204 | tests = filter(lambda n: n[5:] in which, tests) |
|---|
| 205 | |
|---|
| [165] | 206 | for test in [t for t in tests if hasattr(sys.modules[__name__], t)]: |
|---|
| [102] | 207 | t = timeit.Timer(setup='from __main__ import %s;' % test, |
|---|
| 208 | stmt='%s()' % test) |
|---|
| 209 | time = t.timeit(number=number) / number |
|---|
| 210 | |
|---|
| [165] | 211 | if time < 0.00001: |
|---|
| 212 | result = ' (not installed?)' |
|---|
| 213 | else: |
|---|
| 214 | result = '%16.2f ms' % (1000 * time) |
|---|
| 215 | print '%-35s %s' % (getattr(sys.modules[__name__], test).__doc__, result) |
|---|
| [102] | 216 | |
|---|
| 217 | |
|---|
| 218 | if __name__ == '__main__': |
|---|
| 219 | which = [arg for arg in sys.argv[1:] if arg[0] != '-'] |
|---|
| 220 | |
|---|
| 221 | if '-p' in sys.argv: |
|---|
| [1017] | 222 | import cProfile, pstats |
|---|
| 223 | prof = cProfile.Profile() |
|---|
| 224 | prof.run('run(%r, number=1)' % which) |
|---|
| 225 | stats = pstats.Stats(prof) |
|---|
| [102] | 226 | stats.strip_dirs() |
|---|
| 227 | stats.sort_stats('time', 'calls') |
|---|
| [1017] | 228 | stats.print_stats(25) |
|---|
| 229 | if '-v' in sys.argv: |
|---|
| 230 | stats.print_callees() |
|---|
| 231 | stats.print_callers() |
|---|
| [102] | 232 | else: |
|---|
| 233 | run(which) |
|---|