From: Armin Ronacher Date: Fri, 25 Apr 2008 23:44:14 +0000 (+0200) Subject: some performance improvements X-Git-Tag: 2.0rc1~133 X-Git-Url: http://git.tremily.us/?a=commitdiff_plain;h=de6bf71e8f22cac9b0a06016b23b7dd64913226b;p=jinja2.git some performance improvements --HG-- branch : trunk --- diff --git a/examples/bench.py b/examples/bench.py index 0e6b307..9073758 100644 --- a/examples/bench.py +++ b/examples/bench.py @@ -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( - ${page_title|e} + <title>${page_title|e}
@@ -48,7 +55,7 @@ django_template = DjangoTemplate("""\ - {{ page_title }} + <title>{{ page_title }}
@@ -78,7 +85,7 @@ mako_template = MakoTemplate("""\ - ${page_title|h} + <title>${page_title|h}
@@ -104,10 +111,62 @@ mako_template = MakoTemplate("""\ \ """) -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("""\ + + + ${page_title} + + +
+

${page_title}

+
+ +
+ + + + +
${cell}
+
+ +\ +""") + +cheetah_template = CheetahTemplate("""\ +#import cgi + + + + $cgi.escape($page_title) + + +
+

$cgi.escape($page_title)

+
+ +
+ + #for $row in $table: + + #for $cell in $row: + + #end for + + #end for +
$cell
+
+ +\ +""", 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' % test) + sys.stdout.flush() + sys.stdout.write('\r %-20s%.4f ms\n' % (test, t.timeit(number=100) / 100)) +sys.stdout.write('=' * 80 + '\n') diff --git a/jinja2/compiler.py b/jinja2/compiler.py index 958b2c3..bc5163b 100644 --- a/jinja2/compiler.py +++ b/jinja2/compiler.py @@ -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: diff --git a/jinja2/environment.py b/jinja2/environment.py index 239193e..5325c2b 100644 --- a/jinja2/environment.py +++ b/jinja2/environment.py @@ -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 diff --git a/jinja2/optimizer.py b/jinja2/optimizer.py index c432b3b..f52b77f 100644 --- a/jinja2/optimizer.py +++ b/jinja2/optimizer.py @@ -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 diff --git a/jinja2/runtime.py b/jinja2/runtime.py index 7860dcc..8f0e1cc 100644 --- a/jinja2/runtime.py +++ b/jinja2/runtime.py @@ -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>' % (