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