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.
Data scientist focusing on simulation, optimization and modeling in R, SQL, VBA and Python
Leave a Reply