|
1 #! /usr/bin/env python |
|
2 |
|
3 """Tool for measuring execution time of small code snippets. |
|
4 |
|
5 This module avoids a number of common traps for measuring execution |
|
6 times. See also Tim Peters' introduction to the Algorithms chapter in |
|
7 the Python Cookbook, published by O'Reilly. |
|
8 |
|
9 Library usage: see the Timer class. |
|
10 |
|
11 Command line usage: |
|
12 python timeit.py [-n N] [-r N] [-s S] [-t] [-c] [-h] [statement] |
|
13 |
|
14 Options: |
|
15 -n/--number N: how many times to execute 'statement' (default: see below) |
|
16 -r/--repeat N: how many times to repeat the timer (default 3) |
|
17 -s/--setup S: statement to be executed once initially (default 'pass') |
|
18 -t/--time: use time.time() (default on Unix) |
|
19 -c/--clock: use time.clock() (default on Windows) |
|
20 -v/--verbose: print raw timing results; repeat for more digits precision |
|
21 -h/--help: print this usage message and exit |
|
22 statement: statement to be timed (default 'pass') |
|
23 |
|
24 A multi-line statement may be given by specifying each line as a |
|
25 separate argument; indented lines are possible by enclosing an |
|
26 argument in quotes and using leading spaces. Multiple -s options are |
|
27 treated similarly. |
|
28 |
|
29 If -n is not given, a suitable number of loops is calculated by trying |
|
30 successive powers of 10 until the total time is at least 0.2 seconds. |
|
31 |
|
32 The difference in default timer function is because on Windows, |
|
33 clock() has microsecond granularity but time()'s granularity is 1/60th |
|
34 of a second; on Unix, clock() has 1/100th of a second granularity and |
|
35 time() is much more precise. On either platform, the default timer |
|
36 functions measure wall clock time, not the CPU time. This means that |
|
37 other processes running on the same computer may interfere with the |
|
38 timing. The best thing to do when accurate timing is necessary is to |
|
39 repeat the timing a few times and use the best time. The -r option is |
|
40 good for this; the default of 3 repetitions is probably enough in most |
|
41 cases. On Unix, you can use clock() to measure CPU time. |
|
42 |
|
43 Note: there is a certain baseline overhead associated with executing a |
|
44 pass statement. The code here doesn't try to hide it, but you should |
|
45 be aware of it. The baseline overhead can be measured by invoking the |
|
46 program without arguments. |
|
47 |
|
48 The baseline overhead differs between Python versions! Also, to |
|
49 fairly compare older Python versions to Python 2.3, you may want to |
|
50 use python -O for the older versions to avoid timing SET_LINENO |
|
51 instructions. |
|
52 """ |
|
53 |
|
54 import gc |
|
55 import sys |
|
56 import time |
|
57 try: |
|
58 import itertools |
|
59 except ImportError: |
|
60 # Must be an older Python version (see timeit() below) |
|
61 itertools = None |
|
62 |
|
63 __all__ = ["Timer"] |
|
64 |
|
65 dummy_src_name = "<timeit-src>" |
|
66 default_number = 1000000 |
|
67 default_repeat = 3 |
|
68 |
|
69 if sys.platform == "win32": |
|
70 # On Windows, the best timer is time.clock() |
|
71 default_timer = time.clock |
|
72 else: |
|
73 # On most other platforms the best timer is time.time() |
|
74 default_timer = time.time |
|
75 |
|
76 # Don't change the indentation of the template; the reindent() calls |
|
77 # in Timer.__init__() depend on setup being indented 4 spaces and stmt |
|
78 # being indented 8 spaces. |
|
79 template = """ |
|
80 def inner(_it, _timer): |
|
81 %(setup)s |
|
82 _t0 = _timer() |
|
83 for _i in _it: |
|
84 %(stmt)s |
|
85 _t1 = _timer() |
|
86 return _t1 - _t0 |
|
87 """ |
|
88 |
|
89 def reindent(src, indent): |
|
90 """Helper to reindent a multi-line statement.""" |
|
91 return src.replace("\n", "\n" + " "*indent) |
|
92 |
|
93 def _template_func(setup, func): |
|
94 """Create a timer function. Used if the "statement" is a callable.""" |
|
95 def inner(_it, _timer): |
|
96 setup() |
|
97 _t0 = _timer() |
|
98 for _i in _it: |
|
99 func() |
|
100 _t1 = _timer() |
|
101 return _t1 - _t0 |
|
102 return inner |
|
103 |
|
104 class Timer: |
|
105 """Class for timing execution speed of small code snippets. |
|
106 |
|
107 The constructor takes a statement to be timed, an additional |
|
108 statement used for setup, and a timer function. Both statements |
|
109 default to 'pass'; the timer function is platform-dependent (see |
|
110 module doc string). |
|
111 |
|
112 To measure the execution time of the first statement, use the |
|
113 timeit() method. The repeat() method is a convenience to call |
|
114 timeit() multiple times and return a list of results. |
|
115 |
|
116 The statements may contain newlines, as long as they don't contain |
|
117 multi-line string literals. |
|
118 """ |
|
119 |
|
120 def __init__(self, stmt="pass", setup="pass", timer=default_timer): |
|
121 """Constructor. See class doc string.""" |
|
122 self.timer = timer |
|
123 ns = {} |
|
124 if isinstance(stmt, basestring): |
|
125 stmt = reindent(stmt, 8) |
|
126 if isinstance(setup, basestring): |
|
127 setup = reindent(setup, 4) |
|
128 src = template % {'stmt': stmt, 'setup': setup} |
|
129 elif callable(setup): |
|
130 src = template % {'stmt': stmt, 'setup': '_setup()'} |
|
131 ns['_setup'] = setup |
|
132 else: |
|
133 raise ValueError("setup is neither a string nor callable") |
|
134 self.src = src # Save for traceback display |
|
135 code = compile(src, dummy_src_name, "exec") |
|
136 exec code in globals(), ns |
|
137 self.inner = ns["inner"] |
|
138 elif callable(stmt): |
|
139 self.src = None |
|
140 if isinstance(setup, basestring): |
|
141 _setup = setup |
|
142 def setup(): |
|
143 exec _setup in globals(), ns |
|
144 elif not callable(setup): |
|
145 raise ValueError("setup is neither a string nor callable") |
|
146 self.inner = _template_func(setup, stmt) |
|
147 else: |
|
148 raise ValueError("stmt is neither a string nor callable") |
|
149 |
|
150 def print_exc(self, file=None): |
|
151 """Helper to print a traceback from the timed code. |
|
152 |
|
153 Typical use: |
|
154 |
|
155 t = Timer(...) # outside the try/except |
|
156 try: |
|
157 t.timeit(...) # or t.repeat(...) |
|
158 except: |
|
159 t.print_exc() |
|
160 |
|
161 The advantage over the standard traceback is that source lines |
|
162 in the compiled template will be displayed. |
|
163 |
|
164 The optional file argument directs where the traceback is |
|
165 sent; it defaults to sys.stderr. |
|
166 """ |
|
167 import linecache, traceback |
|
168 if self.src is not None: |
|
169 linecache.cache[dummy_src_name] = (len(self.src), |
|
170 None, |
|
171 self.src.split("\n"), |
|
172 dummy_src_name) |
|
173 # else the source is already stored somewhere else |
|
174 |
|
175 traceback.print_exc(file=file) |
|
176 |
|
177 def timeit(self, number=default_number): |
|
178 """Time 'number' executions of the main statement. |
|
179 |
|
180 To be precise, this executes the setup statement once, and |
|
181 then returns the time it takes to execute the main statement |
|
182 a number of times, as a float measured in seconds. The |
|
183 argument is the number of times through the loop, defaulting |
|
184 to one million. The main statement, the setup statement and |
|
185 the timer function to be used are passed to the constructor. |
|
186 """ |
|
187 if itertools: |
|
188 it = itertools.repeat(None, number) |
|
189 else: |
|
190 it = [None] * number |
|
191 gcold = gc.isenabled() |
|
192 gc.disable() |
|
193 timing = self.inner(it, self.timer) |
|
194 if gcold: |
|
195 gc.enable() |
|
196 return timing |
|
197 |
|
198 def repeat(self, repeat=default_repeat, number=default_number): |
|
199 """Call timeit() a few times. |
|
200 |
|
201 This is a convenience function that calls the timeit() |
|
202 repeatedly, returning a list of results. The first argument |
|
203 specifies how many times to call timeit(), defaulting to 3; |
|
204 the second argument specifies the timer argument, defaulting |
|
205 to one million. |
|
206 |
|
207 Note: it's tempting to calculate mean and standard deviation |
|
208 from the result vector and report these. However, this is not |
|
209 very useful. In a typical case, the lowest value gives a |
|
210 lower bound for how fast your machine can run the given code |
|
211 snippet; higher values in the result vector are typically not |
|
212 caused by variability in Python's speed, but by other |
|
213 processes interfering with your timing accuracy. So the min() |
|
214 of the result is probably the only number you should be |
|
215 interested in. After that, you should look at the entire |
|
216 vector and apply common sense rather than statistics. |
|
217 """ |
|
218 r = [] |
|
219 for i in range(repeat): |
|
220 t = self.timeit(number) |
|
221 r.append(t) |
|
222 return r |
|
223 |
|
224 def timeit(stmt="pass", setup="pass", timer=default_timer, |
|
225 number=default_number): |
|
226 """Convenience function to create Timer object and call timeit method.""" |
|
227 return Timer(stmt, setup, timer).timeit(number) |
|
228 |
|
229 def repeat(stmt="pass", setup="pass", timer=default_timer, |
|
230 repeat=default_repeat, number=default_number): |
|
231 """Convenience function to create Timer object and call repeat method.""" |
|
232 return Timer(stmt, setup, timer).repeat(repeat, number) |
|
233 |
|
234 def main(args=None): |
|
235 """Main program, used when run as a script. |
|
236 |
|
237 The optional argument specifies the command line to be parsed, |
|
238 defaulting to sys.argv[1:]. |
|
239 |
|
240 The return value is an exit code to be passed to sys.exit(); it |
|
241 may be None to indicate success. |
|
242 |
|
243 When an exception happens during timing, a traceback is printed to |
|
244 stderr and the return value is 1. Exceptions at other times |
|
245 (including the template compilation) are not caught. |
|
246 """ |
|
247 if args is None: |
|
248 args = sys.argv[1:] |
|
249 import getopt |
|
250 try: |
|
251 opts, args = getopt.getopt(args, "n:s:r:tcvh", |
|
252 ["number=", "setup=", "repeat=", |
|
253 "time", "clock", "verbose", "help"]) |
|
254 except getopt.error, err: |
|
255 print err |
|
256 print "use -h/--help for command line help" |
|
257 return 2 |
|
258 timer = default_timer |
|
259 stmt = "\n".join(args) or "pass" |
|
260 number = 0 # auto-determine |
|
261 setup = [] |
|
262 repeat = default_repeat |
|
263 verbose = 0 |
|
264 precision = 3 |
|
265 for o, a in opts: |
|
266 if o in ("-n", "--number"): |
|
267 number = int(a) |
|
268 if o in ("-s", "--setup"): |
|
269 setup.append(a) |
|
270 if o in ("-r", "--repeat"): |
|
271 repeat = int(a) |
|
272 if repeat <= 0: |
|
273 repeat = 1 |
|
274 if o in ("-t", "--time"): |
|
275 timer = time.time |
|
276 if o in ("-c", "--clock"): |
|
277 timer = time.clock |
|
278 if o in ("-v", "--verbose"): |
|
279 if verbose: |
|
280 precision += 1 |
|
281 verbose += 1 |
|
282 if o in ("-h", "--help"): |
|
283 print __doc__, |
|
284 return 0 |
|
285 setup = "\n".join(setup) or "pass" |
|
286 # Include the current directory, so that local imports work (sys.path |
|
287 # contains the directory of this script, rather than the current |
|
288 # directory) |
|
289 import os |
|
290 sys.path.insert(0, os.curdir) |
|
291 t = Timer(stmt, setup, timer) |
|
292 if number == 0: |
|
293 # determine number so that 0.2 <= total time < 2.0 |
|
294 for i in range(1, 10): |
|
295 number = 10**i |
|
296 try: |
|
297 x = t.timeit(number) |
|
298 except: |
|
299 t.print_exc() |
|
300 return 1 |
|
301 if verbose: |
|
302 print "%d loops -> %.*g secs" % (number, precision, x) |
|
303 if x >= 0.2: |
|
304 break |
|
305 try: |
|
306 r = t.repeat(repeat, number) |
|
307 except: |
|
308 t.print_exc() |
|
309 return 1 |
|
310 best = min(r) |
|
311 if verbose: |
|
312 print "raw times:", " ".join(["%.*g" % (precision, x) for x in r]) |
|
313 print "%d loops," % number, |
|
314 usec = best * 1e6 / number |
|
315 if usec < 1000: |
|
316 print "best of %d: %.*g usec per loop" % (repeat, precision, usec) |
|
317 else: |
|
318 msec = usec / 1000 |
|
319 if msec < 1000: |
|
320 print "best of %d: %.*g msec per loop" % (repeat, precision, msec) |
|
321 else: |
|
322 sec = msec / 1000 |
|
323 print "best of %d: %.*g sec per loop" % (repeat, precision, sec) |
|
324 return None |
|
325 |
|
326 if __name__ == "__main__": |
|
327 sys.exit(main()) |