timeit versus timing decorator
Use timeit. Running the test more than once gives me much better results.
func_list=[locals()[key] for key in locals().keys()
if callable(locals()[key]) and key.startswith('time')]
alist=range(1000000)
times=[]
for f in func_list:
n = 10
times.append( min( t for t,_,_ in (f(alist,31) for i in range(n))))
for (time,func_name) in zip(times, func_list):
print '%s took %0.3fms.' % (func_name, time*1000.)
->
<function wrapper at 0x01FCB5F0> took 39.000ms.
<function wrapper at 0x01FCB670> took 41.000ms.
Decorator for timeit.timeit method?
from functools import wraps
from time import time
def measure(func):
@wraps(func)
def _time_it(*args, **kwargs):
start = int(round(time() * 1000))
try:
return func(*args, **kwargs)
finally:
end_ = int(round(time() * 1000)) - start
print(f"Total execution time: {end_ if end_ > 0 else 0} ms")
return _time_it
@measure
def hello():
print('hello world')
hello()
Python - measure function execution time with decorator
Your function is running in 0.0001 something seconds. But since the number is so large, it takes almost 3 seconds to print it. So your code is working, I/O(print) is just slow. You can see it yourself if you only print the time it took.
To further demonstrate this and also add emil's comment
def decorator_timer(some_function):
from time import time
def wrapper(*args, **kwargs):
t1 = time()
result = some_function(*args, **kwargs)
end = time()-t1
return result, end
return wrapper
@decorator_timer
def my_pow(a, b):
res = a ** b
return res
# or just return a ** b, it doesn't really matter
result, exec_time = my_pow(99999 , 99999)
print(exec_time) # prints after 0.07347989082336426 seconds
print(result) # takes ~3 seconds
Measure : ipython timeit vs timeit method
I am not aware of anything standalone, but this can be easily extracted out of IPython, most of what it does is in the TimeitResult
class which formats the output https://github.com/ipython/ipython/blob/8520f3063ca36655b5febbbd18bf55e59cb2cbb5/IPython/core/magics/execution.py#L55-L104
Then there's the nicer reporting of the compile time, worst run being much worse than the fastest, and getting the run number automatically like timeit's cli.
Reusing the code from IPython and python's timeit, and removing some checks that are probably unnecessary for the common use, we can get a simple function to do the timing in the same way IPython does:
import math
import timeit
def _format_time(timespan, precision=3):
"""Formats the timespan in a human readable form"""
units = ["s", "ms", "\xb5s", "ns"]
scaling = [1, 1e3, 1e6, 1e9]
if timespan > 0.0:
order = min(-int(math.floor(math.log10(timespan)) // 3), 3)
else:
order = 3
scaled_time = timespan * scaling[order]
unit = units[order]
return f"{scaled_time:.{precision}g} {unit}"
class TimeitResult(object):
"""
Object returned by the timeit magic with info about the run.
Contains the following attributes :
loops: (int) number of loops done per measurement
repeat: (int) number of times the measurement has been repeated
best: (float) best execution time / number
all_runs: (list of float) execution time of each run (in s)
compile_time: (float) time of statement compilation (s)
"""
def __init__(self, loops, repeat, best, worst, all_runs, compile_time, precision):
self.loops = loops
self.repeat = repeat
self.best = best
self.worst = worst
self.all_runs = all_runs
self.compile_time = compile_time
self._precision = precision
self.timings = [dt / self.loops for dt in all_runs]
@property
def average(self):
return math.fsum(self.timings) / len(self.timings)
@property
def stdev(self):
mean = self.average
return (
math.fsum([(x - mean) ** 2 for x in self.timings]) / len(self.timings)
) ** 0.5
def __str__(self):
return "{mean} {pm} {std} per loop (mean {pm} std. dev. of {runs} run{run_plural}, {loops} loop{loop_plural} each)".format(
pm="+-",
runs=self.repeat,
loops=self.loops,
loop_plural="" if self.loops == 1 else "s",
run_plural="" if self.repeat == 1 else "s",
mean=_format_time(self.average, self._precision),
std=_format_time(self.stdev, self._precision),
)
def nice_timeit(
stmt="pass",
setup="pass",
number=0,
repeat=None,
precision=3,
timer_func=timeit.default_timer,
globals=None,
):
"""Time execution of a Python statement or expression."""
if repeat is None:
repeat = 7 if timeit.default_repeat < 7 else timeit.default_repeat
timer = timeit.Timer(stmt, setup, timer=timer_func, globals=globals)
# Get compile time
compile_time_start = timer_func()
compile(timer.src, "<timeit>", "exec")
total_compile_time = timer_func() - compile_time_start
# This is used to check if there is a huge difference between the
# best and worst timings.
# Issue: https://github.com/ipython/ipython/issues/6471
if number == 0:
# determine number so that 0.2 <= total time < 2.0
for index in range(0, 10):
number = 10 ** index
time_number = timer.timeit(number)
if time_number >= 0.2:
break
all_runs = timer.repeat(repeat, number)
best = min(all_runs) / number
worst = max(all_runs) / number
timeit_result = TimeitResult(
number, repeat, best, worst, all_runs, total_compile_time, precision
)
# Check best timing is greater than zero to avoid a
# ZeroDivisionError.
# In cases where the slowest timing is lesser than a microsecond
# we assume that it does not really matter if the fastest
# timing is 4 times faster than the slowest timing or not.
if worst > 4 * best and best > 0 and worst > 1e-6:
print(
f"The slowest run took {worst / best:.2f} times longer than the "
f"fastest. This could mean that an intermediate result "
f"is being cached."
)
print(timeit_result)
if total_compile_time > 0.1:
print(f"Compiler time: {total_compile_time:.2f} s")
return timeit_result
nice_timeit("time.sleep(0.3)", "import time")
# IPython license
# BSD 3-Clause License
#
# - Copyright (c) 2008-Present, IPython Development Team
# - Copyright (c) 2001-2007, Fernando Perez <fernando.perez@colorado.edu>
# - Copyright (c) 2001, Janko Hauser <jhauser@zscout.de>
# - Copyright (c) 2001, Nathaniel Gray <n8gray@caltech.edu>
#
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice, this
# list of conditions and the following disclaimer.
#
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
#
# * Neither the name of the copyright holder nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
How do I use timeit module inside a decorator
the problem as I mention is in the setup, doing that import get you the already decorated function, that when executed once again do the import and get the decorated function and goes and execute the import and soo on until it blow up the stack... and we don't want that.
The timeit module can also take function as its stmt, so with just building a lambda function it solve the problem and we can use the undercoated function that way
The stmt and setup parameters can also take objects that are callable without arguments. This will embed calls to them in a timer function that will then be executed by timeit(). Note that the timing overhead is a little larger in this case because of the extra function calls.
import timeit
from functools import wraps
def tool(func):
"""Include debug statement and timeit setup"""
@wraps(func)
def wrapper(*args):
print(func.__doc__, args)
res = func(*args)
print(res)
times = timeit.repeat(
stmt=lambda: func(*args),
repeat=3, number=10000)
print(times)
return wrapper
and a quick test
>>> @tool
def fun():
return 42
>>> fun()
None ()
42
[0.0013318000000026586, 0.0013294000000314554, 0.0013452000000597764]
>>>
Decorator to Find the Time of Execution of a Function in Python
A decorator takes a function and returns a new function to replace the original. It is evaluated immediately.
So:
def decorate(func):
print("Decorate!")
return func
@decorate
def add(x, y):
return x + y
will print "Decorate!" even without invoking add
.
What you want to do is "wrap" the original function in a newer function which does some work and then invokes the original.
def decorate(func):
def _wrap(*args, **kwargs):
print("Start")
return func(*args, **kwargs)
return _wrap
@decorate
def add(x, y):
return x + y
Now "add" will be replaced by _wrap
which when invoked will run your print statement first and then invoke the original function with the same args and return the result. If you want to run some "post" logic as well, you can leverage a try...finally:
def decorate(func):
def _wrap(*args, **kwargs):
print("Start")
try:
return func(*args, **kwargs)
finally:
print("End")
return _wrap
@decorate
def add(x, y):
return x + y
The finally block is guaranteed to execute. If you don't care about printing "End" in error scenarios, you could also store the return result:
def decorate(func):
def _wrap(*args, **kwargs):
print("Start")
result = func(*args, **kwargs)
print("End")
return result
return _wrap
@decorate
def add(x, y):
return x + y
If you need to pass args into the decorator, you just create a decorator factory, i.e. a function which takes some args and returns a decorate function.
def makeDecorator(text):
def decorate(func):
def _wrap(*args, **kwargs):
print(f"Start: {text}")
try:
return func(*args, **kwargs)
finally:
print("End")
return _wrap
return decorate
@makeDecorator(text="Hi")
def add(x, y):
return x + y
The key is that decorators "replace" the decorated object. So if you're decorating a function, you need to return a function. The decorate function itself is not the function that gets invoked when you call add()
.
Additional Tip: When you return the _wrap function, its name will be "_wrap" rather than the name of the original function. The functools standard library provides some helpers such as the wraps decorator which you can use on _wrap to give it the same name as the original function. Useful for debugging.
Python time measure function
First and foremost, I highly suggest using a profiler or atleast use timeit.
However if you wanted to write your own timing method strictly to learn, here is somewhere to get started using a decorator.
Python 2:
def timing(f):
def wrap(*args):
time1 = time.time()
ret = f(*args)
time2 = time.time()
print '%s function took %0.3f ms' % (f.func_name, (time2-time1)*1000.0)
return ret
return wrap
And the usage is very simple, just use the @timing decorator:
@timing
def do_work():
#code
Python 3:
def timing(f):
def wrap(*args, **kwargs):
time1 = time.time()
ret = f(*args, **kwargs)
time2 = time.time()
print('{:s} function took {:.3f} ms'.format(f.__name__, (time2-time1)*1000.0))
return ret
return wrap
Note I'm calling f.func_name
to get the function name as a string(in Python 2), or f.__name__
in Python 3.
time.time vs. timeit.timeit
timeit
is more accurate, for three reasons:
- it repeats the tests many times to eliminate the influence of other tasks on your machine, such as disk flushing and OS scheduling.
- it disables the garbage collector to prevent that process from skewing the results by scheduling a collection run at an inopportune moment.
- it picks the most accurate timer for your OS,
time.time
ortime.clock
in Python 2 andtime.perf_counter()
on Python 3. Seetimeit.default_timer
.
python timeit: function with decorator
Python code is interpreted "top to bottom". Therefore, if you declare variables either side of your function that are initialised with a "timestamp" you can deduce that the difference between those values will be the time taken to parse the code between them. So, for example,
from functools import cache
import time
import re
start = time.perf_counter()
@cache
def get_color_pattern():
return re.compile(r'#(?:(?:[\da-f]{3}){1,2}|(?:[\da-f]{4}){1,2})|rgb\(\d{1,3},\d{1,3},\d{1,3}\)')
print(time.perf_counter()-start)
start = time.perf_counter()
re.compile(r'#(?:(?:[\da-f]{3}){1,2}|(?:[\da-f]{4}){1,2})|rgb\(\d{1,3},\d{1,3},\d{1,3}\)')
print(time.perf_counter()-start)
Output:
5.995000265102135e-06
0.00024428899996564724
Thus we see the difference in time taken to parse the function versus compiling the expression
Related Topics
Virtualenv --No-Site-Packages and Pip Still Finding Global Packages
How to Plot Multiple Functions on the Same Figure, in Matplotlib
Simple Argparse Example Wanted: 1 Argument, 3 Results
How to Format a String Using a Dictionary in Python-3.X
How to Find Out the Number of Cpus Using Python
What's the Fastest Way in Python to Calculate Cosine Similarity Given Sparse Matrix Data
Compute a Confidence Interval from Sample Data
Get Lat/Long Given Current Point, Distance and Bearing
How to Convert a Numpy Array to (And Display) an Image
Python Nested Functions Variable Scoping
How to Profile Python Code Line-By-Line
What Is the Point of Indexing in Pandas
Timeout for Python Requests.Get Entire Response
Binary Representation of Float in Python (Bits Not Hex)
Accessing Mp3 Metadata with Python
Play Animations in Gif with Tkinter