Reproducible builds info updated
[scons.git] / bench / env.__setitem__.py
blobf9fe0c0cd1d8541c7836c4736c84f9b97ce534bc
1 # __COPYRIGHT__
3 # Benchmarks for testing various possible implementations of the
4 # env.__setitem__() method(s) in the src/engine/SCons/Environment.py
5 # module.
7 from __future__ import print_function
9 import os.path
10 import re
11 import sys
12 import timeit
14 # Utility Timing class and function from:
15 # ASPN: Python Cookbook : Timing various python statements
16 # http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/544297
18 # These wrap the basic timeit function to make it a little more
19 # convenient to do side-by-side tests of code.
21 class Timing:
22 def __init__(self, name, num, init, statement):
23 self.__timer = timeit.Timer(statement, init)
24 self.__num = num
25 self.name = name
26 self.statement = statement
27 self.__result = None
29 def timeit(self):
30 self.__result = self.__timer.timeit(self.__num)
32 def getResult(self):
33 return self.__result
35 def times(num=1000000, init='', title='Results:', **statements):
36 # time each statement
37 timings = []
38 for n, s in statements.items():
39 t = Timing(n, num, init, s)
40 t.timeit()
41 timings.append(t)
43 print()
44 print(title)
45 for i in sorted([(i.getResult(),i.name) for i in timings]):
46 print(" %9.3f s %s" % i)
48 # Import the necessary local SCons.* modules used by some of our
49 # alternative implementations below, first manipulating sys.path so
50 # we pull in the right local modules without forcing the user to set
51 # PYTHONPATH.
53 import __main__
54 try:
55 filename = __main__.__file__
56 except AttributeError:
57 filename = sys.argv[0]
58 script_dir = os.path.split(filename)[0]
59 if script_dir:
60 script_dir = script_dir + '/'
61 sys.path = [os.path.abspath(script_dir + '../src/engine')] + sys.path
63 import SCons.Errors
64 import SCons.Environment
66 is_valid_construction_var = SCons.Environment.is_valid_construction_var
67 global_valid_var = re.compile(r'[_a-zA-Z]\w*$')
69 # The classes with different __setitem__() implementations that we're
70 # going to horse-race.
72 # The base class (Environment) should contain *all* class initialization
73 # of anything that will be used by any of the competing sub-class
74 # implementations. Each timing run will create an instance of the class,
75 # and all competing sub-classes should share the same initialization
76 # overhead so our timing focuses on just the __setitem__() performance.
78 # All subclasses should be prefixed with env_, in which case they'll be
79 # picked up automatically by the code below for testing.
81 # The env_Original subclass contains the original implementation (which
82 # actually had the is_valid_construction_var() function in SCons.Util
83 # originally).
85 # The other subclasses (except for env_Best) each contain *one*
86 # significant change from the env_Original implementation. The doc string
87 # describes the change, and is what gets displayed in the final timing.
88 # The doc strings of these other subclasses are "grouped" informally
89 # by a prefix that kind of indicates what specific aspect of __setitem__()
90 # is being varied and tested.
92 # The env_Best subclass contains the "best practices" from each of
93 # the different "groups" of techniques tested in the other subclasses,
94 # and is where to experiment with different combinations of techniques.
95 # After we're done should be the one that shows up at the top of the
96 # list as we run our timings.
98 class Environment:
99 _special_set = {
100 'BUILDERS' : None,
101 'SCANNERS' : None,
102 'TARGET' : None,
103 'TARGETS' : None,
104 'SOURCE' : None,
105 'SOURCES' : None,
107 _special_set_keys = list(_special_set.keys())
108 _valid_var = re.compile(r'[_a-zA-Z]\w*$')
109 def __init__(self, **kw):
110 self._dict = kw
112 class env_Original(Environment):
113 """Original __setitem__()"""
114 def __setitem__(self, key, value):
115 special = self._special_set.get(key)
116 if special:
117 special(self, key, value)
118 else:
119 if not SCons.Environment.is_valid_construction_var(key):
120 raise SCons.Errors.UserError("Illegal construction variable `%s'" % key)
121 self._dict[key] = value
123 class env_Global_is_valid(Environment):
124 """is_valid_construction_var(): use a global function"""
125 def __setitem__(self, key, value):
126 special = self._special_set.get(key)
127 if special:
128 special(self, key, value)
129 else:
130 if not is_valid_construction_var(key):
131 raise SCons.Errors.UserError("Illegal construction variable `%s'" % key)
132 self._dict[key] = value
134 class env_Method_is_valid(Environment):
135 """is_valid_construction_var(): use a method"""
136 def is_valid_construction_var(self, varstr):
137 """Return if the specified string is a legitimate construction
138 variable.
140 return self._valid_var.match(varstr)
142 def __setitem__(self, key, value):
143 special = self._special_set.get(key)
144 if special:
145 special(self, key, value)
146 else:
147 if not self.is_valid_construction_var(key):
148 raise SCons.Errors.UserError("Illegal construction variable `%s'" % key)
149 self._dict[key] = value
151 class env_regex_attribute_is_valid(Environment):
152 """is_valid_construction_var(): use a regex attribute"""
153 def __setitem__(self, key, value):
154 special = self._special_set.get(key)
155 if special:
156 special(self, key, value)
157 else:
158 if not self._valid_var.match(key):
159 raise SCons.Errors.UserError("Illegal construction variable `%s'" % key)
160 self._dict[key] = value
162 class env_global_regex_is_valid(Environment):
163 """is_valid_construction_var(): use a global regex"""
164 def __setitem__(self, key, value):
165 special = self._special_set.get(key)
166 if special:
167 special(self, key, value)
168 else:
169 if not global_valid_var.match(key):
170 raise SCons.Errors.UserError("Illegal construction variable `%s'" % key)
171 self._dict[key] = value
173 class env_special_set_has_key(Environment):
174 """_special_set.get(): use _special_set.has_key() instead"""
175 def __setitem__(self, key, value):
176 if key in self._special_set:
177 self._special_set[key](self, key, value)
178 else:
179 if not SCons.Environment.is_valid_construction_var(key):
180 raise SCons.Errors.UserError("Illegal construction variable `%s'" % key)
181 self._dict[key] = value
183 class env_key_in_tuple(Environment):
184 """_special_set.get(): use "key in tuple" instead"""
185 def __setitem__(self, key, value):
186 if key in ('BUILDERS', 'SCANNERS', 'TARGET', 'TARGETS', 'SOURCE', 'SOURCES'):
187 self._special_set[key](self, key, value)
188 else:
189 if not SCons.Environment.is_valid_construction_var(key):
190 raise SCons.Errors.UserError("Illegal construction variable `%s'" % key)
191 self._dict[key] = value
193 class env_key_in_list(Environment):
194 """_special_set.get(): use "key in list" instead"""
195 def __setitem__(self, key, value):
196 if key in ['BUILDERS', 'SCANNERS', 'TARGET', 'TARGETS', 'SOURCE', 'SOURCES']:
197 self._special_set[key](self, key, value)
198 else:
199 if not SCons.Environment.is_valid_construction_var(key):
200 raise SCons.Errors.UserError("Illegal construction variable `%s'" % key)
201 self._dict[key] = value
203 class env_key_in_attribute(Environment):
204 """_special_set.get(): use "key in attribute" instead"""
205 def __setitem__(self, key, value):
206 if key in self._special_set_keys:
207 self._special_set[key](self, key, value)
208 else:
209 if not SCons.Environment.is_valid_construction_var(key):
210 raise SCons.Errors.UserError("Illegal construction variable `%s'" % key)
211 self._dict[key] = value
213 class env_try_except(Environment):
214 """avoid is_valid_construction_var(): use try:-except:"""
215 def __setitem__(self, key, value):
216 special = self._special_set.get(key)
217 if special:
218 special(self, key, value)
219 else:
220 try:
221 self._dict[key]
222 except KeyError:
223 if not SCons.Environment.is_valid_construction_var(key):
224 raise SCons.Errors.UserError("Illegal construction variable `%s'" % key)
225 self._dict[key] = value
227 class env_not_has_key(Environment):
228 """avoid is_valid_construction_var(): use not .has_key()"""
229 def __setitem__(self, key, value):
230 special = self._special_set.get(key)
231 if special:
232 special(self, key, value)
233 else:
234 if key not in self._dict \
235 and not SCons.Environment.is_valid_construction_var(key):
236 raise SCons.Errors.UserError("Illegal construction variable `%s'" % key)
237 self._dict[key] = value
239 class env_Best_attribute(Environment):
240 """Best __setitem__(), with an attribute"""
241 def __setitem__(self, key, value):
242 if key in self._special_set_keys:
243 self._special_set[key](self, key, value)
244 else:
245 if key not in self._dict \
246 and not global_valid_var.match(key):
247 raise SCons.Errors.UserError("Illegal construction variable `%s'" % key)
248 self._dict[key] = value
250 class env_Best_has_key(Environment):
251 """Best __setitem__(), with has_key"""
252 def __setitem__(self, key, value):
253 if key in self._special_set:
254 self._special_set[key](self, key, value)
255 else:
256 if key not in self._dict \
257 and not global_valid_var.match(key):
258 raise SCons.Errors.UserError("Illegal construction variable `%s'" % key)
259 self._dict[key] = value
261 class env_Best_list(Environment):
262 """Best __setitem__(), with a list"""
263 def __setitem__(self, key, value):
264 if key in ['BUILDERS', 'SCANNERS', 'TARGET', 'TARGETS', 'SOURCE', 'SOURCES']:
265 self._special_set[key](self, key, value)
266 else:
267 if key not in self._dict \
268 and not global_valid_var.match(key):
269 raise SCons.Errors.UserError("Illegal construction variable `%s'" % key)
270 self._dict[key] = value
272 try:
273 ''.isalnum
274 except AttributeError:
275 pass
276 else:
277 class env_isalnum(Environment):
278 """Greg's Folly: isalnum instead of probe"""
279 def __setitem__(self, key, value):
280 if key in self._special_set:
281 self._special_set[key](self, key, value)
282 else:
283 if not key.isalnum() and not global_valid_var.match(key):
284 raise SCons.Errors.UserError("Illegal construction variable `%s'" % key)
285 self._dict[key] = value
287 # We'll use the names of all the env_* classes we find later to build
288 # the dictionary of statements to be timed, and the import statement
289 # that the timer will use to get at these classes.
291 class_names = []
292 for n in list(locals().keys()):
293 #if n.startswith('env_'):
294 if n[:4] == 'env_':
295 class_names.append(n)
297 # This is *the* function that gets timed. It will get called for the
298 # specified number of iterations for the cross product of the number of
299 # classes we're testing and the number of data sets (defined below).
301 iterations = 10000
303 def do_it(names, env_class):
304 e = env_class()
305 for key in names:
306 e[key] = 1
308 # Build the list of "statements" that will be tested. For each class
309 # we're testing, the doc string describing the class is the key, and
310 # the statement we test is a simple "doit(names, {class})" call.
312 statements = {}
314 for class_name in class_names:
315 ec = eval(class_name)
316 statements[ec.__doc__] = 'do_it(names, %s)' % class_name
318 # The common_imports string is used in the initialization of each
319 # test run. The timeit module insulates the test snippets from the
320 # global namespace, so we have to import these explicitly from __main__.
322 common_import_variables = ['do_it'] + class_names
324 common_imports = """
325 from __main__ import %s
326 """ % ', '.join(common_import_variables)
328 # The test data (lists of variable names) that we'll use for the runs.
330 same_variable_names = ['XXX'] * 100
331 uniq_variable_names = []
332 for i in range(100): uniq_variable_names.append('X%05d' % i)
333 mixed_variable_names = uniq_variable_names[:50] + same_variable_names[:50]
335 # Lastly, put it all together...
337 def run_it(title, init):
338 s = statements.copy()
339 s['num'] = iterations
340 s['title'] = title
341 s['init'] = init
342 times(**s)
344 print('Environment __setitem__ benchmark using', end=' ')
345 print('Python', sys.version.split()[0], end=' ')
346 print('on', sys.platform, os.name)
348 run_it('Results for re-adding an existing variable name 100 times:',
349 common_imports + """
350 import __main__ ; names = __main__.same_variable_names
351 """)
353 run_it('Results for adding 100 variable names, 50 existing and 50 new:',
354 common_imports + """
355 import __main__ ; names = __main__.mixed_variable_names
356 """)
358 run_it('Results for adding 100 new, unique variable names:',
359 common_imports + """
360 import __main__ ; names = __main__.uniq_variable_names
361 """)
363 # Local Variables:
364 # tab-width:4
365 # indent-tabs-mode:nil
366 # End:
367 # vim: set expandtab tabstop=4 shiftwidth=4: