Module timeit requires you to specify the number of iterations for the timing loop. This module does not.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 | """
Determine function execution time.
>>> def f():
... return sum(range(10))
...
>>> pytime(f)
(Time to execute function f, including function call overhead).
>>> 1.0/pytime(f)
(Function calls/sec, including function call overhead).
>>> 1.0/pytime_statement('sum(range(10))')
(Statements/sec, does not include any function call overhead).
"""
import sys
# Source code is public domain.
if sys.platform == "win32":
from time import clock
else:
from time import time as clock
def pytime(f, args=(), kwargs={}, Tmax=2.0):
"""
Calls f many times to determine the average time to execute f.
Tmax is the maximum time to spend in pytime(), in seconds.
"""
count = 1
while True:
start = clock()
if args == () and kwargs == {}:
for i in xrange(count):
f()
elif kwargs == {}:
for i in xrange(count):
f(*args)
else:
for i in xrange(count):
f(*args, **kwargs)
T = clock() - start
if T >= Tmax/4.0: break
count *= 2
return T / count
def pytime_statement(stmt, global_dict=None, Tmax=2.0,
repeat_count=128):
"""
Determine time to execute statement (or block) of Python code.
Here global_dict is the globals dict used for exec, Tmax is the max
time to spend in pytime_statement(), in sec, and repeat_count is the
number of times to paste stmt into the inner timing loop (this is
automatically set to 1 if stmt takes too long).
"""
if global_dict is None:
global_dict = globals()
ns = {}
code = 'def timed_func():' + ('\n' +
'\n'.join([' '+x for x in stmt.split('\n')]))
exec code in global_dict, ns
start = clock()
ns['timed_func']()
T = clock() - start
if T >= Tmax/4.0:
return T
elif T >= Tmax/4.0/repeat_count:
return pytime(ns['timed_func'], (), {}, Tmax-T)
else:
code = 'def timed_func():' + ('\n' +
'\n'.join([' '+x for x in stmt.split('\n')]))*repeat_count
exec code in global_dict, ns
return pytime(ns['timed_func'], (), {}, Tmax-T) / repeat_count
|
Python's timing facilities are...unpythonic. Module timeit requires you to specify the number of iterations for the timing loop. But that number varies according to the speed of the machine you're running on, level of optimization of your code, etc. It's more Pythonic to calculate the number of iterations in the timing procedure. This recipe does just that!
this function will report incorrect results. The code...
...is calculating the duration of 1) a for loop, 2) a list creation call (range), and 3) a function call. It is not calculating the time a function takes to run its code.
Addressing concerns. Thanks for criticising the pytime() function. As part of the recipe's design, I intended pytime() to include the function call overhead. However, as you said, using range() in the timing loop is a bad idea (I modified the recipe to use xrange). Also, I reduced some more overhead by calling f() directly if no arguments are given, f(args) if only positional arguments are given, and f(args, **kwargs) at last resort.
Now back to your concerns. Often one may want to time Python statements directly, without the function call overhead. I added pytime_statement(), which does this. It works by pasting the statement multiple times into the inner timing loop, thus it has slightly less timing overhead than module timeit.