summaryrefslogtreecommitdiff
path: root/bench/env.__setitem__.py
diff options
context:
space:
mode:
Diffstat (limited to 'bench/env.__setitem__.py')
-rw-r--r--bench/env.__setitem__.py362
1 files changed, 362 insertions, 0 deletions
diff --git a/bench/env.__setitem__.py b/bench/env.__setitem__.py
new file mode 100644
index 00000000..3826176f
--- /dev/null
+++ b/bench/env.__setitem__.py
@@ -0,0 +1,362 @@
+# __COPYRIGHT__
+#
+# Benchmarks for testing various possible implementations of the
+# env.__setitem__() method(s) in the src/engine/SCons/Environment.py
+# module.
+
+import os.path
+import re
+import string
+import sys
+import timeit
+
+# Utility Timing class and function from:
+# ASPN: Python Cookbook : Timing various python statements
+# http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/544297
+#
+# These wrap the basic timeit function to make it a little more
+# convenient to do side-by-side tests of code.
+
+class Timing:
+ def __init__(self, name, num, init, statement):
+ self.__timer = timeit.Timer(statement, init)
+ self.__num = num
+ self.name = name
+ self.statement = statement
+ self.__result = None
+
+ def timeit(self):
+ self.__result = self.__timer.timeit(self.__num)
+
+ def getResult(self):
+ return self.__result
+
+def times(num=1000000, init='', title='Results:', **statements):
+ # time each statement
+ timings = []
+ for n, s in statements.items():
+ t = Timing(n, num, init, s)
+ t.timeit()
+ timings.append(t)
+
+ print
+ print title
+ l = []
+ for i in timings: l.append((i.getResult(),i.name))
+ l.sort()
+ for i in l: print " %9.3f s %s" % i
+
+# Import the necessary local SCons.* modules used by some of our
+# alternative implementations below, first manipulating sys.path so
+# we pull in the right local modules without forcing the user to set
+# PYTHONPATH.
+
+import __main__
+try:
+ filename = __main__.__file__
+except AttributeError:
+ filename = sys.argv[0]
+script_dir = os.path.split(filename)[0]
+if script_dir:
+ script_dir = script_dir + '/'
+sys.path = [os.path.abspath(script_dir + '../src/engine')] + sys.path
+
+import SCons.Errors
+import SCons.Environment
+
+is_valid_construction_var = SCons.Environment.is_valid_construction_var
+global_valid_var = re.compile(r'[_a-zA-Z]\w*$')
+
+# The classes with different __setitem__() implementations that we're
+# going to horse-race.
+#
+# The base class (Environment) should contain *all* class initialization
+# of anything that will be used by any of the competing sub-class
+# implementations. Each timing run will create an instance of the class,
+# and all competing sub-classes should share the same initialization
+# overhead so our timing focuses on just the __setitem__() performance.
+#
+# All subclasses should be prefixed with env_, in which case they'll be
+# picked up automatically by the code below for testing.
+#
+# The env_Original subclass contains the original implementation (which
+# actually had the is_valid_construction_var() function in SCons.Util
+# originally).
+#
+# The other subclasses (except for env_Best) each contain *one*
+# significant change from the env_Original implementation. The doc string
+# describes the change, and is what gets displayed in the final timing.
+# The doc strings of these other subclasses are "grouped" informally
+# by a prefix that kind of indicates what specific aspect of __setitem__()
+# is being varied and tested.
+#
+# The env_Best subclass contains the "best practices" from each of
+# the different "groups" of techniques tested in the other subclasses,
+# and is where to experiment with different combinations of techniques.
+# After we're done should be the one that shows up at the top of the
+# list as we run our timings.
+
+class Environment:
+ _special_set = {
+ 'BUILDERS' : None,
+ 'SCANNERS' : None,
+ 'TARGET' : None,
+ 'TARGETS' : None,
+ 'SOURCE' : None,
+ 'SOURCES' : None,
+ }
+ _special_set_keys = _special_set.keys()
+ _valid_var = re.compile(r'[_a-zA-Z]\w*$')
+ def __init__(self, **kw):
+ self._dict = kw
+
+class env_Original(Environment):
+ """Original __setitem__()"""
+ def __setitem__(self, key, value):
+ special = self._special_set.get(key)
+ if special:
+ special(self, key, value)
+ else:
+ if not SCons.Environment.is_valid_construction_var(key):
+ raise SCons.Errors.UserError, "Illegal construction variable `%s'" % key
+ self._dict[key] = value
+
+class env_Global_is_valid(Environment):
+ """is_valid_construction_var(): use a global function"""
+ def __setitem__(self, key, value):
+ special = self._special_set.get(key)
+ if special:
+ special(self, key, value)
+ else:
+ if not is_valid_construction_var(key):
+ raise SCons.Errors.UserError, "Illegal construction variable `%s'" % key
+ self._dict[key] = value
+
+class env_Method_is_valid(Environment):
+ """is_valid_construction_var(): use a method"""
+ def is_valid_construction_var(self, varstr):
+ """Return if the specified string is a legitimate construction
+ variable.
+ """
+ return self._valid_var.match(varstr)
+
+ def __setitem__(self, key, value):
+ special = self._special_set.get(key)
+ if special:
+ special(self, key, value)
+ else:
+ if not self.is_valid_construction_var(key):
+ raise SCons.Errors.UserError, "Illegal construction variable `%s'" % key
+ self._dict[key] = value
+
+class env_regex_attribute_is_valid(Environment):
+ """is_valid_construction_var(): use a regex attribute"""
+ def __setitem__(self, key, value):
+ special = self._special_set.get(key)
+ if special:
+ special(self, key, value)
+ else:
+ if not self._valid_var.match(key):
+ raise SCons.Errors.UserError, "Illegal construction variable `%s'" % key
+ self._dict[key] = value
+
+class env_global_regex_is_valid(Environment):
+ """is_valid_construction_var(): use a global regex"""
+ def __setitem__(self, key, value):
+ special = self._special_set.get(key)
+ if special:
+ special(self, key, value)
+ else:
+ if not global_valid_var.match(key):
+ raise SCons.Errors.UserError, "Illegal construction variable `%s'" % key
+ self._dict[key] = value
+
+class env_special_set_has_key(Environment):
+ """_special_set.get(): use _special_set.has_key() instead"""
+ def __setitem__(self, key, value):
+ if self._special_set.has_key(key):
+ self._special_set[key](self, key, value)
+ else:
+ if not SCons.Environment.is_valid_construction_var(key):
+ raise SCons.Errors.UserError, "Illegal construction variable `%s'" % key
+ self._dict[key] = value
+
+class env_key_in_tuple(Environment):
+ """_special_set.get(): use "key in tuple" instead"""
+ def __setitem__(self, key, value):
+ if key in ('BUILDERS', 'SCANNERS', 'TARGET', 'TARGETS', 'SOURCE', 'SOURCES'):
+ self._special_set[key](self, key, value)
+ else:
+ if not SCons.Environment.is_valid_construction_var(key):
+ raise SCons.Errors.UserError, "Illegal construction variable `%s'" % key
+ self._dict[key] = value
+
+class env_key_in_list(Environment):
+ """_special_set.get(): use "key in list" instead"""
+ def __setitem__(self, key, value):
+ if key in ['BUILDERS', 'SCANNERS', 'TARGET', 'TARGETS', 'SOURCE', 'SOURCES']:
+ self._special_set[key](self, key, value)
+ else:
+ if not SCons.Environment.is_valid_construction_var(key):
+ raise SCons.Errors.UserError, "Illegal construction variable `%s'" % key
+ self._dict[key] = value
+
+class env_key_in_attribute(Environment):
+ """_special_set.get(): use "key in attribute" instead"""
+ def __setitem__(self, key, value):
+ if key in self._special_set_keys:
+ self._special_set[key](self, key, value)
+ else:
+ if not SCons.Environment.is_valid_construction_var(key):
+ raise SCons.Errors.UserError, "Illegal construction variable `%s'" % key
+ self._dict[key] = value
+
+class env_try_except(Environment):
+ """avoid is_valid_construction_var(): use try:-except:"""
+ def __setitem__(self, key, value):
+ special = self._special_set.get(key)
+ if special:
+ special(self, key, value)
+ else:
+ try:
+ self._dict[key]
+ except KeyError:
+ if not SCons.Environment.is_valid_construction_var(key):
+ raise SCons.Errors.UserError, "Illegal construction variable `%s'" % key
+ self._dict[key] = value
+
+class env_not_has_key(Environment):
+ """avoid is_valid_construction_var(): use not .has_key()"""
+ def __setitem__(self, key, value):
+ special = self._special_set.get(key)
+ if special:
+ special(self, key, value)
+ else:
+ if not self._dict.has_key(key) \
+ and not SCons.Environment.is_valid_construction_var(key):
+ raise SCons.Errors.UserError, "Illegal construction variable `%s'" % key
+ self._dict[key] = value
+
+class env_Best_attribute(Environment):
+ """Best __setitem__(), with an attribute"""
+ def __setitem__(self, key, value):
+ if key in self._special_set_keys:
+ self._special_set[key](self, key, value)
+ else:
+ if not self._dict.has_key(key) \
+ and not global_valid_var.match(key):
+ raise SCons.Errors.UserError, "Illegal construction variable `%s'" % key
+ self._dict[key] = value
+
+class env_Best_has_key(Environment):
+ """Best __setitem__(), with has_key"""
+ def __setitem__(self, key, value):
+ if self._special_set.has_key(key):
+ self._special_set[key](self, key, value)
+ else:
+ if not self._dict.has_key(key) \
+ and not global_valid_var.match(key):
+ raise SCons.Errors.UserError, "Illegal construction variable `%s'" % key
+ self._dict[key] = value
+
+class env_Best_list(Environment):
+ """Best __setitem__(), with a list"""
+ def __setitem__(self, key, value):
+ if key in ['BUILDERS', 'SCANNERS', 'TARGET', 'TARGETS', 'SOURCE', 'SOURCES']:
+ self._special_set[key](self, key, value)
+ else:
+ if not self._dict.has_key(key) \
+ and not global_valid_var.match(key):
+ raise SCons.Errors.UserError, "Illegal construction variable `%s'" % key
+ self._dict[key] = value
+
+try:
+ ''.isalnum
+except AttributeError:
+ pass
+else:
+ class env_isalnum(Environment):
+ """Greg's Folly: isalnum instead of probe"""
+ def __setitem__(self, key, value):
+ if self._special_set.has_key(key):
+ self._special_set[key](self, key, value)
+ else:
+ if not key.isalnum() and not global_valid_var.match(key):
+ raise SCons.Errors.UserError, "Illegal construction variable `%s'" % key
+ self._dict[key] = value
+
+# We'll use the names of all the env_* classes we find later to build
+# the dictionary of statements to be timed, and the import statement
+# that the timer will use to get at these classes.
+
+class_names = []
+for n in locals().keys():
+ #if n.startswith('env_'):
+ if n[:4] == 'env_':
+ class_names.append(n)
+
+# This is *the* function that gets timed. It will get called for the
+# specified number of iterations for the cross product of the number of
+# classes we're testing and the number of data sets (defined below).
+
+iterations = 10000
+
+def do_it(names, env_class):
+ e = env_class()
+ for key in names:
+ e[key] = 1
+
+# Build the list of "statements" that will be tested. For each class
+# we're testing, the doc string describing the class is the key, and
+# the statement we test is a simple "doit(names, {class})" call.
+
+statements = {}
+
+for class_name in class_names:
+ ec = eval(class_name)
+ statements[ec.__doc__] = 'do_it(names, %s)' % class_name
+
+# The common_imports string is used in the initialization of each
+# test run. The timeit module insulates the test snippets from the
+# global namespace, so we have to import these explicitly from __main__.
+
+common_import_variables = ['do_it'] + class_names
+
+common_imports = """
+from __main__ import %s
+""" % string.join(common_import_variables, ', ')
+
+# The test data (lists of variable names) that we'll use for the runs.
+
+same_variable_names = ['XXX'] * 100
+uniq_variable_names = []
+for i in range(100): uniq_variable_names.append('X%05d' % i)
+mixed_variable_names = uniq_variable_names[:50] + same_variable_names[:50]
+
+# Lastly, put it all together...
+
+def run_it(title, init):
+ s = statements.copy()
+ s['num'] = iterations
+ s['title'] = title
+ s['init'] = init
+ apply(times,(),s)
+
+print 'Environment __setitem__ benchmark using',
+print 'Python', string.split(sys.version)[0],
+print 'on', sys.platform, os.name
+
+run_it('Results for re-adding an existing variable name 100 times:',
+ common_imports + """
+import __main__ ; names = __main__.same_variable_names
+""")
+
+run_it('Results for adding 100 variable names, 50 existing and 50 new:',
+ common_imports + """
+import __main__ ; names = __main__.mixed_variable_names
+""")
+
+run_it('Results for adding 100 new, unique variable names:',
+ common_imports + """
+import __main__ ; names = __main__.uniq_variable_names
+""")