Faster Python simulations with Numba

An essential part of simulation modeling is simulation runtime. Large discrete-event simulation models and even medium-sized agent-based simulation models consume computational ressources and can have a very long runtime. This is especially true if the source code is fully written in Python. I therefore conducted some tests with Numba in Python. I share my results here. I run a simple test on a test function in Python.

Installing Numba for faster Python programs

Numba can be installed using the pip install command. Simply type the following into your terminal:

pip install numba

For lists I recommend that you use numpy (i.e. numpy arrays), as this will allow you to avoid default Python list. If not already installed you can install numpy in the same way:

pip install numpy

You can now import numpy and numba and use numba to make your code run faster. For this you can use the @njit decorator.

Testing how numba results in faster Python programs

Time to have a look at a simple runtime improvement test in Python. Below code implements Numba to have a faster Python program.

import numpy as np

from numba import jit

import random
import time

def rand_events(n: int) -> np.ndarray:
    ls = np.zeros(n)
    for i in range(n):
        x = random.random()
        while True:
            y = random.random()
            if x < y: 
                ls[i] = y
                break
    return ls

""" RUNTIME TEST WITHOUT NUMBA  -------------------------- """

_start_time = time.time()
vals = rand_events(10000000)
_end_time = time.time()

print(str(_end_time - _start_time))

""" RUNTIME TEST WITH NUMBA ------------------------------ """

_start_time = time.time()
jit_rand_events = jit(nopython=True)(rand_events)
vals = jit_rand_events(10000000)
_end_time = time.time()

print(str(_end_time-_start_time))

_start_time = time.time()
vals = jit_rand_events(10000000)
_end_time = time.time()

print(str(_end_time-_start_time))

In above code calling jit(nopython=True) equals the use of the @njit declarator. When running above code the output in the terminal will be something similar to the following (depends on machine and random number generation outcome)

9.1829195022583
1.2913379669189453
0.8751001358032227

This means that it took 9.18 sec to call the rand_events() function with n = 10,000,000. After applying the @njit declarator and running the function again, it takes only 1.29 sec (including “jitting” the function). Without the call of the @njit declarator it would take 0.87 sec to call the “jitted” version of rand_events(). That is a significant runtime improvement.

The first time a numba-optimized function is executed it takes more time to execute it. For everytime the function is executed after that the runtime is significantly shorter. For this please also see the following example:

In above monte-carlo simulation example I improved simulation runtime by factor 25 – thanks numba.

Applying Numba for faster Python simulations

numba can make simulation frameworks faster. But only if major parts of the framework are numerically oriented. In addition, the framework should integrate numpy, as numba understands numpy very well. In a strongly object oriented framework this can be a challenge. But if you can manage to transform computationally intensive parts of the code into numerical code then numba can result in significant runtime improvements of Python code.

You May Also Like

Leave a Reply

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.