3 # Benchmarks for testing various possible implementations of the
4 # env.__setitem__() method(s) in the src/engine/SCons/Environment.py
13 # Utility Timing class and function from:
14 # ASPN: Python Cookbook : Timing various python statements
15 # http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/544297
17 # These wrap the basic timeit function to make it a little more
18 # convenient to do side-by-side tests of code.
21 def __init__(self, name, num, init, statement):
22 self.__timer = timeit.Timer(statement, init)
25 self.statement = statement
29 self.__result = self.__timer.timeit(self.__num)
34 def times(num=1000000, init='', title='Results:', **statements):
37 for n, s in statements.items():
38 t = Timing(n, num, init, s)
45 for i in timings: l.append((i.getResult(),i.name))
47 for i in l: print " %9.3f s %s" % i
49 # Import the necessary local SCons.* modules used by some of our
50 # alternative implementations below, first manipulating sys.path so
51 # we pull in the right local modules without forcing the user to set
56 filename = __main__.__file__
57 except AttributeError:
58 filename = sys.argv[0]
59 script_dir = os.path.split(filename)[0]
61 script_dir = script_dir + '/'
62 sys.path = [os.path.abspath(script_dir + '../src/engine')] + sys.path
65 import SCons.Environment
67 is_valid_construction_var = SCons.Environment.is_valid_construction_var
68 global_valid_var = re.compile(r'[_a-zA-Z]\w*$')
70 # The classes with different __setitem__() implementations that we're
71 # going to horse-race.
73 # The base class (Environment) should contain *all* class initialization
74 # of anything that will be used by any of the competing sub-class
75 # implementations. Each timing run will create an instance of the class,
76 # and all competing sub-classes should share the same initialization
77 # overhead so our timing focuses on just the __setitem__() performance.
79 # All subclasses should be prefixed with env_, in which case they'll be
80 # picked up automatically by the code below for testing.
82 # The env_Original subclass contains the original implementation (which
83 # actually had the is_valid_construction_var() function in SCons.Util
86 # The other subclasses (except for env_Best) each contain *one*
87 # significant change from the env_Original implementation. The doc string
88 # describes the change, and is what gets displayed in the final timing.
89 # The doc strings of these other subclasses are "grouped" informally
90 # by a prefix that kind of indicates what specific aspect of __setitem__()
91 # is being varied and tested.
93 # The env_Best subclass contains the "best practices" from each of
94 # the different "groups" of techniques tested in the other subclasses,
95 # and is where to experiment with different combinations of techniques.
96 # After we're done should be the one that shows up at the top of the
97 # list as we run our timings.
108 _special_set_keys = _special_set.keys()
109 _valid_var = re.compile(r'[_a-zA-Z]\w*$')
110 def __init__(self, **kw):
113 class env_Original(Environment):
114 """Original __setitem__()"""
115 def __setitem__(self, key, value):
116 special = self._special_set.get(key)
118 special(self, key, value)
120 if not SCons.Environment.is_valid_construction_var(key):
121 raise SCons.Errors.UserError, "Illegal construction variable `%s'" % key
122 self._dict[key] = value
124 class env_Global_is_valid(Environment):
125 """is_valid_construction_var(): use a global function"""
126 def __setitem__(self, key, value):
127 special = self._special_set.get(key)
129 special(self, key, value)
131 if not is_valid_construction_var(key):
132 raise SCons.Errors.UserError, "Illegal construction variable `%s'" % key
133 self._dict[key] = value
135 class env_Method_is_valid(Environment):
136 """is_valid_construction_var(): use a method"""
137 def is_valid_construction_var(self, varstr):
138 """Return if the specified string is a legitimate construction
141 return self._valid_var.match(varstr)
143 def __setitem__(self, key, value):
144 special = self._special_set.get(key)
146 special(self, key, value)
148 if not self.is_valid_construction_var(key):
149 raise SCons.Errors.UserError, "Illegal construction variable `%s'" % key
150 self._dict[key] = value
152 class env_regex_attribute_is_valid(Environment):
153 """is_valid_construction_var(): use a regex attribute"""
154 def __setitem__(self, key, value):
155 special = self._special_set.get(key)
157 special(self, key, value)
159 if not self._valid_var.match(key):
160 raise SCons.Errors.UserError, "Illegal construction variable `%s'" % key
161 self._dict[key] = value
163 class env_global_regex_is_valid(Environment):
164 """is_valid_construction_var(): use a global regex"""
165 def __setitem__(self, key, value):
166 special = self._special_set.get(key)
168 special(self, key, value)
170 if not global_valid_var.match(key):
171 raise SCons.Errors.UserError, "Illegal construction variable `%s'" % key
172 self._dict[key] = value
174 class env_special_set_has_key(Environment):
175 """_special_set.get(): use _special_set.has_key() instead"""
176 def __setitem__(self, key, value):
177 if self._special_set.has_key(key):
178 self._special_set[key](self, key, value)
180 if not SCons.Environment.is_valid_construction_var(key):
181 raise SCons.Errors.UserError, "Illegal construction variable `%s'" % key
182 self._dict[key] = value
184 class env_key_in_tuple(Environment):
185 """_special_set.get(): use "key in tuple" instead"""
186 def __setitem__(self, key, value):
187 if key in ('BUILDERS', 'SCANNERS', 'TARGET', 'TARGETS', 'SOURCE', 'SOURCES'):
188 self._special_set[key](self, key, value)
190 if not SCons.Environment.is_valid_construction_var(key):
191 raise SCons.Errors.UserError, "Illegal construction variable `%s'" % key
192 self._dict[key] = value
194 class env_key_in_list(Environment):
195 """_special_set.get(): use "key in list" instead"""
196 def __setitem__(self, key, value):
197 if key in ['BUILDERS', 'SCANNERS', 'TARGET', 'TARGETS', 'SOURCE', 'SOURCES']:
198 self._special_set[key](self, key, value)
200 if not SCons.Environment.is_valid_construction_var(key):
201 raise SCons.Errors.UserError, "Illegal construction variable `%s'" % key
202 self._dict[key] = value
204 class env_key_in_attribute(Environment):
205 """_special_set.get(): use "key in attribute" instead"""
206 def __setitem__(self, key, value):
207 if key in self._special_set_keys:
208 self._special_set[key](self, key, value)
210 if not SCons.Environment.is_valid_construction_var(key):
211 raise SCons.Errors.UserError, "Illegal construction variable `%s'" % key
212 self._dict[key] = value
214 class env_try_except(Environment):
215 """avoid is_valid_construction_var(): use try:-except:"""
216 def __setitem__(self, key, value):
217 special = self._special_set.get(key)
219 special(self, key, value)
224 if not SCons.Environment.is_valid_construction_var(key):
225 raise SCons.Errors.UserError, "Illegal construction variable `%s'" % key
226 self._dict[key] = value
228 class env_not_has_key(Environment):
229 """avoid is_valid_construction_var(): use not .has_key()"""
230 def __setitem__(self, key, value):
231 special = self._special_set.get(key)
233 special(self, key, value)
235 if not self._dict.has_key(key) \
236 and not SCons.Environment.is_valid_construction_var(key):
237 raise SCons.Errors.UserError, "Illegal construction variable `%s'" % key
238 self._dict[key] = value
240 class env_Best_attribute(Environment):
241 """Best __setitem__(), with an attribute"""
242 def __setitem__(self, key, value):
243 if key in self._special_set_keys:
244 self._special_set[key](self, key, value)
246 if not self._dict.has_key(key) \
247 and not global_valid_var.match(key):
248 raise SCons.Errors.UserError, "Illegal construction variable `%s'" % key
249 self._dict[key] = value
251 class env_Best_has_key(Environment):
252 """Best __setitem__(), with has_key"""
253 def __setitem__(self, key, value):
254 if self._special_set.has_key(key):
255 self._special_set[key](self, key, value)
257 if not self._dict.has_key(key) \
258 and not global_valid_var.match(key):
259 raise SCons.Errors.UserError, "Illegal construction variable `%s'" % key
260 self._dict[key] = value
262 class env_Best_list(Environment):
263 """Best __setitem__(), with a list"""
264 def __setitem__(self, key, value):
265 if key in ['BUILDERS', 'SCANNERS', 'TARGET', 'TARGETS', 'SOURCE', 'SOURCES']:
266 self._special_set[key](self, key, value)
268 if not self._dict.has_key(key) \
269 and not global_valid_var.match(key):
270 raise SCons.Errors.UserError, "Illegal construction variable `%s'" % key
271 self._dict[key] = value
275 except AttributeError:
278 class env_isalnum(Environment):
279 """Greg's Folly: isalnum instead of probe"""
280 def __setitem__(self, key, value):
281 if self._special_set.has_key(key):
282 self._special_set[key](self, key, value)
284 if not key.isalnum() and not global_valid_var.match(key):
285 raise SCons.Errors.UserError, "Illegal construction variable `%s'" % key
286 self._dict[key] = value
288 # We'll use the names of all the env_* classes we find later to build
289 # the dictionary of statements to be timed, and the import statement
290 # that the timer will use to get at these classes.
293 for n in locals().keys():
294 #if n.startswith('env_'):
296 class_names.append(n)
298 # This is *the* function that gets timed. It will get called for the
299 # specified number of iterations for the cross product of the number of
300 # classes we're testing and the number of data sets (defined below).
304 def do_it(names, env_class):
309 # Build the list of "statements" that will be tested. For each class
310 # we're testing, the doc string describing the class is the key, and
311 # the statement we test is a simple "doit(names, {class})" call.
315 for class_name in class_names:
316 ec = eval(class_name)
317 statements[ec.__doc__] = 'do_it(names, %s)' % class_name
319 # The common_imports string is used in the initialization of each
320 # test run. The timeit module insulates the test snippets from the
321 # global namespace, so we have to import these explicitly from __main__.
323 common_import_variables = ['do_it'] + class_names
326 from __main__ import %s
327 """ % string.join(common_import_variables, ', ')
329 # The test data (lists of variable names) that we'll use for the runs.
331 same_variable_names = ['XXX'] * 100
332 uniq_variable_names = []
333 for i in range(100): uniq_variable_names.append('X%05d' % i)
334 mixed_variable_names = uniq_variable_names[:50] + same_variable_names[:50]
336 # Lastly, put it all together...
338 def run_it(title, init):
339 s = statements.copy()
340 s['num'] = iterations
345 print 'Environment __setitem__ benchmark using',
346 print 'Python', string.split(sys.version)[0],
347 print 'on', sys.platform, os.name
349 run_it('Results for re-adding an existing variable name 100 times:',
351 import __main__ ; names = __main__.same_variable_names
354 run_it('Results for adding 100 variable names, 50 existing and 50 new:',
356 import __main__ ; names = __main__.mixed_variable_names
359 run_it('Results for adding 100 new, unique variable names:',
361 import __main__ ; names = __main__.uniq_variable_names
366 # indent-tabs-mode:nil
368 # vim: set expandtab tabstop=4 shiftwidth=4: