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