36a76727f2076f24bc489ed6e87b71b3a6b8302f
[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     for i in sorted([(i.getResult(),i.name) for i in timings]):
44         print "  %9.3f s   %s" % i
45
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
49 # PYTHONPATH.
50
51 import __main__
52 try:
53     filename = __main__.__file__
54 except AttributeError:
55     filename = sys.argv[0]
56 script_dir = os.path.split(filename)[0]
57 if script_dir:
58     script_dir = script_dir + '/'
59 sys.path = [os.path.abspath(script_dir + '../src/engine')] + sys.path
60
61 import SCons.Errors
62 import SCons.Environment
63
64 is_valid_construction_var = SCons.Environment.is_valid_construction_var
65 global_valid_var = re.compile(r'[_a-zA-Z]\w*$')
66
67 # The classes with different __setitem__() implementations that we're
68 # going to horse-race.
69 #
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.
75 #
76 # All subclasses should be prefixed with env_, in which case they'll be
77 # picked up automatically by the code below for testing.
78 #
79 # The env_Original subclass contains the original implementation (which
80 # actually had the is_valid_construction_var() function in SCons.Util
81 # originally).
82 #
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.
89 #
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.
95
96 class Environment:
97     _special_set = {
98         'BUILDERS' : None,
99         'SCANNERS' : None,
100         'TARGET'   : None,
101         'TARGETS'  : None,
102         'SOURCE'   : None,
103         'SOURCES'  : None,
104     }
105     _special_set_keys = _special_set.keys()
106     _valid_var = re.compile(r'[_a-zA-Z]\w*$')
107     def __init__(self, **kw):
108         self._dict = kw
109
110 class env_Original(Environment):
111     """Original __setitem__()"""
112     def __setitem__(self, key, value):
113         special = self._special_set.get(key)
114         if special:
115             special(self, key, value)
116         else:
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
120
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)
125         if special:
126             special(self, key, value)
127         else:
128             if not is_valid_construction_var(key):
129                 raise SCons.Errors.UserError, "Illegal construction variable `%s'" % key
130             self._dict[key] = value
131
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
136         variable.
137         """
138         return self._valid_var.match(varstr)
139
140     def __setitem__(self, key, value):
141         special = self._special_set.get(key)
142         if special:
143             special(self, key, value)
144         else:
145             if not self.is_valid_construction_var(key):
146                 raise SCons.Errors.UserError, "Illegal construction variable `%s'" % key
147             self._dict[key] = value
148
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)
153         if special:
154             special(self, key, value)
155         else:
156             if not self._valid_var.match(key):
157                 raise SCons.Errors.UserError, "Illegal construction variable `%s'" % key
158             self._dict[key] = value
159
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)
164         if special:
165             special(self, key, value)
166         else:
167             if not global_valid_var.match(key):
168                 raise SCons.Errors.UserError, "Illegal construction variable `%s'" % key
169             self._dict[key] = value
170
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)
176         else:
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
180
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)
186         else:
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
190
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)
196         else:
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
200
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)
206         else:
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
210
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)
215         if special:
216             special(self, key, value)
217         else:
218             try:
219                 self._dict[key]
220             except KeyError:
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
224
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)
229         if special:
230             special(self, key, value)
231         else:
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
236
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)
242         else:
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
247
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)
253         else:
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
258
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)
264         else:
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
269
270 try:
271     ''.isalnum
272 except AttributeError:
273     pass
274 else:
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)
280             else:
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
284
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.
288
289 class_names = []
290 for n in locals().keys():
291     #if n.startswith('env_'):
292     if n[:4] == 'env_':
293         class_names.append(n)
294
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).
298
299 iterations = 10000
300
301 def do_it(names, env_class):
302     e = env_class()
303     for key in names:
304         e[key] = 1
305
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.
309
310 statements = {}
311
312 for class_name in class_names:
313     ec = eval(class_name)
314     statements[ec.__doc__] = 'do_it(names, %s)' % class_name
315
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__.
319
320 common_import_variables = ['do_it'] + class_names
321
322 common_imports = """
323 from __main__ import %s
324 """ % ', '.join(common_import_variables)
325
326 # The test data (lists of variable names) that we'll use for the runs.
327
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]
332
333 # Lastly, put it all together...
334
335 def run_it(title, init):
336       s = statements.copy()
337       s['num'] = iterations
338       s['title'] = title
339       s['init'] = init
340       times(**s)
341
342 print 'Environment __setitem__ benchmark using',
343 print 'Python', sys.version.split()[0],
344 print 'on', sys.platform, os.name
345
346 run_it('Results for re-adding an existing variable name 100 times:',
347       common_imports + """
348 import __main__ ; names = __main__.same_variable_names
349 """)
350
351 run_it('Results for adding 100 variable names, 50 existing and 50 new:',
352       common_imports + """
353 import __main__ ; names = __main__.mixed_variable_names
354 """)
355
356 run_it('Results for adding 100 new, unique variable names:',
357       common_imports + """
358 import __main__ ; names = __main__.uniq_variable_names
359 """)
360
361 # Local Variables:
362 # tab-width:4
363 # indent-tabs-mode:nil
364 # End:
365 # vim: set expandtab tabstop=4 shiftwidth=4: