61d8c1eb4fffd11292f49382fb5ebcfabeec8e8e
[scons.git] / bench / env.__setitem__.py
1 # __COPYRIGHT__
2 #
3 # Benchmarks for testing various possible implementations of the
4 # env.__setitem__() method(s) in the src/engine/SCons/Environment.py
5 # module.
6
7 import os.path
8 import re
9 import sys
10 import timeit
11
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
15 #
16 # These wrap the basic timeit function to make it a little more
17 # convenient to do side-by-side tests of code.
18
19 class Timing:
20     def __init__(self, name, num, init, statement):
21         self.__timer = timeit.Timer(statement, init)
22         self.__num   = num
23         self.name    = name
24         self.statement = statement
25         self.__result  = None
26         
27     def timeit(self):
28         self.__result = self.__timer.timeit(self.__num)
29         
30     def getResult(self):
31         return self.__result
32
33 def times(num=1000000, init='', title='Results:', **statements):
34     # time each statement
35     timings = []
36     for n, s in statements.items():
37         t = Timing(n, num, init, s)
38         t.timeit()
39         timings.append(t)
40     
41     print
42     print title
43     l = []
44     for i in timings: l.append((i.getResult(),i.name))
45     l.sort()
46     for i in l: print "  %9.3f s   %s" % i
47
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.
52
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
62
63 import SCons.Errors
64 import SCons.Environment
65
66 is_valid_construction_var = SCons.Environment.is_valid_construction_var
67 global_valid_var = re.compile(r'[_a-zA-Z]\w*$')
68
69 # The classes with different __setitem__() implementations that we're
70 # going to horse-race.
71 #
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.
77 #
78 # All subclasses should be prefixed with env_, in which case they'll be
79 # picked up automatically by the code below for testing.
80 #
81 # The env_Original subclass contains the original implementation (which
82 # actually had the is_valid_construction_var() function in SCons.Util
83 # originally).
84 #
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.
91 #
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.
97
98 class Environment:
99     _special_set = {
100         'BUILDERS' : None,
101         'SCANNERS' : None,
102         'TARGET'   : None,
103         'TARGETS'  : None,
104         'SOURCE'   : None,
105         'SOURCES'  : None,
106     }
107     _special_set_keys = _special_set.keys()
108     _valid_var = re.compile(r'[_a-zA-Z]\w*$')
109     def __init__(self, **kw):
110         self._dict = kw
111
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
122
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
133
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.
139         """
140         return self._valid_var.match(varstr)
141
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
150
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
161
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
172
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
182
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
192
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
202
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
212
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
226
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
238
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
249
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
260
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
271
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
286
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.
290
291 class_names = []
292 for n in locals().keys():
293     #if n.startswith('env_'):
294     if n[:4] == 'env_':
295         class_names.append(n)
296
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).
300
301 iterations = 10000
302
303 def do_it(names, env_class):
304     e = env_class()
305     for key in names:
306         e[key] = 1
307
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.
311
312 statements = {}
313
314 for class_name in class_names:
315     ec = eval(class_name)
316     statements[ec.__doc__] = 'do_it(names, %s)' % class_name
317
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__.
321
322 common_import_variables = ['do_it'] + class_names
323
324 common_imports = """
325 from __main__ import %s
326 """ % ', '.join(common_import_variables)
327
328 # The test data (lists of variable names) that we'll use for the runs.
329
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]
334
335 # Lastly, put it all together...
336
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)
343
344 print 'Environment __setitem__ benchmark using',
345 print 'Python', sys.version.split()[0],
346 print 'on', sys.platform, os.name
347
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 """)
352
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 """)
357
358 run_it('Results for adding 100 new, unique variable names:',
359       common_imports + """
360 import __main__ ; names = __main__.uniq_variable_names
361 """)
362
363 # Local Variables:
364 # tab-width:4
365 # indent-tabs-mode:nil
366 # End:
367 # vim: set expandtab tabstop=4 shiftwidth=4: