Permalink
Browse files

added spitfire to bench and did some more refactoring

--HG--
branch : trunk
  • Loading branch information...
1 parent 5304229 commit 32a910f041ef31a62dcff468ab1d6f3d3f711266 @mitsuhiko mitsuhiko committed Apr 26, 2008
Showing with 114 additions and 91 deletions.
  1. +56 −8 examples/bench.py
  2. +21 −15 jinja2/compiler.py
  3. +25 −3 jinja2/debug.py
  4. +1 −2 jinja2/environment.py
  5. +1 −2 jinja2/parser.py
  6. +10 −61 jinja2/runtime.py
View
64 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 @@ def test_tenjin():
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("""\
+<!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>\
+""", '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<running>' % test)
+ sys.stdout.write(' >> %-20s<running>' % 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')
View
36 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 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 @@ def visit_Template(self, node, frame=None):
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 @@ def visit_For(self, node, frame):
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 @@ def visit_Macro(self, node, frame):
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 @@ def visit_Output(self, node, frame):
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 @@ def visit_Assign(self, node, frame):
# 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':
View
28 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():
View
3 jinja2/environment.py
@@ -231,8 +231,7 @@ def get_template(self, name, parent=None, globals=None):
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."""
View
3 jinja2/parser.py
@@ -685,8 +685,7 @@ def flush_data():
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')
View
71 jinja2/runtime.py
@@ -14,12 +14,12 @@
# 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 @@ def get(self, key, default=None):
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 @@ def get_all(self):
"""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 @@ def __repr__(self):
)
-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 @@ def cycle(self, *args):
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 @@ def __repr__(self):
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."""

0 comments on commit 32a910f

Please sign in to comment.