Application of Decorators (2): time a function in Python

Chuan Zhang
2 min readAug 17, 2020

Timing a function is one way to evaluate its performance. This post demonstrates how to use decorator to time a function.

Timing a function Directly

It is very straightforward, if we want to time a function directly. The code snippet below demonstrate how to do it.

import time
start = time.time()
result = function_to_be_timed()
end = time.time()
print(f'function_to_be_timed() takes {end-start} seconds.'

Timing a function Using Decorator

With decorator, the code for timing can be reused. Below is an example implementation of a timer decorator.

def timed(fnc):
from functools import wraps
import time
@wraps(fnc)
def wrapper(*args, **kwargs):
start = time.time()
result = fnc(*args, **kwargs)
end = time.time()
arg_str = ','.join([str(arg) for arg in args] + \
['{0}={1}'.format(k, v) for (k, v) in kwargs])
fnc_str = fnc.__name__ + '(' + arg_str + ')'
print(f'{fnc_str} takes {end - start} seconds.')
return result
return wrapper

The example below demonstrates how the above decorator can be used to time functions.

import threading, multiprocessingdef countdown(num: int) -> None:
while num > 0:
num -= 1
@timed
def func0(num: int=100000000) -> None:
countdown(num)
countdown(num)
@timed
def func1(num: int=100000000) -> None:
thread_1 = threading.Thread(target=countdown, args=(num,))
thread_2 = threading.Thread(target=countdown, args=(num,))
thread_1.start()
thread_2.start()
thread_1.join()
thread_2.join()
@timed
def func2(num: int=100000000) -> None:
process_1 = multiprocessing.Process(target=countdown, \
args=(num,))
process_2 = multiprocessing.Process(target=countdown, \
args=(num,))
process_1.start()
process_2.start()
process_1.join()
process_2.join()
>>> func0(10000000)
func0(10000000) takes 0.9984452724456787 seconds.
>>>
>>> func1(10000000)
func1(10000000) takes 1.1887896060943604 seconds.
>>>
>>> func2(10000000)
func2(10000000) takes 0.6226904392242432 seconds.
>>>

It is very interesting to see that multi-threading does not run faster. This is because I am using CPython interpreter which restricts only one thread can be executed at a time via GIL (Global Interpreter Lock). If you are using a different python interpreter, you can check the documentation to see how it handles threads. If it allows multiple threads being executed at a time, you can use the decorator demonstrated above to see that similar to multi-processing, multi-threading can speed up the function execution too.

Conclusion

In this short post, how python decorator can be used to time the execution of a function is demonstrated. In the examples, we observed that while multi-processing speeds up function execution almost in proportion to the number of the joining processes, multi-threading does not help execute function faster. This is due to GIL of the CPython interpreter which we use. Depending on the python interpreter you use, multiple threads may be allowed (e.g. Jython or IronPython), in which case you would be able to see that your function finishes faster using a decorator similar to what is demonstrated in this post.

--

--