Mercurial > genshi > genshi-test
changeset 69:e9a3930f8823
A couple of minor performance improvements.
author | cmlenz |
---|---|
date | Mon, 10 Jul 2006 17:37:01 +0000 |
parents | a1f93d095759 |
children | 0498da8e5de7 |
files | examples/bench/clearsilver/header.cs examples/bench/clearsilver/template.cs examples/bench/kid/base.kid examples/bench/kid/template.kid examples/bench/markup/base.html examples/bench/markup/footer.html examples/bench/markup/header.html examples/bench/markup/template.html examples/bench/run.py markup/core.py markup/eval.py markup/filters.py markup/input.py markup/output.py markup/path.py markup/template.py |
diffstat | 16 files changed, 186 insertions(+), 140 deletions(-) [+] |
line wrap: on
line diff
--- a/examples/bench/clearsilver/header.cs +++ b/examples/bench/clearsilver/header.cs @@ -1,3 +1,7 @@ <div id="header"> <h1><?cs var:title ?></h1> </div> + +<?cs def:greeting(name) ?> + <p>Hello, <?cs var:name ?>!</p> +<?cs /def ?>
--- a/examples/bench/clearsilver/template.cs +++ b/examples/bench/clearsilver/template.cs @@ -7,12 +7,15 @@ </head> <body> <?cs include:"header.cs" ?> + <?cs call:greeting('you') ?> + <?cs call:greeting('me') ?> + <?cs call:greeting('all the others') ?> <h2>Loop</h2> <?cs if:len(items) ?> <ul> <?cs each:item = items ?> - <li><?cs var:item ?></li> + <li<?cs if:name(item) == len(items) ?> class="last"<?cs /if ?>><?cs var:item ?></li> <?cs /each ?> </ul> <?cs /if ?>
--- a/examples/bench/kid/base.kid +++ b/examples/bench/kid/base.kid @@ -1,8 +1,15 @@ <html xmlns="http://www.w3.org/1999/xhtml" xmlns:py="http://purl.org/kid/ns#"> - <div py:def="header()" id="header"> - <h1><?cs var:title ?></h1> - </div> - <div py:def="footer()" id="footer"> - </div> + + <p py:def="greeting(name)"> + Hello, ${name}! + </p> + + <body py:match="item.tag == '{http://www.w3.org/1999/xhtml}body'" py:strip=""> + <div id="header"> + <h1>${title}</h1> + </div> + ${item} + <div id="footer" /> + </body> </html>
--- a/examples/bench/kid/template.kid +++ b/examples/bench/kid/template.kid @@ -9,13 +9,14 @@ <title>${title}</title> </head> <body> - ${header()} + <div>${greeting('you')}</div> + <div>${greeting('me')}</div> + <div>${greeting('all the others')}</div> <h2>Loop</h2> <ul py:if="items"> - <li py:for="item in items">${item}</li> + <li py:for="idx, item in enumerate(items)" py:content="item" + class="${idx == len(items) and 'last' or None}" /> </ul> - - ${footer()} </body> </html>
new file mode 100644 --- /dev/null +++ b/examples/bench/markup/base.html @@ -0,0 +1,17 @@ +<html xmlns="http://www.w3.org/1999/xhtml" + xmlns:py="http://markup.edgewall.org/" + py:strip=""> + + <p py:def="greeting(name)"> + Hello, ${name}! + </p> + + <py:match path="body"> + <div id="header"> + <h1>${title}</h1> + </div> + ${select('*')} + <div id="footer" /> + </py:match> + +</html>
deleted file mode 100644 --- a/examples/bench/markup/footer.html +++ /dev/null @@ -1,2 +0,0 @@ -<div id="footer"> -</div>
deleted file mode 100644 --- a/examples/bench/markup/header.html +++ /dev/null @@ -1,3 +0,0 @@ -<div id="header"> - <h1>${title}</h1> -</div>
--- a/examples/bench/markup/template.html +++ b/examples/bench/markup/template.html @@ -5,17 +5,20 @@ xmlns:py="http://markup.edgewall.org/" xmlns:xi="http://www.w3.org/2001/XInclude" lang="en"> + <xi:include href="base.html" /> <head> <title>${title}</title> </head> <body> - <xi:include href="header.html" /> + <div>${greeting('you')}</div> + <div>${greeting('me')}</div> + <div>${greeting('all the others')}</div> <h2>Loop</h2> <ul py:if="items"> - <li py:for="item in items" py:content="item"/> + <li py:for="idx, item in enumerate(items)" py:content="item" + class="${idx == len(items) and 'last' or None}" /> </ul> - <xi:include href="footer.html" /> </body> </html>
--- a/examples/bench/run.py +++ b/examples/bench/run.py @@ -1,11 +1,12 @@ from cgi import escape -from datetime import datetime, timedelta import os import sys +import time +import timeit def markup(dirname): from markup.template import Context, TemplateLoader - loader = TemplateLoader([dirname]) + loader = TemplateLoader([dirname], auto_reload=False) template = loader.load('template.html') def render(): ctxt = Context(title='Just a test', @@ -75,30 +76,26 @@ except ImportError: return None -def main(): +def main(engines): basepath = os.path.abspath(os.path.dirname(__file__)) - for engine in ('markup', 'clearsilver', 'kid'): + for engine in engines: dirname = os.path.join(basepath, engine) print '%s:' % engine.capitalize() - func = globals()[engine](dirname) - if not func: - print 'Skipping %s, not installed?' % engine.capitalize() - continue - times = [] - for i in range(100): - start = datetime.now() - sys.stdout.write('.') - sys.stdout.flush() - func() - times.append(datetime.now() - start) - - print - total_ms = sum([t.seconds * 1000 + t.microseconds for t in times]) - print ' --> timing: %s (avg), %s (min), %s (max)' % ( - timedelta(microseconds=total_ms / len(times)), - timedelta(microseconds=min([t.seconds * 1000 + t.microseconds for t in times])), - timedelta(microseconds=max([t.seconds * 1000 + t.microseconds for t in times]))) - print + t = timeit.Timer(setup='from __main__ import %s; render = %s("%s")' + % (engine, engine, dirname), + stmt='render()') + print '%.2f ms' % (1000 * t.timeit(number=1000) / 1000) if __name__ == '__main__': - main() + engines = [arg for arg in sys.argv[1:] if arg[0] != '-'] + + if '-p' in sys.argv: + import hotshot, hotshot.stats + prof = hotshot.Profile("template.prof") + benchtime = prof.runcall(main, engines) + stats = hotshot.stats.load("template.prof") + stats.strip_dirs() + stats.sort_stats('time', 'calls') + stats.print_stats() + else: + main(engines)
--- a/markup/core.py +++ b/markup/core.py @@ -129,6 +129,17 @@ return self.render(encoding=None) +START = Stream.START +END = Stream.END +TEXT = Stream.TEXT +PROLOG = Stream.PROLOG +DOCTYPE = Stream.DOCTYPE +START_NS = Stream.START_NS +END_NS = Stream.END_NS +PI = Stream.PI +COMMENT = Stream.COMMENT + + class Attributes(list): """Sequence type that stores the attributes of an element.
--- a/markup/eval.py +++ b/markup/eval.py @@ -69,7 +69,7 @@ 3 """ __slots__ = ['source', 'ast'] - __visitors = {} + _visitors = {} def __init__(self, source): """Create the expression. @@ -97,10 +97,10 @@ # AST traversal def _visit(self, node, data): - v = self.__visitors.get(node.__class__) + v = self._visitors.get(node.__class__) if not v: v = getattr(self, '_visit_%s' % node.__class__.__name__.lower()) - self.__visitors[node.__class__] = v + self._visitors[node.__class__] = v return v(node, data) def _visit_expression(self, node, data): @@ -248,10 +248,10 @@ # AST traversal def _visit(self, node, data): - v = self.__visitors.get(node.__class__) + v = self._visitors.get(node.__class__) if not v: v = getattr(self, '_visit_%s' % node.__class__.__name__.lower()) - self.__visitors[node.__class__] = v + self._visitors[node.__class__] = v return v(node, data) def _visit_expression(self, node, data):
--- a/markup/filters.py +++ b/markup/filters.py @@ -19,7 +19,8 @@ from sets import ImmutableSet as frozenset import re -from markup.core import Attributes, Markup, Namespace, Stream +from markup.core import Attributes, Markup, Namespace +from markup.core import END, END_NS, START, START_NS, TEXT from markup.path import Path __all__ = ['IncludeFilter', 'WhitespaceFilter', 'HTMLSanitizer'] @@ -53,11 +54,11 @@ ns_prefixes = [] in_fallback = False include_href, fallback_stream = None, None + namespace = self.NAMESPACE for kind, data, pos in stream: - if kind is Stream.START and data[0] in self.NAMESPACE \ - and not in_fallback: + if kind is START and not in_fallback and data[0] in namespace: tag, attrib = data if tag.localname == 'include': include_href = attrib.get('href') @@ -65,7 +66,7 @@ in_fallback = True fallback_stream = [] - elif kind is Stream.END and data in self.NAMESPACE: + elif kind is END and data in namespace: if data.localname == 'include': try: if not include_href: @@ -91,10 +92,10 @@ elif in_fallback: fallback_stream.append((kind, data, pos)) - elif kind is Stream.START_NS and data[1] == self.NAMESPACE: + elif kind is START_NS and data[1] == namespace: ns_prefixes.append(data[0]) - elif kind is Stream.END_NS and data in ns_prefixes: + elif kind is END_NS and data in ns_prefixes: ns_prefixes.pop() else: @@ -104,34 +105,35 @@ class WhitespaceFilter(object): """A filter that removes extraneous white space from the stream. - Todo: + TODO: * Support for xml:space """ - _TRAILING_SPACE = re.compile('[ \t]+(?=\n)') _LINE_COLLAPSE = re.compile('\n{2,}') def __call__(self, stream, ctxt=None): + trim_trailing_space = self._TRAILING_SPACE.sub + collapse_lines = self._LINE_COLLAPSE.sub + mjoin = Markup('').join + textbuf = [] - prev_kind = None for kind, data, pos in stream: - if kind is Stream.TEXT: + if kind is TEXT: textbuf.append(data) - elif prev_kind is Stream.TEXT: - text = Markup('').join(textbuf, escape_quotes=False) - text = self._TRAILING_SPACE.sub('', text) - text = self._LINE_COLLAPSE.sub('\n', text) - yield Stream.TEXT, Markup(text), pos - del textbuf[:] - prev_kind = kind - if kind is not Stream.TEXT: + else: + if textbuf: + text = mjoin(textbuf, escape_quotes=False) + text = trim_trailing_space('', text) + text = collapse_lines('\n', text) + yield TEXT, Markup(text), pos + del textbuf[:] yield kind, data, pos - - if textbuf: - text = Markup('').join(textbuf, escape_quotes=False) - text = self._TRAILING_SPACE.sub('', text) - text = self._LINE_COLLAPSE.sub('\n', text) - yield Stream.TEXT, Markup(text), pos + else: + if textbuf: + text = mjoin(textbuf, escape_quotes=False) + text = trim_trailing_space('', text) + text = collapse_lines('\n', text) + yield TEXT, Markup(text), pos class HTMLSanitizer(object): @@ -168,7 +170,7 @@ waiting_for = None for kind, data, pos in stream: - if kind is Stream.START: + if kind is START: if waiting_for: continue tag, attrib = data @@ -204,7 +206,7 @@ yield kind, (tag, new_attrib), pos - elif kind is Stream.END: + elif kind is END: tag = data if waiting_for: if waiting_for == tag:
--- a/markup/input.py +++ b/markup/input.py @@ -85,7 +85,7 @@ try: bufsize = 4 * 1024 # 4K done = False - while True: + while 1: while not done and len(self._queue) == 0: data = self.source.read(bufsize) if data == '': # end of data @@ -194,7 +194,7 @@ try: bufsize = 4 * 1024 # 4K done = False - while True: + while 1: while not done and len(self._queue) == 0: data = self.source.read(bufsize) if data == '': # end of data
--- a/markup/output.py +++ b/markup/output.py @@ -20,7 +20,8 @@ except NameError: from sets import ImmutableSet as frozenset -from markup.core import Markup, Namespace, QName, Stream +from markup.core import Markup, Namespace, QName +from markup.core import DOCTYPE, START, END, START_NS, END_NS, TEXT __all__ = ['Serializer', 'XMLSerializer', 'HTMLSerializer'] @@ -54,11 +55,11 @@ stream = _PushbackIterator(stream) for kind, data, pos in stream: - if kind is Stream.DOCTYPE: + if kind is DOCTYPE: # FIXME: what if there's no system or public ID in the input? yield Markup('<!DOCTYPE %s "%s" "%s">\n' % data) - elif kind is Stream.START_NS: + elif kind is START_NS: prefix, uri = data if uri not in ns_mapping: ns_mapping[uri] = prefix @@ -67,7 +68,7 @@ else: ns_attrib.append((QName('xmlns:%s' % prefix), uri)) - elif kind is Stream.START: + elif kind is START: tag, attrib = data tagname = tag.localname @@ -75,10 +76,10 @@ try: prefix = ns_mapping[tag.namespace] if prefix: - tagname = prefix + ':' + tag.localname + tagname = '%s:%s' % (prefix, tag.localname) except KeyError: ns_attrib.append((QName('xmlns'), tag.namespace)) - buf = ['<', tagname] + buf = ['<%s' % tagname] if ns_attrib: attrib.extend(ns_attrib) @@ -88,11 +89,11 @@ if attr.namespace: prefix = ns_mapping.get(attr.namespace) if prefix: - attrname = prefix + ':' + attrname + attrname = '%s:%s' % (prefix, attrname) buf.append(' %s="%s"' % (attrname, Markup.escape(value))) kind, data, pos = stream.next() - if kind is Stream.END: + if kind is END: buf.append('/>') else: buf.append('>') @@ -100,16 +101,16 @@ yield Markup(''.join(buf)) - elif kind is Stream.END: + elif kind is END: tag = data tagname = tag.localname if tag.namespace: prefix = ns_mapping.get(tag.namespace) if prefix: - tagname = prefix + ':' + tag.localname + tagname = '%s:%s' % (prefix, tag.localname) yield Markup('</%s>' % tagname) - elif kind is Stream.TEXT: + elif kind is TEXT: yield Markup.escape(data, quotes=False) @@ -137,15 +138,15 @@ stream = _PushbackIterator(stream) for kind, data, pos in stream: - if kind is Stream.DOCTYPE: + if kind is DOCTYPE: yield Markup('<!DOCTYPE %s "%s" "%s">\n' % data) - elif kind is Stream.START_NS: + elif kind is START_NS: prefix, uri = data if uri not in ns_mapping: ns_mapping[uri] = prefix - elif kind is Stream.START: + elif kind is START: tag, attrib = data if tag.namespace and tag not in self.NAMESPACE: continue # not in the HTML namespace, so don't emit @@ -162,18 +163,18 @@ if tag.localname in self._EMPTY_ELEMS: kind, data, pos = stream.next() - if kind is not Stream.END: + if kind is not END: stream.pushback((kind, data, pos)) yield Markup(''.join(buf + ['>'])) - elif kind is Stream.END: + elif kind is END: tag = data if tag.namespace and tag not in self.NAMESPACE: continue # not in the HTML namespace, so don't emit yield Markup('</%s>' % tag.localname) - elif kind is Stream.TEXT: + elif kind is TEXT: yield Markup.escape(data, quotes=False)
--- a/markup/path.py +++ b/markup/path.py @@ -114,6 +114,8 @@ @param stream: the stream to select from @return: the substream matching the path, or an empty stream """ + from markup.core import END, START + stream = iter(stream) def _generate(): test = self.test() @@ -124,7 +126,7 @@ depth = 1 while depth > 0: ev = stream.next() - depth += {Stream.START: 1, Stream.END: -1}.get(ev[0], 0) + depth += {START: 1, END: -1}.get(ev[0], 0) yield ev test(*ev) elif result: @@ -149,17 +151,18 @@ START (u'child', [(u'id', u'1')]) START (u'child', [(u'id', u'2')]) """ + from markup.core import END, START stack = [0] # stack of cursors into the location path def _test(kind, data, pos): if not stack: return False - if kind is Stream.END: + elif kind is END: stack.pop() return None - if kind is Stream.START: + elif kind is START: stack.append(stack[-1]) matched = False @@ -180,7 +183,7 @@ else: stack[-1] += 1 - elif kind is Stream.START and not closure: + elif kind is START and not closure: # If this step is not a closure, it cannot be matched until the # current element is closed... so we need to move the cursor # back to the last closure and retest that against the current
--- a/markup/template.py +++ b/markup/template.py @@ -45,8 +45,9 @@ from StringIO import StringIO from markup.core import Attributes, Namespace, Stream, StreamEventKind +from markup.core import START, END, START_NS, END_NS, TEXT from markup.eval import Expression -from markup.input import HTML, XMLParser, XML +from markup.input import XMLParser from markup.path import Path __all__ = ['Context', 'BadDirectiveError', 'TemplateError', @@ -257,7 +258,7 @@ kind, data, pos = stream.next() if kind is Stream.START: yield kind, data, pos # emit start tag - yield Template.EXPR, self.expr, pos + yield EXPR, self.expr, pos previous = stream.next() for event in stream: previous = event @@ -342,8 +343,7 @@ else: scope[name] = kwargs.pop(name, self.defaults.get(name)) ctxt.push(**scope) - stream = self._apply_directives(self.stream, ctxt, self.directives) - for event in stream: + for event in self._apply_directives(self.stream, ctxt, self.directives): yield event ctxt.pop() @@ -479,7 +479,7 @@ def __call__(self, stream, ctxt, directives): kind, data, pos = stream.next() - yield Template.EXPR, self.expr, pos + yield EXPR, self.expr, pos class StripDirective(Directive): @@ -680,7 +680,7 @@ for kind, data, pos in XMLParser(self.source, filename=self.filename): - if kind is Stream.START_NS: + if kind is START_NS: # Strip out the namespace declaration for template directives prefix, uri = data if uri == self.NAMESPACE: @@ -688,13 +688,13 @@ else: stream.append((kind, data, pos)) - elif kind is Stream.END_NS: + elif kind is END_NS: if data in ns_prefix: del ns_prefix[data] else: stream.append((kind, data, pos)) - elif kind is Stream.START: + elif kind is START: # Record any directive attributes in start tags tag, attrib = data directives = [] @@ -727,7 +727,7 @@ stream.append((kind, (tag, Attributes(new_attrib)), pos)) depth += 1 - elif kind is Stream.END: + elif kind is END: depth -= 1 stream.append((kind, data, pos)) @@ -738,10 +738,10 @@ substream = stream[start_offset:] if strip: substream = substream[1:-1] - stream[start_offset:] = [(Template.SUB, - (directives, substream), pos)] + stream[start_offset:] = [(SUB, (directives, substream), + pos)] - elif kind is Stream.TEXT: + elif kind is TEXT: for kind, data, pos in self._interpolate(data, *pos): stream.append((kind, data, pos)) @@ -768,14 +768,14 @@ def _interpolate(text): for idx, group in enumerate(patterns.pop(0).split(text)): if idx % 2: - yield Template.EXPR, Expression(group), (lineno, offset) + yield EXPR, Expression(group), (lineno, offset) elif group: if patterns: for result in _interpolate(group): yield result else: - yield Stream.TEXT, group.replace('$$', '$'), \ - (filename, lineno, offset) + yield TEXT, group.replace('$$', '$'), (filename, lineno, + offset) return _interpolate(text) _interpolate = classmethod(_interpolate) @@ -791,9 +791,8 @@ if not hasattr(ctxt, '_match_templates'): ctxt._match_templates = [] - stream = self._flatten(self._match(self._eval(self.stream, ctxt), ctxt), - ctxt) - for filter_ in self.filters: + stream = self.stream + for filter_ in [self._eval, self._match, self._flatten] + self.filters: stream = filter_(iter(stream), ctxt) return Stream(stream) @@ -803,7 +802,7 @@ """ for kind, data, pos in stream: - if kind is Stream.START: + if kind is START: # Attributes may still contain expressions in start tags at # this point, so do some evaluation tag, attrib = data @@ -814,7 +813,7 @@ else: values = [] for subkind, subdata, subpos in substream: - if subkind is Template.EXPR: + if subkind is EXPR: values.append(subdata.evaluate(ctxt)) else: values.append(subdata) @@ -824,7 +823,7 @@ new_attrib.append((name, u''.join(value))) yield kind, (tag, Attributes(new_attrib)), pos - elif kind is Template.EXPR: + elif kind is EXPR: result = data.evaluate(ctxt) if result is None: continue @@ -833,7 +832,7 @@ # succeeds, and the string will be chopped up into individual # characters if isinstance(result, basestring): - yield Stream.TEXT, result, pos + yield TEXT, result, pos else: # Test if the expression evaluated to an iterable, in which # case we yield the individual items @@ -844,7 +843,7 @@ except TypeError: # Neither a string nor an iterable, so just pass it # through - yield Stream.TEXT, unicode(result), pos + yield TEXT, unicode(result), pos else: yield kind, data, pos @@ -853,19 +852,22 @@ """Internal stream filter that expands `SUB` events in the stream.""" try: for kind, data, pos in stream: - if kind is Template.SUB: + if kind is SUB: # This event is a list of directives and a list of nested # events to which those directives should be applied directives, substream = data - substream = directives[0](iter(substream), ctxt, directives[1:]) - substream = self._match(self._eval(substream, ctxt), ctxt) - for event in self._flatten(substream, ctxt): + if directives: + substream = directives[0](iter(substream), ctxt, + directives[1:]) + for filter_ in (self._eval, self._match, self._flatten): + substream = filter_(substream, ctxt) + for event in substream: yield event continue else: yield kind, data, pos except SyntaxError, err: - raise TemplateSyntaxError(err, self.filename, pos[1], + raise TemplateSyntaxError(err, pos[0], pos[1], pos[2] + (err.offset or 0)) def _match(self, stream, ctxt=None, match_templates=None): @@ -879,7 +881,7 @@ # We (currently) only care about start and end events for matching # We might care about namespace events in the future, though - if kind not in (Stream.START, Stream.END): + if kind not in (START, END): yield kind, data, pos continue @@ -893,15 +895,10 @@ content = [(kind, data, pos)] depth = 1 while depth > 0: - event = stream.next() - if event[0] is Stream.START: - depth += 1 - elif event[0] is Stream.END: - depth -= 1 - content.append(event) - - # enable the path to keep track of the stream state - test(*event) + ev = stream.next() + depth += {START: 1, END: -1}.get(ev[0], 0) + content.append(ev) + test(*ev) content = list(self._flatten(content, ctxt)) ctxt.push(select=lambda path: Stream(content).select(path)) @@ -909,18 +906,22 @@ if directives: template = directives[0](iter(template), ctxt, directives[1:]) - template = self._match(self._eval(iter(template), ctxt), - ctxt, match_templates[:idx] + - match_templates[idx + 1:]) - for event in template: + for event in self._match(self._eval(template, ctxt), + ctxt, match_templates[:idx] + + match_templates[idx + 1:]): yield event ctxt.pop() break - else: + + else: # no matches yield kind, data, pos +EXPR = Template.EXPR +SUB = Template.SUB + + class TemplateLoader(object): """Responsible for loading templates from files on the specified search path. @@ -990,6 +991,8 @@ template is being loaded, or `None` if the template is being loaded directly """ + from markup.filters import IncludeFilter + if relative_to: filename = posixpath.join(posixpath.dirname(relative_to), filename) filename = os.path.normpath(filename) @@ -1013,7 +1016,6 @@ try: fileobj = file(filepath, 'rt') try: - from markup.filters import IncludeFilter tmpl = Template(fileobj, basedir=dirname, filename=filename) tmpl.filters.append(IncludeFilter(self)) finally: