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