From: Armin Ronacher Date: Sat, 26 Apr 2008 21:21:03 +0000 (+0200) Subject: added spitfire to bench and did some more refactoring X-Git-Tag: 2.0rc1~130 X-Git-Url: http://git.tremily.us/?a=commitdiff_plain;h=32a910f041ef31a62dcff468ab1d6f3d3f711266;p=jinja2.git added spitfire to bench and did some more refactoring --HG-- branch : trunk --- diff --git a/examples/bench.py b/examples/bench.py index 287f831..15830a0 100644 --- a/examples/bench.py +++ b/examples/bench.py @@ -1,9 +1,10 @@ -""" +"""\ This benchmark compares some python templating engines with Jinja 2 so that we get a picture of how fast Jinja 2 is for a semi real world - template. If a template engine is not installed the test is skipped. + template. If a template engine is not installed the test is skipped.\ """ import sys +import cgi from timeit import Timer from jinja2 import Environment as JinjaEnvironment @@ -247,20 +248,67 @@ else: from tenjin.helpers import escape, to_str tenjin_template.render(context, locals()) +try: + from spitfire.compiler import util as SpitfireTemplate + from spitfire.compiler.analyzer import o2_options as spitfire_optimizer +except ImportError: + test_spitfire = None +else: + spitfire_template = SpitfireTemplate.load_template("""\ + + + + $cgi.escape($page_title) + + +
+

$cgi.escape($page_title)

