## Overview

Teaching:20 min

Exercises:0 minQuestions

How does Numba just-in-time compiling work?

Objectives

Learn how to use the

`@jit`

decoration to improve performance.

Numba’s central feature is the `numba.jit()`

decoration. Using this decorator, it is possible to mark a function for optimization by Numba’s
JIT compiler. Various invocation modes trigger differing compilation options and behaviours.

## Python Decorators

Decorators are a way to uniformly modify functions in a particular way. You can think of them as functions that take functions as input and produce a function as output. See the Python reference documentation for a detailed discussion.

A function definition may be wrapped by one or more decorator expressions. Decorator expressions are evaluated when the function is defined, in the scope that contains the function definition. The result must be a callable, which is invoked with the function object as the only argument. The returned value is bound to the function name instead of the function object. Multiple decorators are applied in nested fashion. For example, the following code:

`@f1(arg) @f2 def func(): pass`

is equivalent to:

`def func(): pass func = f1(arg)(f2(func))`

As pointed out there, they are not limited neccesarily to function definitions, and can also be used on class definitions.

Let’s see Numba in action. The following is a Python implementation of bubblesort for NumPy arrays.

```
def bubblesort(X):
N = len(X)
for end in range(N, 1, -1):
for i in range(end - 1):
cur = X[i]
if cur > X[i + 1]:
tmp = X[i]
X[i] = X[i + 1]
X[i + 1] = tmp
```

First we’ll create an array of sorted values and randomly shuffle them.

```
import numpy as np
original = np.arange(0.0, 10.0, 0.01, dtype='f4')
shuffled = original.copy()
np.random.shuffle(shuffled)
```

Next, create a copy and do a bubble sort on the copy.

```
sorted = shuffled.copy()
bubblesort(sorted)
print(np.array_equal(sorted, original))
```

When this is run, you should see the following:

```
True
```

Now let’s time the execution. Note: we need to copy the array so we sort a random array each time as sorting an already sorted array is faster and so would distort our timing.

```
%timeit sorted[:] = shuffled[:]; bubblesort(sorted)
10 loops, best of 3: 175 ms per loop
```

Ok, so we know how fast the pure Python implementation is. The recommended way to use the `@jit`

decorator is to let Numba decide when and how to
optimize, so we simply add the decorator to the function:

```
from numba import jit
@jit
def bubblesort(X):
N = len(X)
for end in range(N, 1, -1):
for i in range(end - 1):
cur = X[i]
if cur > X[i + 1]:
tmp = X[i]
X[i] = X[i + 1]
X[i + 1] = tmp
```

Now we can check the execution time of the optimized code.

```
%timeit sorted[:] = shuffled[:]; bubblesort(sorted)
The slowest run took 103.54 times longer than the fastest. This could mean that an intermediate result is being cached.
1000 loops, best of 3: 1.29 ms per loop
```

Using the decorator in this way will defer compilation until the first function execution, so the first execution will be significantly slower.

Numba will infer the argument types at call time, and generate optimized code based on this information. Numba will also be able to compile separate specializations depending on the input types.

## Key Points

The central feature of Numba is the

`@jit`

decoration.