~ubuntu-branches/ubuntu/maverick/python3.1/maverick

« back to all changes in this revision

Viewing changes to Lib/timeit.py

  • Committer: Bazaar Package Importer
  • Author(s): Matthias Klose
  • Date: 2009-03-23 00:01:27 UTC
  • Revision ID: james.westby@ubuntu.com-20090323000127-5fstfxju4ufrhthq
Tags: upstream-3.1~a1+20090322
ImportĀ upstreamĀ versionĀ 3.1~a1+20090322

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
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, str):
 
125
            stmt = reindent(stmt, 8)
 
126
            if isinstance(setup, str):
 
127
                setup = reindent(setup, 4)
 
128
                src = template % {'stmt': stmt, 'setup': setup}
 
129
            elif hasattr(setup, '__call__'):
 
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, globals(), ns)
 
137
            self.inner = ns["inner"]
 
138
        elif hasattr(stmt, '__call__'):
 
139
            self.src = None
 
140
            if isinstance(setup, str):
 
141
                _setup = setup
 
142
                def setup():
 
143
                    exec(_setup, globals(), ns)
 
144
            elif not hasattr(setup, '__call__'):
 
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 as 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__, end=' ')
 
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, end=' ')
 
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())