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