Update with info on 1.2.0d20100117 checkpoint release
[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 string
10 import sys
11 import timeit
12
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
16 #
17 # These wrap the basic timeit function to make it a little more
18 # convenient to do side-by-side tests of code.
19
20 class Timing:
21     def __init__(self, name, num, init, statement):
22         self.__timer = timeit.Timer(statement, init)
23         self.__num   = num
24         self.name    = name
25         self.statement = statement
26         self.__result  = None
27         
28     def timeit(self):
29         self.__result = self.__timer.timeit(self.__num)
30         
31     def getResult(self):
32         return self.__result
33
34 def times(num=1000000, init='', title='Results:', **statements):
35     # time each statement
36     timings = []
37     for n, s in statements.items():
38         t = Timing(n, num, init, s)
39         t.timeit()
40         timings.append(t)
41     
42     print
43     print title
44     l = []
45     for i in timings: l.append((i.getResult(),i.name))
46     l.sort()
47     for i in l: print "  %9.3f s   %s" % i
48
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
52 # PYTHONPATH.
53
54 import __main__
55 try:
56     filename = __main__.__file__
57 except AttributeError:
58     filename = sys.argv[0]
59 script_dir = os.path.split(filename)[0]
60 if script_dir:
61     script_dir = script_dir + '/'
62 sys.path = [os.path.abspath(script_dir + '../src/engine')] + sys.path
63
64 import SCons.Errors
65 import SCons.Environment
66
67 is_valid_construction_var = SCons.Environment.is_valid_construction_var
68 global_valid_var = re.compile(r'[_a-zA-Z]\w*$')
69
70 # The classes with different __setitem__() implementations that we're
71 # going to horse-race.
72 #
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.
78 #
79 # All subclasses should be prefixed with env_, in which case they'll be
80 # picked up automatically by the code below for testing.
81 #
82 # The env_Original subclass contains the original implementation (which
83 # actually had the is_valid_construction_var() function in SCons.Util
84 # originally).
85 #
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.
92 #
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.
98
99 class Environment:
100     _special_set = {
101         'BUILDERS' : None,
102         'SCANNERS' : None,
103         'TARGET'   : None,
104         'TARGETS'  : None,
105         'SOURCE'   : None,
106         'SOURCES'  : None,
107     }
108     _special_set_keys = _special_set.keys()
109     _valid_var = re.compile(r'[_a-zA-Z]\w*$')
110     def __init__(self, **kw):
111         self._dict = kw
112
113 class env_Original(Environment):
114     """Original __setitem__()"""
115     def __setitem__(self, key, value):
116         special = self._special_set.get(key)
117         if special:
118             special(self, key, value)
119         else:
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
123
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)
128         if special:
129             special(self, key, value)
130         else:
131             if not is_valid_construction_var(key):
132                 raise SCons.Errors.UserError, "Illegal construction variable `%s'" % key
133             self._dict[key] = value
134
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
139         variable.
140         """
141         return self._valid_var.match(varstr)
142
143     def __setitem__(self, key, value):
144         special = self._special_set.get(key)
145         if special:
146             special(self, key, value)
147         else:
148             if not self.is_valid_construction_var(key):
149                 raise SCons.Errors.UserError, "Illegal construction variable `%s'" % key
150             self._dict[key] = value
151
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)
156         if special:
157             special(self, key, value)
158         else:
159             if not self._valid_var.match(key):
160                 raise SCons.Errors.UserError, "Illegal construction variable `%s'" % key
161             self._dict[key] = value
162
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)
167         if special:
168             special(self, key, value)
169         else:
170             if not global_valid_var.match(key):
171                 raise SCons.Errors.UserError, "Illegal construction variable `%s'" % key
172             self._dict[key] = value
173
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)
179         else:
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
183
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)
189         else:
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
193
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)
199         else:
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
203
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)
209         else:
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
213
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)
218         if special:
219             special(self, key, value)
220         else:
221             try:
222                 self._dict[key]
223             except KeyError:
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
227
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)
232         if special:
233             special(self, key, value)
234         else:
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
239
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)
245         else:
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
250
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)
256         else:
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
261
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)
267         else:
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
272
273 try:
274     ''.isalnum
275 except AttributeError:
276     pass
277 else:
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)
283             else:
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
287
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.
291
292 class_names = []
293 for n in locals().keys():
294     #if n.startswith('env_'):
295     if n[:4] == 'env_':
296         class_names.append(n)
297
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).
301
302 iterations = 10000
303
304 def do_it(names, env_class):
305     e = env_class()
306     for key in names:
307         e[key] = 1
308
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.
312
313 statements = {}
314
315 for class_name in class_names:
316     ec = eval(class_name)
317     statements[ec.__doc__] = 'do_it(names, %s)' % class_name
318
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__.
322
323 common_import_variables = ['do_it'] + class_names
324
325 common_imports = """
326 from __main__ import %s
327 """ % string.join(common_import_variables, ', ')
328
329 # The test data (lists of variable names) that we'll use for the runs.
330
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]
335
336 # Lastly, put it all together...
337
338 def run_it(title, init):
339       s = statements.copy()
340       s['num'] = iterations
341       s['title'] = title
342       s['init'] = init
343       apply(times,(),s)
344
345 print 'Environment __setitem__ benchmark using',
346 print 'Python', string.split(sys.version)[0],
347 print 'on', sys.platform, os.name
348
349 run_it('Results for re-adding an existing variable name 100 times:',
350       common_imports + """
351 import __main__ ; names = __main__.same_variable_names
352 """)
353
354 run_it('Results for adding 100 variable names, 50 existing and 50 new:',
355       common_imports + """
356 import __main__ ; names = __main__.mixed_variable_names
357 """)
358
359 run_it('Results for adding 100 new, unique variable names:',
360       common_imports + """
361 import __main__ ; names = __main__.uniq_variable_names
362 """)
363
364 # Local Variables:
365 # tab-width:4
366 # indent-tabs-mode:nil
367 # End:
368 # vim: set expandtab tabstop=4 shiftwidth=4: