some performance improvements
authorArmin Ronacher <armin.ronacher@active-4.com>
Fri, 25 Apr 2008 23:44:14 +0000 (01:44 +0200)
committerArmin Ronacher <armin.ronacher@active-4.com>
Fri, 25 Apr 2008 23:44:14 +0000 (01:44 +0200)
--HG--
branch : trunk

examples/bench.py
jinja2/compiler.py
jinja2/environment.py
jinja2/optimizer.py
jinja2/runtime.py

index 0e6b3072243a250bbd6b1f70467f765f15f424f3..9073758fddacec088b205de3e266927c1d1ceb9d 100644 (file)
@@ -1,10 +1,17 @@
+import sys
 from django.conf import settings
 settings.configure()
 from django.template import Template as DjangoTemplate, Context as DjangoContext
 from jinja2 import Environment as JinjaEnvironment
 from mako.template import Template as MakoTemplate
+from genshi.template import MarkupTemplate as GenshiTemplate
+from Cheetah.Template import Template as CheetahTemplate
 from timeit import Timer
 
+context = {
+    'page_title': 'mitsuhiko\'s benchmark',
+    '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)]
+}
 
 jinja_template = JinjaEnvironment(
     line_statement_prefix='%',
@@ -14,7 +21,7 @@ jinja_template = JinjaEnvironment(
 <!doctype html>
 <html>
   <head>
-    <title>${page_title|e}
+    <title>${page_title|e}</title>
   </head>
   <body>
     <div class="header">
@@ -48,7 +55,7 @@ django_template = DjangoTemplate("""\
 <!doctype html>
 <html>
   <head>
-    <title>{{ page_title }}
+    <title>{{ page_title }}</title>
   </head>
   <body>
     <div class="header">
@@ -78,7 +85,7 @@ mako_template = MakoTemplate("""\
 <!doctype html>
 <html>
   <head>
-    <title>${page_title|h}
+    <title>${page_title|h}</title>
   </head>
   <body>
     <div class="header">
@@ -104,10 +111,62 @@ mako_template = MakoTemplate("""\
 </html>\
 """)
 
-context = {
-    'page_title': 'mitsuhiko\'s benchmark',
-    '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)]
-}
+genshi_template = GenshiTemplate("""\
+<html xmlns="http://www.w3.org/1999/xhtml" xmlns:py="http://genshi.edgewall.org/">
+  <head>
+    <title>${page_title}</title>
+  </head>
+  <body>
+    <div class="header">
+      <h1>${page_title}</h1>
+    </div>
+    <ul class="navigation">
+      <li py:for="href, caption in [
+        ('index.html', 'Index'),
+        ('downloads.html', 'Downloads'),
+        ('products.html', 'Products')]"><a href="${href}">${caption}</a></li>
+    </ul>
+    <div class="table">
+      <table>
+        <tr py:for="row in table">
+          <td py:for="cell in row">${cell}</td>
+        </tr>
+      </table>
+    </div>
+  </body>
+</html>\
+""")
+
+cheetah_template = CheetahTemplate("""\
+#import cgi
+<!doctype html>
+<html>
+  <head>
+    <title>$cgi.escape($page_title)</title>
+  </head>
+  <body>
+    <div class="header">
+      <h1>$cgi.escape($page_title)</h1>
+    </div>
+    <ul class="navigation">
+    #for $href, $caption in [('index.html', 'Index'), ('downloads.html', 'Downloads'), ('products.html', 'Products')]:
+      <li><a href="$cgi.escape($href)">$cgi.escape($caption)</a></li>
+    #end for
+    </ul>
+    <div class="table">
+      <table>
+      #for $row in $table:
+        <tr>
+        #for $cell in $row:
+          <td>$cell</td>
+        #end for
+        </tr>
+      #end for
+      </table>
+    </div>
+  </body>
+</html>\
+""", searchList=[dict(context)])
 
 def test_jinja():
     jinja_template.render(context)
@@ -120,8 +179,21 @@ def test_django():
 def test_mako():
     mako_template.render(**context)
 
+def test_genshi():
+    genshi_template.generate(**context).render('html', strip_whitespace=False)
+
+def test_cheetah():
+    unicode(cheetah_template)
 
-for test in 'jinja', 'mako', 'django':
+sys.stdout.write('\r%s\n%s\n%s\n' % (
+    '=' * 80,
+    'Template Engine BigTable Benchmark'.center(80),
+    '-' * 80
+))
+for test in 'jinja', 'mako', 'django', 'genshi', 'cheetah':
     t = Timer(setup='from __main__ import test_%s as bench' % test,
               stmt='bench()')
-    print '%-20s%.4fms' % (test, t.timeit(number=20) / 20)
+    sys.stdout.write('> %-20s<running>' % test)
+    sys.stdout.flush()
+    sys.stdout.write('\r  %-20s%.4f ms\n' % (test, t.timeit(number=100) / 100))
+sys.stdout.write('=' * 80 + '\n')
index 958b2c3e6e2243bb826fc647ef222e376e6a1eb2..bc5163b885393dbf60ae57fab77ac3510c89964d 100644 (file)
@@ -14,7 +14,7 @@ from cStringIO import StringIO
 from jinja2 import nodes
 from jinja2.visitor import NodeVisitor, NodeTransformer
 from jinja2.exceptions import TemplateAssertionError
-from jinja2.runtime import StaticLoopContext
+from jinja2.runtime import StaticLoopContext, concat
 from jinja2.utils import Markup
 
 
@@ -728,7 +728,7 @@ class CodeGenerator(NodeVisitor):
         self.pull_locals(macro_frame, indent=False)
         self.writeline('%s = []' % buf)
         self.blockvisit(node.body, macro_frame, indent=False)
-        self.writeline("return Markup(u''.join(%s))" % buf)
+        self.writeline("return Markup(concat(%s))" % buf)
         self.outdent()
         self.newline()
         if frame.toplevel:
@@ -756,7 +756,7 @@ class CodeGenerator(NodeVisitor):
         self.pull_locals(call_frame, indent=False)
         self.writeline('%s = []' % buf)
         self.blockvisit(node.body, call_frame, indent=False)
-        self.writeline("return Markup(u''.join(%s))" % buf)
+        self.writeline("return Markup(concat(%s))" % buf)
         self.outdent()
         arg_tuple = ', '.join(repr(x.name) for x in node.args)
         if len(node.args) == 1:
@@ -794,7 +794,7 @@ class CodeGenerator(NodeVisitor):
             self.writeline('yield ', node)
         else:
             self.writeline('%s.append(' % frame.buffer, node)
-        self.visit_Filter(node.filter, filter_frame, "u''.join(%s)" % buf)
+        self.visit_Filter(node.filter, filter_frame, 'concat(%s)' % buf)
         if frame.buffer is not None:
             self.write(')')
 
@@ -842,7 +842,7 @@ class CodeGenerator(NodeVisitor):
         if len(body) < 3:
             for item in body:
                 if isinstance(item, list):
-                    val = repr(u''.join(item))
+                    val = repr(concat(item))
                     if frame.buffer is None:
                         self.writeline('yield ' + val)
                     else:
@@ -863,7 +863,7 @@ class CodeGenerator(NodeVisitor):
             arguments = []
             for item in body:
                 if isinstance(item, list):
-                    format.append(u''.join(item).replace('%', '%%'))
+                    format.append(concat(item).replace('%', '%%'))
                 else:
                     format.append('%s')
                     arguments.append(item)
@@ -871,7 +871,7 @@ class CodeGenerator(NodeVisitor):
                 self.writeline('yield ')
             else:
                 self.writeline('%s.append(' % frame.buffer)
-            self.write(repr(u''.join(format)) + ' % (')
+            self.write(repr(concat(format)) + ' % (')
             idx = -1
             self.indent()
             for argument in arguments:
index 239193e5049d6fd86e3503424c7cfc895f430e0c..5325c2b40d0dde1f1dafb855ecf3a9a45d671d45 100644 (file)
@@ -13,7 +13,7 @@ from jinja2.lexer import Lexer
 from jinja2.parser import Parser
 from jinja2.optimizer import optimize
 from jinja2.compiler import generate
-from jinja2.runtime import Undefined, TemplateContext
+from jinja2.runtime import Undefined, TemplateContext, concat
 from jinja2.debug import translate_exception
 from jinja2.utils import import_string, LRUCache, Markup
 from jinja2.defaults import DEFAULT_FILTERS, DEFAULT_TESTS, DEFAULT_NAMESPACE
@@ -301,7 +301,7 @@ class Template(object):
     def render(self, *args, **kwargs):
         """Render the template into a string."""
         try:
-            return u''.join(self.generate(*args, **kwargs))
+            return concat(self.generate(*args, **kwargs))
         except:
             # hide the `generate` frame
             exc_type, exc_value, tb = sys.exc_info()
@@ -395,7 +395,7 @@ class IncludedTemplate(object):
     """Represents an included template."""
 
     def __init__(self, template, context):
-        body = Markup(u''.join(template.root_render_func(context)))
+        body = Markup(concat(template.root_render_func(context)))
         self.__dict__.update(context.get_exported())
         self._name = template.name
         self._rendered_body = body
@@ -448,7 +448,7 @@ class TemplateStream(object):
                 except StopIteration:
                     if not c_size:
                         raise
-                yield u''.join(buf)
+                yield concat(buf)
                 del buf[:]
                 c_size = 0
 
index c432b3b1f895ff9bec69d1261a00458682375e48..f52b77f8e5b0c84b7218a73a06780cedb6732a4b 100644 (file)
@@ -18,7 +18,7 @@
 """
 from jinja2 import nodes
 from jinja2.visitor import NodeVisitor, NodeTransformer
-from jinja2.runtime import LoopContext
+from jinja2.runtime import LoopContext, concat
 
 
 def optimize(node, environment, context_hint=None):
@@ -111,7 +111,7 @@ class Optimizer(NodeTransformer):
 
         # now check if we can evaluate the filter at compile time.
         try:
-            data = node.filter.as_const(u''.join(buffer))
+            data = node.filter.as_const(concat(buffer))
         except nodes.Impossible:
             return node
 
index 7860dcc0d83a396ede390f55d751a7488e05431e..8f0e1cc70ff0da5e7a7adbb685918c493990487b 100644 (file)
@@ -14,13 +14,17 @@ from jinja2.exceptions import UndefinedError
 
 
 __all__ = ['LoopContext', 'StaticLoopContext', 'TemplateContext',
-           'Macro', 'Markup', 'missing']
+           'Macro', 'Markup', 'missing', 'concat']
 
 
 # special singleton representing missing values for the runtime
 missing = object()
 
 
+# concatenate a list of strings and convert them to unicode.
+concat = u''.join
+
+
 class TemplateContext(object):
     """Holds the variables of the local template or of the global one.  It's
     not save to use this class outside of the compiled code.  For example
@@ -116,7 +120,7 @@ class SuperBlock(object):
         self._render_func = render_func
 
     def __call__(self):
-        return Markup(u''.join(self._render_func(self._context)))
+        return Markup(concat(self._render_func(self._context)))
 
     def __repr__(self):
         return '<%s %r>' % (