A one-time notice for models whose search is compiled with numba.
The first fit of such a model on a new machine compiles its search, which takes from seconds to
minutes, and the result is cached on disk, so later processes start in about a second. Without a
word that first fit looks hung; notify_first_compile() says what is happening, once per process
and only when no compiled code is cached yet.
Expand source code
"""A one-time notice for models whose search is compiled with numba.
The first fit of such a model on a new machine compiles its search, which takes from seconds to
minutes, and the result is cached on disk, so later processes start in about a second. Without a
word that first fit looks hung; ``notify_first_compile`` says what is happening, once per process
and only when no compiled code is cached yet.
"""
import glob
import os
import pickle
import sys
__all__ = ['notify_first_compile']
_NOTIFIED = set()
def _cache_dirs(module_file):
"""Where numba keeps the disk cache of the kernels defined in ``module_file``."""
here = os.path.dirname(os.path.abspath(module_file))
dirs = [os.path.join(here, '__pycache__')] # next to the module, when writable
rel = os.path.splitdrive(here)[1].lstrip(os.sep)
user = os.environ.get('NUMBA_CACHE_DIR')
if user:
dirs.append(os.path.join(user, rel))
dirs.append(os.path.join(os.path.expanduser('~'), '.cache', 'numba', rel)) # numba's user-wide fallback
return dirs
def _numba_version():
numba = sys.modules.get('numba')
if numba is not None:
return numba.__version__
try:
from importlib.metadata import version
return version('numba')
except Exception:
return None
def _index_is_fresh(path, stamp, numba_version):
"""Whether the numba index file ``path`` was written by this numba for the source file as it is now.
numba's index is a pickled numba version followed by a pickled ``((st_mtime, st_size) of the source,
overloads)``; numba ignores an index whose version or stamp differs, and so does this."""
try:
with open(path, 'rb') as f:
if pickle.load(f) != numba_version:
return False
saved_stamp = pickle.loads(f.read())[0]
return tuple(saved_stamp) == stamp
except Exception: # unreadable or in another format: say not cached, so the notice is shown
return False
def is_cached(module_file):
"""Whether numba has compiled code on disk for the kernels of ``module_file`` that this Python and numba can
load: an index for this Python version (``<module>.<kernel>-<line>.py<major><minor>.nbi``) written by the
installed numba for the current source file (same modification time and size). When unsure, False."""
stem = os.path.splitext(os.path.basename(module_file))[0]
tag = f'py{sys.version_info.major}{sys.version_info.minor}'
try:
st = os.stat(module_file)
except OSError:
return False
stamp = (st.st_mtime, st.st_size)
numba_version = _numba_version()
if numba_version is None:
return False
for d in _cache_dirs(module_file):
for path in glob.glob(os.path.join(glob.escape(d), f'{glob.escape(stem)}.*.{tag}.nbi')):
if _index_is_fresh(path, stamp, numba_version):
return True
return False
def notify_first_compile(module_file, model_name, duration, cache_enabled=True, cache_env=None):
"""Print a notice to stderr before the first fit of ``model_name`` in this process compiles its
numba search, unless the compiled code is already cached on disk.
Parameters
----------
module_file: str
``__file__`` of the module that defines the kernels
model_name: str
the estimator's name, e.g. 'FastRiskScoreClassifier'
duration: str
how long the compilation takes, e.g. 'about 2 minutes'
cache_enabled: bool
whether the kernels are cached on disk
cache_env: str
the environment variable that turns the cache off, named in the notice
"""
if model_name in _NOTIFIED:
return
_NOTIFIED.add(model_name)
if cache_enabled and is_cached(module_file):
return
if cache_enabled:
after = 'The result is cached, so later fits on this machine start in about a second.'
else:
off = f' ({cache_env}=0)' if cache_env else ''
after = f'The disk cache is off{off}, so every new process compiles it again.'
print(f'{model_name}: compiling its numba search for the first time, which takes {duration}. {after}',
file=sys.stderr, flush=True)
Functions
def notify_first_compile(module_file, model_name, duration, cache_enabled=True, cache_env=None)-
Print a notice to stderr before the first fit of
model_namein this process compiles its numba search, unless the compiled code is already cached on disk.Parameters
module_file:str__file__of the module that defines the kernelsmodel_name:str- the estimator's name, e.g. 'FastRiskScoreClassifier'
duration:str- how long the compilation takes, e.g. 'about 2 minutes'
cache_enabled:bool- whether the kernels are cached on disk
cache_env:str- the environment variable that turns the cache off, named in the notice
Expand source code
def notify_first_compile(module_file, model_name, duration, cache_enabled=True, cache_env=None): """Print a notice to stderr before the first fit of ``model_name`` in this process compiles its numba search, unless the compiled code is already cached on disk. Parameters ---------- module_file: str ``__file__`` of the module that defines the kernels model_name: str the estimator's name, e.g. 'FastRiskScoreClassifier' duration: str how long the compilation takes, e.g. 'about 2 minutes' cache_enabled: bool whether the kernels are cached on disk cache_env: str the environment variable that turns the cache off, named in the notice """ if model_name in _NOTIFIED: return _NOTIFIED.add(model_name) if cache_enabled and is_cached(module_file): return if cache_enabled: after = 'The result is cached, so later fits on this machine start in about a second.' else: off = f' ({cache_env}=0)' if cache_env else '' after = f'The disk cache is off{off}, so every new process compiles it again.' print(f'{model_name}: compiling its numba search for the first time, which takes {duration}. {after}', file=sys.stderr, flush=True)