Skip to content

Design: caching

Why cache=True is the default

Numba's own recommendation, and what the majority of users (scripts, notebooks, single-process services) want: compile once, load from disk forever after. The environments where the cache is dangerous — multi-process farms, network filesystems, ephemeral containers (see ../numba-cache.md) — are real but specific, and a library API should not be designed around its worst-case environment.

Alternative considered: cache off by default, opt-in. Rejected: punishes the common case to protect a case that has an explicit, documented escape hatch.

Why the global override beats explicit per-call arguments

configure(cache=False) / NUMBA_UTILS_CACHE=0 wins even over @cached_njit(cache=True). Deliberate, and the opposite of the usual "most specific wins" rule: the override exists as an environment policy tool — "this machine cannot safely use the cache" — and a policy that any call site can silently defeat is not a policy. The operator deploying to the broken environment outranks the author of any one decorator line.

Why some parallel functions declare cache=False themselves

parallel_histogram, parallel_prefix_sum and parallel_topk trip Numba's "dynamic globals" cache limitation after the parfor transform (via get_num_threads), and parallel_reduce drivers capture a kernel in a closure, which Numba cannot cache. Left alone, each would emit a NumbaWarning on every user's first compile. Declaring cache=False with a comment converts an unavoidable limitation into a documented, silent decision.

Why @boundscheck dev mode disables the cache

Dev builds compile with boundscheck=True and are transient by definition — caching them buys nothing and writes dev artifacts into the same __pycache__ namespace production builds read from. Keeping dev compilation fully in-memory is free and removes a class of "which binary am I actually running?" questions.

Why the content-hash locator is opt-in, not the default

Numba stamps cached binaries with (mtime, size). Deployment channels that preserve mtime (docker COPY, tar -x, rsync -a, cp -p) plus a size collision make a new release silently run the previous version's binary — reproduced, not theorized. ContentHashLocator closes this by stamping with a SHA-256 of the source bytes.

It is opt-in (NUMBA_CACHE_LOCATOR_CLASSES, before the first import) for two reasons. First, the hook is process-global: registering it REPLACES Numba's locator chain for every cached function in the process, including code that is not numba-utils' — a library must not make that decision for its host application at import time. Second, the failure it prevents lives in a deployment channel, not in code: the environments that need it (image-based deploys) are exactly the ones that can set one environment variable in the image, and everyone else would pay the per-function hash for a window they are never in. The module deliberately imports nothing from numba_utils, because Numba loads it lazily mid-decoration — which can happen mid-import of the package itself.