Misreading compile time as run time is the most common reason people think Numba 'didn't help'.
Compilation happens lazily, on the first call with a new combination of argument types. That call includes type inference and LLVM optimisation, which can take from a fraction of a second to several seconds for big functions. Every later call with the same types reuses the compiled machine code.
So benchmark correctly: call the function once to warm it up, then time later calls (for example with timeit or time.perf_counter). Timing the first call measures the compiler, not your code.
Each distinct type signature gets its own compiled version. Calling with an int64 array and later a float32 array compiles twice. The dispatcher's signatures attribute lists what has been compiled so far.
Compiled code lives in memory and disappears when the process exits. For scripts and command-line tools that start often, pass cache=True: Numba writes the compiled code to a __pycache__ directory next to the source file and loads it in later processes, as long as the source and the Numba version are unchanged. Caching only works for functions defined in files (not in an interactive session or exec).
If compile time matters, also consider fewer, larger compiled functions rather than many tiny ones, and avoid recompiling by keeping input dtypes consistent.
Example
import time
import numpy as np
from numba import njit
@njit(cache=True)
def clip_sum(a, lo, hi):
s = 0.0
for x in a:
s += min(max(x, lo), hi)
return s
a = np.random.default_rng(0).normal(size=2_000_000)
t = time.perf_counter(); clip_sum(a, -1.0, 1.0)
print(f"first call: {time.perf_counter() - t:.3f}s") # includes compiling (or loading the cache)
t = time.perf_counter(); clip_sum(a, -1.0, 1.0)
print(f"second call: {time.perf_counter() - t:.3f}s") # run time only, e.g. 0.002s
clip_sum(a.astype(np.float32), -1.0, 1.0) # new dtype -> compiles again
print(len(clip_sum.signatures)) # 2Remember
- The first call per type signature compiles; later calls are fast
- Warm up before timing
- cache=True stores compiled code on disk for later processes
Watch out for
- Benchmarking only the first call and concluding Numba is slow.