added spitfire to bench and did some more refactoring
authorArmin Ronacher <armin.ronacher@active-4.com>
Sat, 26 Apr 2008 21:21:03 +0000 (23:21 +0200)
committerArmin Ronacher <armin.ronacher@active-4.com>
Sat, 26 Apr 2008 21:21:03 +0000 (23:21 +0200)
--HG--
branch : trunk

examples/bench.py
jinja2/compiler.py
jinja2/debug.py
jinja2/environment.py
jinja2/parser.py
jinja2/runtime.py

index 287f8318ab257f74cea6a2eb01d1ae6cfb0c6a39..15830a0b728d4411aeb7f09120030457f9f4f6f3 100644 (file)
@@ -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("""\
+<!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')
index 8d58fbe04740d1fbd35d0bb404dfb212cdc84355..a1ffdecab65837cf9e8b3a0faed18edefae38f6c 100644 (file)
@@ -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':
index d0157bb975fb2985178f9e24ed88fccbbc9ddfbf..622f2b3e1a61f10cfe122b4d1dd07781aa54227c 100644 (file)
@@ -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():
index 3a212b86145cda93fa1fd6048f1881fe8631cce6..ef66d1b35997b8de98074cd11daa2d5cc5ebace2 100644 (file)
@@ -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."""
index 34a3140874a668664829755a6493a21cdff3daa4..2b26fc7739dcca782021f8dcd2b1b45ed6ff65b4 100644 (file)
@@ -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')
index 6d63d47f25b65a8817c5aa7eb06c6badbf385163..20fa098487c849e0079f7eed9774c9a77e42a3ef 100644 (file)
@@ -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."""