3 # Benchmarks for testing various possible implementations of the
4 # env.__setitem__() method(s) in the SCons/Environment.py module.
6 from __future__
import print_function
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
)
44 for i
in sorted([(i
.getResult(),i
.name
) for i
in timings
]):
45 print(" %9.3f s %s" % i
)
47 # Import the necessary local SCons.* modules used by some of our
48 # alternative implementations below, first manipulating sys.path so
49 # we pull in the right local modules without forcing the user to set
54 filename
= __main__
.__file
__
55 except AttributeError:
56 filename
= sys
.argv
[0]
57 script_dir
= os
.path
.split(filename
)[0]
59 script_dir
= script_dir
+ '/'
60 sys
.path
= [os
.path
.abspath(script_dir
+ '..')] + sys
.path
63 import SCons
.Environment
66 is_valid_construction_var
= SCons
.Util
.is_valid_construction_var
67 global_valid_var
= SCons
.Util
.envs
._is
_valid
_var
_re
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). Update: it has moved back, to avoid import loop with Variables.
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.
107 _special_set_keys
= list(_special_set
.keys())
108 _valid_var
= global_valid_var
109 def __init__(self
, **kw
):
112 class env_Original(Environment
):
113 """Original __setitem__()"""
114 def __setitem__(self
, key
, value
):
115 special
= self
._special
_set
.get(key
)
117 special(self
, key
, value
)
119 if not SCons
.Util
.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
)
128 special(self
, key
, value
)
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
140 return self
._valid
_var
.match(varstr
)
142 def __setitem__(self
, key
, value
):
143 special
= self
._special
_set
.get(key
)
145 special(self
, key
, value
)
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
)
156 special(self
, key
, value
)
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
)
167 special(self
, key
, value
)
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
)
179 if not SCons
.Util
.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
)
189 if not SCons
.Util
.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
)
199 if not SCons
.Util
.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
)
209 if not SCons
.Util
.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
)
218 special(self
, key
, value
)
223 if not SCons
.Util
.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
)
232 special(self
, key
, value
)
234 if key
not in self
._dict \
235 and not SCons
.Util
.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
)
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_global_regex(Environment
):
251 """Best __setitem__(), with membership test and global regex"""
252 def __setitem__(self
, key
, value
):
253 if key
in self
._special
_set
:
254 self
._special
_set
[key
](self
, key
, value
)
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_has_key_function(Environment
):
262 """Best __setitem__(), with membership_test and validator function"""
263 def __setitem__(self
, key
, value
):
264 if key
in self
._special
_set
:
265 self
._special
_set
[key
](self
, key
, value
)
267 if key
not in self
._dict \
268 and not is_valid_construction_var(key
):
269 raise SCons
.Errors
.UserError("Illegal construction variable `%s'" % key
)
270 self
._dict
[key
] = value
273 class env_Best_list(Environment
):
274 """Best __setitem__(), with a list"""
275 def __setitem__(self
, key
, value
):
276 if key
in ['BUILDERS', 'SCANNERS', 'TARGET', 'TARGETS', 'SOURCE', 'SOURCES']:
277 self
._special
_set
[key
](self
, key
, value
)
279 if key
not in self
._dict \
280 and not global_valid_var
.match(key
):
281 raise SCons
.Errors
.UserError("Illegal construction variable `%s'" % key
)
282 self
._dict
[key
] = value
286 except AttributeError:
289 class env_isalnum(Environment
):
290 """Greg's Folly: isalnum instead of probe"""
291 def __setitem__(self
, key
, value
):
292 if key
in self
._special
_set
:
293 self
._special
_set
[key
](self
, key
, value
)
295 if not key
.isalnum() and not global_valid_var
.match(key
):
296 raise SCons
.Errors
.UserError("Illegal construction variable `%s'" % key
)
297 self
._dict
[key
] = value
299 class env_Best_isidentifier(Environment
):
300 """Best __setitem__(), with membership test and isidentifier"""
301 def __setitem__(self
, key
, value
):
302 if key
in self
._special
_set
:
303 self
._special
_set
[key
](self
, key
, value
)
305 if key
not in self
._dict
and not key
.isidentifier():
306 raise SCons
.Errors
.UserError("Illegal construction variable `%s'" % key
)
307 self
._dict
[key
] = value
309 # We'll use the names of all the env_* classes we find later to build
310 # the dictionary of statements to be timed, and the import statement
311 # that the timer will use to get at these classes.
314 for n
in list(locals().keys()):
315 #if n.startswith('env_'):
317 class_names
.append(n
)
319 # This is *the* function that gets timed. It will get called for the
320 # specified number of iterations for the cross product of the number of
321 # classes we're testing and the number of data sets (defined below).
325 def do_it(names
, env_class
):
330 # Build the list of "statements" that will be tested. For each class
331 # we're testing, the doc string describing the class is the key, and
332 # the statement we test is a simple "doit(names, {class})" call.
336 for class_name
in class_names
:
337 ec
= eval(class_name
)
338 statements
[ec
.__doc
__] = 'do_it(names, %s)' % class_name
340 # The common_imports string is used in the initialization of each
341 # test run. The timeit module insulates the test snippets from the
342 # global namespace, so we have to import these explicitly from __main__.
344 common_import_variables
= ['do_it'] + class_names
347 from __main__ import %s
348 """ % ', '.join(common_import_variables
)
350 # The test data (lists of variable names) that we'll use for the runs.
352 same_variable_names
= ['XXX'] * 100
353 uniq_variable_names
= []
354 for i
in range(100): uniq_variable_names
.append('X%05d' % i
)
355 mixed_variable_names
= uniq_variable_names
[:50] + same_variable_names
[:50]
357 # Lastly, put it all together...
359 def run_it(title
, init
):
360 s
= statements
.copy()
361 s
['num'] = iterations
366 print('Environment __setitem__ benchmark using', end
=' ')
367 print('Python', sys
.version
.split()[0], end
=' ')
368 print('on', sys
.platform
, os
.name
)
370 run_it('Results for re-adding an existing variable name 100 times:',
372 import __main__ ; names = __main__.same_variable_names
375 run_it('Results for adding 100 variable names, 50 existing and 50 new:',
377 import __main__ ; names = __main__.mixed_variable_names
380 run_it('Results for adding 100 new, unique variable names:',
382 import __main__ ; names = __main__.uniq_variable_names
387 # indent-tabs-mode:nil
389 # vim: set expandtab tabstop=4 shiftwidth=4: