| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546 |
- import sys, re, string, time, copy, gc
- from itertools import *
- import time
- try:
- from io import BytesIO
- except ImportError:
- from StringIO import StringIO as BytesIO
- try:
- izip
- except NameError:
- izip = zip # Py3
- def exec_(code, glob):
- if sys.version_info[0] >= 3:
- exec(code, glob)
- else:
- exec("exec code in glob")
- TREE_FACTOR = 1 # increase tree size with '-l / '-L' cmd option
- _TEXT = "some ASCII text" * TREE_FACTOR
- _UTEXT = u"some klingon: \F8D2" * TREE_FACTOR
- _ATTRIBUTES = {
- '{attr}test1' : _TEXT,
- '{attr}test2' : _TEXT,
- 'bla1' : _TEXT,
- 'bla2' : _TEXT,
- 'bla3' : _TEXT
- }
- def initArgs(argv):
- global TREE_FACTOR
- try:
- argv.remove('-l')
- # use large trees
- TREE_FACTOR *= 2
- except ValueError:
- pass
- try:
- argv.remove('-L')
- # use LARGE trees
- TREE_FACTOR *= 2
- except ValueError:
- pass
- ############################################################
- # benchmark decorators
- ############################################################
- def with_attributes(*use_attributes):
- "Decorator for benchmarks that use attributes"
- vmap = {False : 0, True : 1}
- values = [ vmap[bool(v)] for v in use_attributes ]
- def set_value(function):
- try:
- function.ATTRIBUTES.update(values)
- except AttributeError:
- function.ATTRIBUTES = set(values)
- return function
- return set_value
- def with_text(no_text=False, text=False, utext=False):
- "Decorator for benchmarks that use text"
- values = []
- if no_text:
- values.append(0)
- if text:
- values.append(1)
- if utext:
- values.append(2)
- def set_value(function):
- try:
- function.TEXT.add(values)
- except AttributeError:
- function.TEXT = set(values)
- return function
- return set_value
- def onlylib(*libs):
- "Decorator to restrict benchmarks to specific libraries"
- def set_libs(function):
- if libs:
- function.LIBS = libs
- return function
- return set_libs
- def serialized(function):
- "Decorator for benchmarks that require serialized XML data"
- function.STRING = True
- return function
- def children(function):
- "Decorator for benchmarks that require a list of root children"
- function.CHILDREN = True
- return function
- def nochange(function):
- "Decorator for benchmarks that do not change the XML tree"
- function.NO_CHANGE = True
- return function
- ############################################################
- # benchmark baseclass
- ############################################################
- class SkippedTest(Exception):
- pass
- class TreeBenchMark(object):
- atoz = string.ascii_lowercase
- repeat100 = range(100)
- repeat500 = range(500)
- repeat1000 = range(1000)
- _LIB_NAME_MAP = {
- 'etree' : 'lxe',
- 'ElementTree' : 'ET',
- 'cElementTree' : 'cET'
- }
- SEARCH_TAG = "{cdefg}a00001"
- def __init__(self, etree, etree_parser=None):
- self.etree = etree
- libname = etree.__name__.split('.')[-1]
- self.lib_name = self._LIB_NAME_MAP.get(libname, libname)
- if libname == 'etree':
- deepcopy = copy.deepcopy
- def set_property(root, fname):
- xml = self._serialize_tree(root)
- if etree_parser is not None:
- setattr(self, fname, lambda : etree.XML(xml, etree_parser))
- else:
- setattr(self, fname, lambda : deepcopy(root))
- setattr(self, fname + '_xml', lambda : xml)
- setattr(self, fname + '_children', lambda : root[:])
- else:
- def set_property(root, fname):
- setattr(self, fname, self.et_make_clone_factory(root))
- xml = self._serialize_tree(root)
- setattr(self, fname + '_xml', lambda : xml)
- setattr(self, fname + '_children', lambda : root[:])
- attribute_list = list(enumerate( [{}, _ATTRIBUTES] ))
- text_list = list(enumerate( [None, _TEXT, _UTEXT] ))
- build_name = self._tree_builder_name
- self.setup_times = []
- for tree in self._all_trees():
- times = []
- self.setup_times.append(times)
- setup = getattr(self, '_setup_tree%d' % tree)
- for an, attributes in attribute_list:
- for tn, text in text_list:
- root, t = setup(text, attributes)
- times.append(t)
- set_property(root, build_name(tree, tn, an))
- def _tree_builder_name(self, tree, tn, an):
- return '_root%d_T%d_A%d' % (tree, tn, an)
- def tree_builder(self, tree, tn, an, serial, children):
- name = self._tree_builder_name(tree, tn, an)
- if serial:
- name += '_xml'
- elif children:
- name += '_children'
- return getattr(self, name)
- def _serialize_tree(self, root):
- return self.etree.tostring(root, encoding='UTF-8')
- def et_make_clone_factory(self, elem):
- def generate_elem(append, elem, level):
- var = "e" + str(level)
- arg = repr(elem.tag)
- if elem.attrib:
- arg += ", **%r" % elem.attrib
- if level == 1:
- append(" e1 = Element(%s)" % arg)
- else:
- append(" %s = SubElement(e%d, %s)" % (var, level-1, arg))
- if elem.text:
- append(" %s.text = %r" % (var, elem.text))
- if elem.tail:
- append(" %s.tail = %r" % (var, elem.tail))
- for e in elem:
- generate_elem(append, e, level+1)
- # generate code for a function that creates a tree
- output = ["def element_factory():"]
- generate_elem(output.append, elem, 1)
- output.append(" return e1")
- # setup global function namespace
- namespace = {
- "Element" : self.etree.Element,
- "SubElement" : self.etree.SubElement
- }
- # create function object
- exec_("\n".join(output), namespace)
- return namespace["element_factory"]
- def _all_trees(self):
- all_trees = []
- for name in dir(self):
- if name.startswith('_setup_tree'):
- all_trees.append(int(name[11:]))
- return all_trees
- def _setup_tree1(self, text, attributes):
- "tree with 26 2nd level and 520 * TREE_FACTOR 3rd level children"
- atoz = self.atoz
- SubElement = self.etree.SubElement
- current_time = time.time
- t = current_time()
- root = self.etree.Element('{abc}rootnode')
- for ch1 in atoz:
- el = SubElement(root, "{abc}"+ch1*5, attributes)
- el.text = text
- for ch2 in atoz:
- tag = "{cdefg}%s00001" % ch2
- for i in range(20 * TREE_FACTOR):
- SubElement(el, tag).tail = text
- t = current_time() - t
- return (root, t)
- def _setup_tree2(self, text, attributes):
- "tree with 520 * TREE_FACTOR 2nd level and 26 3rd level children"
- atoz = self.atoz
- SubElement = self.etree.SubElement
- current_time = time.time
- t = current_time()
- root = self.etree.Element('{abc}rootnode')
- for ch1 in atoz:
- for i in range(20 * TREE_FACTOR):
- el = SubElement(root, "{abc}"+ch1*5, attributes)
- el.text = text
- for ch2 in atoz:
- SubElement(el, "{cdefg}%s00001" % ch2).tail = text
- t = current_time() - t
- return (root, t)
- def _setup_tree3(self, text, attributes):
- "tree of depth 8 + TREE_FACTOR with 3 children per node"
- SubElement = self.etree.SubElement
- current_time = time.time
- t = current_time()
- root = self.etree.Element('{abc}rootnode')
- children = [root]
- for i in range(6 + TREE_FACTOR):
- children = [ SubElement(c, "{cdefg}a%05d" % (i%8), attributes)
- for i,c in enumerate(chain(children, children, children)) ]
- for child in children:
- child.text = text
- child.tail = text
- t = current_time() - t
- return (root, t)
- def _setup_tree4(self, text, attributes):
- "small tree with 26 2nd level and 2 3rd level children"
- SubElement = self.etree.SubElement
- current_time = time.time
- t = current_time()
- root = self.etree.Element('{abc}rootnode')
- for ch1 in self.atoz:
- el = SubElement(root, "{abc}"+ch1*5, attributes)
- el.text = text
- SubElement(el, "{cdefg}a00001", attributes).tail = text
- SubElement(el, "{cdefg}z00000", attributes).tail = text
- t = current_time() - t
- return (root, t)
- def benchmarks(self):
- """Returns a list of all benchmarks.
- A benchmark is a tuple containing a method name and a list of tree
- numbers. Trees are prepared by the setup function.
- """
- all_trees = self._all_trees()
- benchmarks = []
- for name in dir(self):
- if not name.startswith('bench_'):
- continue
- method = getattr(self, name)
- if hasattr(method, 'LIBS') and self.lib_name not in method.LIBS:
- method_call = None
- else:
- method_call = method
- if method.__doc__:
- tree_sets = method.__doc__.split()
- else:
- tree_sets = ()
- if tree_sets:
- tree_tuples = [list(map(int, tree_set.split(',')))
- for tree_set in tree_sets]
- else:
- try:
- arg_count = method.func_code.co_argcount - 1
- except AttributeError:
- try:
- arg_count = method.__code__.co_argcount - 1
- except AttributeError:
- arg_count = 1
- tree_tuples = self._permutations(all_trees, arg_count)
- serialized = getattr(method, 'STRING', False)
- children = getattr(method, 'CHILDREN', False)
- no_change = getattr(method, 'NO_CHANGE', False)
- for tree_tuple in tree_tuples:
- for tn in sorted(getattr(method, 'TEXT', (0,))):
- for an in sorted(getattr(method, 'ATTRIBUTES', (0,))):
- benchmarks.append((name, method_call, tree_tuple,
- tn, an, serialized, children,
- no_change))
- return benchmarks
- def _permutations(self, seq, count):
- def _permutations(prefix, remainder, count):
- if count == 0:
- return [ prefix[:] ]
- count -= 1
- perms = []
- prefix.append(None)
- for pos, el in enumerate(remainder):
- new_remainder = remainder[:pos] + remainder[pos+1:]
- prefix[-1] = el
- perms.extend( _permutations(prefix, new_remainder, count) )
- prefix.pop()
- return perms
- return _permutations([], seq, count)
- ############################################################
- # Prepare and run benchmark suites
- ############################################################
- def buildSuites(benchmark_class, etrees, selected):
- benchmark_suites = list(map(benchmark_class, etrees))
- # sorted by name and tree tuple
- benchmarks = [ sorted(b.benchmarks()) for b in benchmark_suites ]
- selected = [ re.compile(r).search for r in selected ]
- if selected:
- benchmarks = [ [ b for b in bs
- if [ match for match in selected
- if match(b[0]) ] ]
- for bs in benchmarks ]
- return (benchmark_suites, benchmarks)
- def build_treeset_name(trees, tn, an, serialized, children):
- text = {0:'-', 1:'S', 2:'U'}[tn]
- attr = {0:'-', 1:'A'}[an]
- ser = {True:'X', False:'T'}[serialized]
- chd = {True:'C', False:'R'}[children]
- return "%s%s%s%s T%s" % (text, attr, ser, chd, ',T'.join(map(str, trees))[:6])
- def printSetupTimes(benchmark_suites):
- print("Setup times for trees in seconds:")
- for b in benchmark_suites:
- sys.stdout.write("%-3s: " % b.lib_name)
- for an in (0,1):
- for tn in (0,1,2):
- sys.stdout.write(' %s ' %
- build_treeset_name((), tn, an, False, False)[:2])
- print('')
- for i, tree_times in enumerate(b.setup_times):
- print(" T%d: %s" % (i+1, ' '.join("%6.4f" % t for t in tree_times)))
- print('')
- def runBench(suite, method_name, method_call, tree_set, tn, an,
- serial, children, no_change):
- if method_call is None:
- raise SkippedTest
- current_time = time.time
- call_repeat = range(10)
- tree_builders = [ suite.tree_builder(tree, tn, an, serial, children)
- for tree in tree_set ]
- rebuild_trees = not no_change and not serial
- args = tuple([ build() for build in tree_builders ])
- method_call(*args) # run once to skip setup overhead
- times = []
- for i in range(3):
- gc.collect()
- gc.disable()
- t = -1
- for i in call_repeat:
- if rebuild_trees:
- args = [ build() for build in tree_builders ]
- t_one_call = current_time()
- method_call(*args)
- t_one_call = current_time() - t_one_call
- if t < 0:
- t = t_one_call
- else:
- t = min(t, t_one_call)
- times.append(1000.0 * t)
- gc.enable()
- if rebuild_trees:
- args = ()
- args = ()
- gc.collect()
- return times
- def runBenchmarks(benchmark_suites, benchmarks):
- for bench_calls in izip(*benchmarks):
- for lib, (bench, benchmark_setup) in enumerate(izip(benchmark_suites, bench_calls)):
- bench_name = benchmark_setup[0]
- tree_set_name = build_treeset_name(*benchmark_setup[-6:-1])
- sys.stdout.write("%-3s: %-28s (%-10s) " % (
- bench.lib_name, bench_name[6:34], tree_set_name))
- sys.stdout.flush()
- try:
- result = runBench(bench, *benchmark_setup)
- except SkippedTest:
- print("skipped")
- except KeyboardInterrupt:
- print("interrupted by user")
- sys.exit(1)
- except Exception:
- exc_type, exc_value = sys.exc_info()[:2]
- print("failed: %s: %s" % (exc_type.__name__, exc_value))
- exc_type = exc_value = None
- else:
- print("%9.4f msec/pass, best of (%s)" % (
- min(result), ' '.join("%9.4f" % t for t in result)))
- if len(benchmark_suites) > 1:
- print('') # empty line between different benchmarks
- ############################################################
- # Main program
- ############################################################
- def main(benchmark_class):
- import_lxml = True
- callgrind_zero = False
- if len(sys.argv) > 1:
- try:
- sys.argv.remove('-i')
- # run benchmark 'inplace'
- sys.path.insert(0, 'src')
- except ValueError:
- pass
- try:
- sys.argv.remove('-nolxml')
- # run without lxml
- import_lxml = False
- except ValueError:
- pass
- try:
- sys.argv.remove('-z')
- # reset callgrind after tree setup
- callgrind_zero = True
- except ValueError:
- pass
- initArgs(sys.argv)
- _etrees = []
- if import_lxml:
- from lxml import etree
- _etrees.append(etree)
- try:
- sys.argv.remove('-fel')
- except ValueError:
- pass
- else:
- # use fast element creation in lxml.etree
- etree.set_element_class_lookup(
- etree.ElementDefaultClassLookup())
- if len(sys.argv) > 1:
- if '-a' in sys.argv or '-c' in sys.argv:
- # 'all' or 'C-implementations' ?
- try:
- sys.argv.remove('-c')
- except ValueError:
- pass
- try:
- import cElementTree as cET
- _etrees.append(cET)
- except ImportError:
- try:
- import xml.etree.cElementTree as cET
- _etrees.append(cET)
- except ImportError:
- pass
- try:
- # 'all' ?
- sys.argv.remove('-a')
- except ValueError:
- pass
- else:
- try:
- from elementtree import ElementTree as ET
- _etrees.append(ET)
- except ImportError:
- try:
- from xml.etree import ElementTree as ET
- _etrees.append(ET)
- except ImportError:
- pass
- if not _etrees:
- print("No library to test. Exiting.")
- sys.exit(1)
- print("Preparing test suites and trees ...")
- selected = set( sys.argv[1:] )
- benchmark_suites, benchmarks = \
- buildSuites(benchmark_class, _etrees, selected)
- print("Running benchmark on", ', '.join(b.lib_name
- for b in benchmark_suites))
- print('')
- printSetupTimes(benchmark_suites)
- if callgrind_zero:
- cmd = open("callgrind.cmd", 'w')
- cmd.write('+Instrumentation\n')
- cmd.write('Zero\n')
- cmd.close()
- runBenchmarks(benchmark_suites, benchmarks)
|