+
+ +
+ + #for $row in $table + + #for $cell in $row + + #end for + + #end for +
$cell
+
+ +\ +""", 'spitfire_tmpl', spitfire_optimizer, {'enable_filters': False}) + spitfire_context = dict(context, **{'cgi': cgi}) + + def test_spitfire(): + spitfire_template(search_list=[spitfire_context]).main() + sys.stdout.write('\r' + '\n'.join(( '=' * 80, 'Template Engine BigTable Benchmark'.center(80), - '-' * 80, + '=' * 80, __doc__, '-' * 80 )) + '\n') -for test in 'jinja', 'tenjin', 'mako', 'django', 'genshi', 'cheetah': +for test in 'jinja', 'tenjin', 'mako', 'spitfire', 'django', 'genshi', 'cheetah': if locals()['test_' + test] is None: - sys.stdout.write(' %-20s*not installed*\n' % test) + sys.stdout.write(' %-20s*not installed*\n' % test) continue t = Timer(setup='from __main__ import test_%s as bench' % test, stmt='bench()') - sys.stdout.write('> %-20s' % test) + sys.stdout.write(' >> %-20s' % test) sys.stdout.flush() - sys.stdout.write('\r %-20s%.4f ms\n' % (test, t.timeit(number=20) / 20)) -sys.stdout.write('=' * 80 + '\n') + sys.stdout.write('\r %-20s%.4f seconds\n' % (test, t.timeit(number=20) / 20)) +sys.stdout.write('-' * 80 + '\n') +sys.stdout.write('''\ + WARNING: The results of this benchmark are useless to compare the + performance of template engines and should not be taken seriously in any + way. It's testing the performance of simple loops and has no real-world + usefulnes. It only used to check if changes on the Jinja code affect + performance in a good or bad way and how it roughly compares to others. +''' + '=' * 80 + '\n') diff --git a/jinja2/compiler.py b/jinja2/compiler.py index 8d58fbe..a1ffdec 100644 --- a/jinja2/compiler.py +++ b/jinja2/compiler.py @@ -8,6 +8,7 @@ :copyright: Copyright 2008 by Armin Ronacher. :license: GNU GPL. """ +from time import time from copy import copy from random import randrange from keyword import iskeyword @@ -16,7 +17,7 @@ from itertools import chain from jinja2 import nodes from jinja2.visitor import NodeVisitor, NodeTransformer from jinja2.exceptions import TemplateAssertionError -from jinja2.runtime import StaticLoopContext, concat +from jinja2.runtime import concat from jinja2.utils import Markup @@ -53,7 +54,7 @@ def has_safe_repr(value): if value is None or value is NotImplemented or value is Ellipsis: return True if isinstance(value, (bool, int, long, float, complex, basestring, - xrange, StaticLoopContext, Markup)): + xrange, Markup)): return True if isinstance(value, (tuple, list, set, frozenset)): for item in value: @@ -545,8 +546,7 @@ class CodeGenerator(NodeVisitor): self.blocks[block.name] = block # generate the root render function. - self.writeline('def root(context, environment=environment' - '):', extra=1) + self.writeline('def root(context, environment=environment):', extra=1) if have_extends: self.indent() self.writeline('parent_template = None') @@ -760,7 +760,7 @@ class CodeGenerator(NodeVisitor): if not extended_loop and node.test is not None: self.indent() self.writeline('if ') - self.visit(node.test) + self.visit(node.test, loop_frame) self.write(':') self.indent() self.writeline('continue') @@ -802,7 +802,8 @@ class CodeGenerator(NodeVisitor): self.outdent() self.newline() if frame.toplevel: - self.write('context[%r] = ' % node.name) + self.write('context.exported_vars.add(%r)' % node.name) + self.writeline('context.vars[%r] = ' % node.name) arg_tuple = ', '.join(repr(x.name) for x in node.args) if len(node.args) == 1: arg_tuple += ',' @@ -909,24 +910,28 @@ class CodeGenerator(NodeVisitor): else: body.append([const]) - # if we have less than 3 nodes we just yield them - if len(body) < 3: + # if we have less than 3 nodes or less than 6 and a buffer we + # yield or extend + if len(body) < 3 or (frame.buffer is not None and len(body) < 6): + if frame.buffer is not None: + self.writeline('%s.extend((' % frame.buffer) for item in body: if isinstance(item, list): val = repr(concat(item)) if frame.buffer is None: self.writeline('yield ' + val) else: - self.writeline('%s.append(%s)' % (frame.buffer, val)) + self.write(val + ', ') else: - self.newline(item) if frame.buffer is None: - self.write('yield ') - else: - self.write('%s.append(' % frame.buffer) + self.writeline('yield ') self.write(finalizer + '(') self.visit(item, frame) - self.write(')' * (1 + (frame.buffer is not None))) + self.write(')') + if frame.buffer is not None: + self.write(', ') + if frame.buffer is not None: + self.write('))') # otherwise we create a format string as this is faster in that case else: @@ -979,7 +984,8 @@ class CodeGenerator(NodeVisitor): # make sure toplevel assignments are added to the context. if frame.toplevel: for name in assignment_frame.assigned_names: - self.writeline('context[%r] = l_%s' % (name, name)) + self.writeline('context.vars[%r] = l_%s' % (name, name)) + self.writeline('context.exported_vars.add(%r)' % name) def visit_Name(self, node, frame): if node.ctx == 'store': diff --git a/jinja2/debug.py b/jinja2/debug.py index d0157bb..622f2b3 100644 --- a/jinja2/debug.py +++ b/jinja2/debug.py @@ -9,6 +9,7 @@ :license: BSD. """ import sys +from types import CodeType def translate_exception(exc_info): @@ -61,20 +62,41 @@ def fake_exc_info(exc_info, filename, lineno, tb_back=None): # and fake the exception code = compile('\n' * (lineno - 1) + 'raise __jinja_exception__[0], ' + '__jinja_exception__[1]', filename, 'exec') + + # if it's possible, change the name of the code. This won't work + # on some python environments such as google appengine + try: + function = tb.tb_frame.f_code.co_name + if function == 'root': + location = 'top-level template code' + elif function.startswith('block_'): + location = 'block "%s"' % function[6:] + else: + location = 'template' + code = CodeType(0, code.co_nlocals, code.co_stacksize, + code.co_flags, code.co_code, code.co_consts, + code.co_names, code.co_varnames, filename, + location, code.co_firstlineno, + code.co_lnotab, (), ()) + except: + pass + + # execute the code and catch the new traceback try: exec code in globals, locals except: exc_info = sys.exc_info() + new_tb = exc_info[2].tb_next # now we can patch the exc info accordingly if tb_set_next is not None: if tb_back is not None: - tb_set_next(tb_back, exc_info[2]) + tb_set_next(tb_back, new_tb) if tb is not None: - tb_set_next(exc_info[2].tb_next, tb.tb_next) + tb_set_next(new_tb, tb.tb_next) # return without this frame - return exc_info[:2] + (exc_info[2].tb_next,) + return exc_info[:2] + (new_tb,) def _init_ugly_crap(): diff --git a/jinja2/environment.py b/jinja2/environment.py index 3a212b8..ef66d1b 100644 --- a/jinja2/environment.py +++ b/jinja2/environment.py @@ -231,8 +231,7 @@ class Environment(object): raise TypeError('no loader for this environment specified') if parent is not None: name = self.join_path(name, parent) - globals = self.make_globals(globals) - return self.loader.load(self, name, globals) + return self.loader.load(self, name, self.make_globals(globals)) def from_string(self, source, globals=None, template_class=None): """Load a template from a string.""" diff --git a/jinja2/parser.py b/jinja2/parser.py index 34a3140..2b26fc7 100644 --- a/jinja2/parser.py +++ b/jinja2/parser.py @@ -685,8 +685,7 @@ class Parser(object): if end_tokens is not None and \ self.stream.current.test_many(end_tokens): return body - while self.stream.current.type is not 'block_end': - body.append(self.parse_statement()) + body.append(self.parse_statement()) self.stream.expect('block_end') else: raise AssertionError('internal parsing error') diff --git a/jinja2/runtime.py b/jinja2/runtime.py index 6d63d47..20fa098 100644 --- a/jinja2/runtime.py +++ b/jinja2/runtime.py @@ -14,12 +14,12 @@ from jinja2.exceptions import UndefinedError # these variables are exported to the template runtime -__all__ = ['LoopContext', 'StaticLoopContext', 'TemplateContext', - 'Macro', 'Markup', 'missing', 'concat'] +__all__ = ['LoopContext', 'TemplateContext', 'Macro', 'Markup', 'missing', + 'concat'] # special singleton representing missing values for the runtime -missing = object() +missing = type('MissingType', (), {'__repr__': lambda x: 'missing'})() # concatenate a list of strings and convert them to unicode. @@ -73,17 +73,6 @@ class TemplateContext(object): return self.parent[key] return default - def setdefault(self, key, default=None): - """For dict compatibility""" - self.exported_vars.add(key) - return self.vars.setdefault(key, default) - - def update(self, *args, **kwargs): - """Update vars from a mapping but don't export them.""" - d = dict(*args, **kwargs) - self.vars.update(d) - self.exported_vars.update(d) - def get_exported(self): """Get a new dict with the exported variables.""" return dict((k, self.vars[k]) for k in self.exported_vars @@ -97,10 +86,6 @@ class TemplateContext(object): """Return a copy of the complete context as dict.""" return dict(self.parent, **self.vars) - def __setitem__(self, key, value): - self.vars[key] = value - self.exported_vars.add(key) - def __contains__(self, name): return name in self.vars or name in self.parent @@ -137,14 +122,16 @@ class SuperBlock(object): ) -class LoopContextBase(object): - """Helper for extended iteration.""" +class LoopContext(object): + """A loop context for dynamic iteration.""" - def __init__(self, iterable, parent=None): + def __init__(self, iterable, enforce_length=False): self._iterable = iterable + self._next = iter(iterable).next self._length = None - self.index0 = 0 - self.parent = parent + self.index0 = -1 + if enforce_length: + len(self) def cycle(self, *args): """A replacement for the old ``{% cycle %}`` tag.""" @@ -161,22 +148,6 @@ class LoopContextBase(object): def __len__(self): return self.length - -class LoopContext(LoopContextBase): - """A loop context for dynamic iteration.""" - - def __init__(self, iterable, enforce_length=False): - self._iterable = iterable - self._next = iter(iterable).next - self._length = None - self.index0 = -1 - if enforce_length: - len(self) - - def make_static(self): - """Return a static loop context for the optimizer.""" - return StaticLoopContext(self.index0, self.length) - def __iter__(self): return self @@ -200,28 +171,6 @@ class LoopContext(LoopContextBase): return 'LoopContext(%r)' % self.index0 -class StaticLoopContext(LoopContextBase): - """The static loop context is used in the optimizer to "freeze" the - status of an iteration. The only reason for this object is if the - loop object is accessed in a non static way (eg: becomes part of a - function call). - """ - - def __init__(self, index0, length): - self.index0 = index0 - self.length = length - - def __repr__(self): - """The repr is used by the optimizer to dump the object.""" - return 'StaticLoopContext(%r, %r)' % ( - self.index0, - self.length - ) - - def make_static(self): - return self - - class Macro(object): """Wraps a macro."""