Python Check performance by using timeit

Python

We should somehow check the performance if we want to compare some implementations to know a faster one. It can be done to use time.time() and get the elapsed time between end time and start time. However, Python offers a better way. It’s timeit. This post shows you how to use timeit.

Sponsored links

Check the elapsed time when the specified function is called x times

Let’s see the simplest example first. We need a function to be executed. Then, pass it to timeit.

import timeit

def func1():
    return 1

print(timeit.timeit(func1))
# 0.06424164399959409
print(timeit.timeit(func1, number=100))
# 5.899999450775795e-06

The output result is in second. It took 64ms for the first call but the second call took 5 microseconds. This is because the default value of number is 1000000. It means that the function is called the specified times.

Sponsored links

Repeat the test specified times

We might sometimes want to repeat the test. Use timeit.repeat() in this case.

print(timeit.repeat(func1, repeat=5, number=100))
# [5.9000003602704965e-06, 5.4989995987853035e-06, 5.500000042957254e-06, 5.499999133462552e-06, 5.500000042957254e-06]

It calls the function 100 times, saves the elapsed time, and repeats it 5 times. So it shows the 5 results.

How to pass arguments to the target function

It’s impossible to pass arguments in the way above. If the target function requires arguments, we write it differently. The function specified in timeit() must not have any arguments. Therefore, the target function needs to be wrapped by for example lambda.

import timeit
from random import randrange

def func2(a: int, b: int):
    return a + b

print(timeit.timeit(lambda: func2(randrange(100), randrange(100)), number=100))
# 0.00011679899944283534

If it’s not so simple as this example, define a function to call the target function.

def call_func2():
    a = randrange(100)
    b = randrange(100)
    func2(a + a, a - b)


print(timeit.timeit(call_func2, number=100))

In this way, we can add as many lines as we want.

Execute a function defined in another module

We might want to test functions written in another module so that we can separate the production code and the performance check code. However, I could not find a way to import a module from the parent directory.

The directory structure is as follows.

src/timeits
├── __init__.py
├── hello_world.py
├── sub
│   └── hey.py
└── use_timeit.py

Our production code is here.

# hello_world.py
print("Hello")

def hello():
    print("hello world")

if __name__ == '__main__':
    print("World!")

Let’s import it by specifying setup argument. The second argument is setup which is executed before executing the target function. It’s initialization code.

# use_timeit.py
import timeit

timeit.timeit("", "import hello_world", number=100)
# Hello
# 1.1000010999850929e-06

This means that it import hello_world module once and executes nothing 100 times. The following code is equivalent to the example above.

import timeit

timeit.timeit("import hello_world", number=1)

We can call hello() function with the import.

# use_timeit.py
import timeit

timeit.timeit(f"hello_world.hello()", "import hello_world", number=1)
# Hello
# hello world
# 1.9500002963468432e-05

imeit.timeit("hello()", "from hello_world import hello", number=1)
# hello world
# 1.8299997464055195e-05

Use module in sub directory

Function defined in subdirectory can be used in this way.

import timeit

timeit.timeit("hey()", "from sub.hey import hey", number=1)
# HEY
# 1.9099999917671084e-05

But I could not find a way to import a module defined in a parent directory. Please leave a comment if you know how to do it.

Show the result in another unit

The unit is second but it might be too big for some cases. If we want another unit, we should simply convert it.

import timeit

elapsed_time_second = timeit.timeit(
    f"hello_world.hello()", "import hello_world", number=1
)
elapsed_time_ms = elapsed_time_second * 1000
elapsed_time_micros = elapsed_time_ms * 1000
elapsed_time_ns = elapsed_time_micros * 1000
print(
    f"{format(elapsed_time_second,'.3f')} sec, "
    f"{format(elapsed_time_ms, '.3f')} ms, "
    f"{format(elapsed_time_micros,'.3f')} micro sec, "
    f"{format(elapsed_time_ns,'.3f')} ns"
)
# hello world
# 0.000 sec, 0.019 ms, 19.100 micro sec, 19100.000 ns

Check performance

Check the following post too. This post uses timetit to check the fastest way to access data.

Comments

Copied title and URL