3 # Benchmarks for testing various possible implementations of the
4 # env.__setitem__() method(s) in the src/engine/SCons/Environment.py
12 # Utility Timing class and function from:
13 # ASPN: Python Cookbook : Timing various python statements
14 # http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/544297
16 # These wrap the basic timeit function to make it a little more
17 # convenient to do side-by-side tests of code.
20 def __init__(self, name, num, init, statement):
21 self.__timer = timeit.Timer(statement, init)
24 self.statement = statement
28 self.__result = self.__timer.timeit(self.__num)
33 def times(num=1000000, init='', title='Results:', **statements):
36 for n, s in statements.items():
37 t = Timing(n, num, init, s)
43 for i in sorted([(i.getResult(),i.name) for i in timings]):
44 print " %9.3f s %s" % i
46 # Import the necessary local SCons.* modules used by some of our
47 # alternative implementations below, first manipulating sys.path so
48 # we pull in the right local modules without forcing the user to set
53 filename = __main__.__file__
54 except AttributeError:
55 filename = sys.argv[0]
56 script_dir = os.path.split(filename)[0]
58 script_dir = script_dir + '/'
59 sys.path = [os.path.abspath(script_dir + '../src/engine')] + sys.path
62 import SCons.Environment
64 is_valid_construction_var = SCons.Environment.is_valid_construction_var
65 global_valid_var = re.compile(r'[_a-zA-Z]\w*$')
67 # The classes with different __setitem__() implementations that we're
68 # going to horse-race.
70 # The base class (Environment) should contain *all* class initialization
71 # of anything that will be used by any of the competing sub-class
72 # implementations. Each timing run will create an instance of the class,
73 # and all competing sub-classes should share the same initialization
74 # overhead so our timing focuses on just the __setitem__() performance.
76 # All subclasses should be prefixed with env_, in which case they'll be
77 # picked up automatically by the code below for testing.
79 # The env_Original subclass contains the original implementation (which
80 # actually had the is_valid_construction_var() function in SCons.Util
83 # The other subclasses (except for env_Best) each contain *one*
84 # significant change from the env_Original implementation. The doc string
85 # describes the change, and is what gets displayed in the final timing.
86 # The doc strings of these other subclasses are "grouped" informally
87 # by a prefix that kind of indicates what specific aspect of __setitem__()
88 # is being varied and tested.
90 # The env_Best subclass contains the "best practices" from each of
91 # the different "groups" of techniques tested in the other subclasses,
92 # and is where to experiment with different combinations of techniques.
93 # After we're done should be the one that shows up at the top of the
94 # list as we run our timings.
105 _special_set_keys = _special_set.keys()
106 _valid_var = re.compile(r'[_a-zA-Z]\w*$')
107 def __init__(self, **kw):
110 class env_Original(Environment):
111 """Original __setitem__()"""
112 def __setitem__(self, key, value):
113 special = self._special_set.get(key)
115 special(self, key, value)
117 if not SCons.Environment.is_valid_construction_var(key):
118 raise SCons.Errors.UserError, "Illegal construction variable `%s'" % key
119 self._dict[key] = value
121 class env_Global_is_valid(Environment):
122 """is_valid_construction_var(): use a global function"""
123 def __setitem__(self, key, value):
124 special = self._special_set.get(key)
126 special(self, key, value)
128 if not is_valid_construction_var(key):
129 raise SCons.Errors.UserError, "Illegal construction variable `%s'" % key
130 self._dict[key] = value
132 class env_Method_is_valid(Environment):
133 """is_valid_construction_var(): use a method"""
134 def is_valid_construction_var(self, varstr):
135 """Return if the specified string is a legitimate construction
138 return self._valid_var.match(varstr)
140 def __setitem__(self, key, value):
141 special = self._special_set.get(key)
143 special(self, key, value)
145 if not self.is_valid_construction_var(key):
146 raise SCons.Errors.UserError, "Illegal construction variable `%s'" % key
147 self._dict[key] = value
149 class env_regex_attribute_is_valid(Environment):
150 """is_valid_construction_var(): use a regex attribute"""
151 def __setitem__(self, key, value):
152 special = self._special_set.get(key)
154 special(self, key, value)
156 if not self._valid_var.match(key):
157 raise SCons.Errors.UserError, "Illegal construction variable `%s'" % key
158 self._dict[key] = value
160 class env_global_regex_is_valid(Environment):
161 """is_valid_construction_var(): use a global regex"""
162 def __setitem__(self, key, value):
163 special = self._special_set.get(key)
165 special(self, key, value)
167 if not global_valid_var.match(key):
168 raise SCons.Errors.UserError, "Illegal construction variable `%s'" % key
169 self._dict[key] = value
171 class env_special_set_has_key(Environment):
172 """_special_set.get(): use _special_set.has_key() instead"""
173 def __setitem__(self, key, value):
174 if key in self._special_set:
175 self._special_set[key](self, key, value)
177 if not SCons.Environment.is_valid_construction_var(key):
178 raise SCons.Errors.UserError, "Illegal construction variable `%s'" % key
179 self._dict[key] = value
181 class env_key_in_tuple(Environment):
182 """_special_set.get(): use "key in tuple" instead"""
183 def __setitem__(self, key, value):
184 if key in ('BUILDERS', 'SCANNERS', 'TARGET', 'TARGETS', 'SOURCE', 'SOURCES'):
185 self._special_set[key](self, key, value)
187 if not SCons.Environment.is_valid_construction_var(key):
188 raise SCons.Errors.UserError, "Illegal construction variable `%s'" % key
189 self._dict[key] = value
191 class env_key_in_list(Environment):
192 """_special_set.get(): use "key in list" instead"""
193 def __setitem__(self, key, value):
194 if key in ['BUILDERS', 'SCANNERS', 'TARGET', 'TARGETS', 'SOURCE', 'SOURCES']:
195 self._special_set[key](self, key, value)
197 if not SCons.Environment.is_valid_construction_var(key):
198 raise SCons.Errors.UserError, "Illegal construction variable `%s'" % key
199 self._dict[key] = value
201 class env_key_in_attribute(Environment):
202 """_special_set.get(): use "key in attribute" instead"""
203 def __setitem__(self, key, value):
204 if key in self._special_set_keys:
205 self._special_set[key](self, key, value)
207 if not SCons.Environment.is_valid_construction_var(key):
208 raise SCons.Errors.UserError, "Illegal construction variable `%s'" % key
209 self._dict[key] = value
211 class env_try_except(Environment):
212 """avoid is_valid_construction_var(): use try:-except:"""
213 def __setitem__(self, key, value):
214 special = self._special_set.get(key)
216 special(self, key, value)
221 if not SCons.Environment.is_valid_construction_var(key):
222 raise SCons.Errors.UserError, "Illegal construction variable `%s'" % key
223 self._dict[key] = value
225 class env_not_has_key(Environment):
226 """avoid is_valid_construction_var(): use not .has_key()"""
227 def __setitem__(self, key, value):
228 special = self._special_set.get(key)
230 special(self, key, value)
232 if key not in self._dict \
233 and not SCons.Environment.is_valid_construction_var(key):
234 raise SCons.Errors.UserError, "Illegal construction variable `%s'" % key
235 self._dict[key] = value
237 class env_Best_attribute(Environment):
238 """Best __setitem__(), with an attribute"""
239 def __setitem__(self, key, value):
240 if key in self._special_set_keys:
241 self._special_set[key](self, key, value)
243 if key not in self._dict \
244 and not global_valid_var.match(key):
245 raise SCons.Errors.UserError, "Illegal construction variable `%s'" % key
246 self._dict[key] = value
248 class env_Best_has_key(Environment):
249 """Best __setitem__(), with has_key"""
250 def __setitem__(self, key, value):
251 if key in self._special_set:
252 self._special_set[key](self, key, value)
254 if key not in self._dict \
255 and not global_valid_var.match(key):
256 raise SCons.Errors.UserError, "Illegal construction variable `%s'" % key
257 self._dict[key] = value
259 class env_Best_list(Environment):
260 """Best __setitem__(), with a list"""
261 def __setitem__(self, key, value):
262 if key in ['BUILDERS', 'SCANNERS', 'TARGET', 'TARGETS', 'SOURCE', 'SOURCES']:
263 self._special_set[key](self, key, value)
265 if key not in self._dict \
266 and not global_valid_var.match(key):
267 raise SCons.Errors.UserError, "Illegal construction variable `%s'" % key
268 self._dict[key] = value
272 except AttributeError:
275 class env_isalnum(Environment):
276 """Greg's Folly: isalnum instead of probe"""
277 def __setitem__(self, key, value):
278 if key in self._special_set:
279 self._special_set[key](self, key, value)
281 if not key.isalnum() and not global_valid_var.match(key):
282 raise SCons.Errors.UserError, "Illegal construction variable `%s'" % key
283 self._dict[key] = value
285 # We'll use the names of all the env_* classes we find later to build
286 # the dictionary of statements to be timed, and the import statement
287 # that the timer will use to get at these classes.
290 for n in locals().keys():
291 #if n.startswith('env_'):
293 class_names.append(n)
295 # This is *the* function that gets timed. It will get called for the
296 # specified number of iterations for the cross product of the number of
297 # classes we're testing and the number of data sets (defined below).
301 def do_it(names, env_class):
306 # Build the list of "statements" that will be tested. For each class
307 # we're testing, the doc string describing the class is the key, and
308 # the statement we test is a simple "doit(names, {class})" call.
312 for class_name in class_names:
313 ec = eval(class_name)
314 statements[ec.__doc__] = 'do_it(names, %s)' % class_name
316 # The common_imports string is used in the initialization of each
317 # test run. The timeit module insulates the test snippets from the
318 # global namespace, so we have to import these explicitly from __main__.
320 common_import_variables = ['do_it'] + class_names
323 from __main__ import %s
324 """ % ', '.join(common_import_variables)
326 # The test data (lists of variable names) that we'll use for the runs.
328 same_variable_names = ['XXX'] * 100
329 uniq_variable_names = []
330 for i in range(100): uniq_variable_names.append('X%05d' % i)
331 mixed_variable_names = uniq_variable_names[:50] + same_variable_names[:50]
333 # Lastly, put it all together...
335 def run_it(title, init):
336 s = statements.copy()
337 s['num'] = iterations
342 print 'Environment __setitem__ benchmark using',
343 print 'Python', sys.version.split()[0],
344 print 'on', sys.platform, os.name
346 run_it('Results for re-adding an existing variable name 100 times:',
348 import __main__ ; names = __main__.same_variable_names
351 run_it('Results for adding 100 variable names, 50 existing and 50 new:',
353 import __main__ ; names = __main__.mixed_variable_names
356 run_it('Results for adding 100 new, unique variable names:',
358 import __main__ ; names = __main__.uniq_variable_names
363 # indent-tabs-mode:nil
365 # vim: set expandtab tabstop=4 shiftwidth=4: