Defer the eager scipy.sparse and scipy.spatial imports (~280 ms off import) - #477
Merged
Conversation
`import autoarray` drops from 464.4 ms to 183.7 ms (medians of 15 runs,
Python 3.13, dev extras) — a 281 ms saving.
The filed task named `derivative_util.py:30`'s module-scope
`from scipy.sparse import csr_matrix` as the target, on the evidence that
`scipy.sparse` costs ~0.11 s of every import. Deferring it changed nothing,
because `scipy.sparse` was never being imported from there:
autoarray/__init__.py:80
-> inversion/mesh/mesh_geometry/delaunay.py:2 import scipy.spatial
-> scipy/spatial/__init__.py:111 from ._kdtree import *
-> scipy/spatial/_kdtree.py:4 from ._ckdtree import cKDTree
`scipy.spatial` (134 ms) pulls `scipy.sparse` (154 ms) in transitively, so the
csr_matrix import was riding on a subtree that was already paid for. Deferring
`scipy.spatial` as well is what actually removes both, and is required to meet
the task's own acceptance criterion.
Changes:
- `inversion/mesh/mesh_geometry/delaunay.py` — drop the module-scope
`import scipy.spatial`. Two of its three use sites already had local
imports; only `voronoi_neighbors_from` needed one added, so this finishes a
deferral that had been started and left half-done.
- `operators/derivative_util.py`, `operators/coarse_interp_util.py` — move
`from scipy.sparse import csr_matrix` into the four functions that use it.
No longer load-bearing for the import time on its own, but it keeps
`scipy.sparse` off the import path independently of what `scipy.spatial`
happens to pull in.
Every use site is inside a function, so a plain local import suffices — no
module-level cache as in `transformer.py:_load_nufftax()`, which needed one
only because unpickled instances in multiprocessing workers never re-run
`__init__`. Function-local imports run on every call and hit `sys.modules`
after the first.
Verified: `python -X importtime -c "import autoarray"` shows neither
`scipy.sparse` nor `scipy.spatial`. Suites green — autoarray 1179 passed,
autogalaxy 1103 passed / 1 skipped, autolens 532 passed / 1 skipped.
`test_autoarray/.../output_test/array.fits` is a tracked file that the test suite rewrites when it runs. It is unrelated to the import deferral and was picked up by `git add -A`; this restores it to its state on main.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
import autoarraydrops from 464.4 ms to 183.7 ms — a 281 ms saving, ~60% of the import cost. Medians of 15 runs each, Python 3.13, dev extras, same interpreter and machine.This is the follow-up filed off the pynufft removal (#475), whose measurement turned up the real import-time cost.
The filed premise was wrong, in the same way the pynufft one was
The task named
derivative_util.py:30's module-scopefrom scipy.sparse import csr_matrixas the target, on the evidence thatscipy.sparsecosts ~0.11 s of every import. Deferring it changed nothing —scipy.sparsewas never being imported from there. Traced with asys.meta_pathhook:scipy.spatial(134 ms) pullsscipy.sparse(154 ms) in transitively. Thecsr_matriximport was riding on a subtree already paid for by something else — exactly the shape of the pynufft error, where a 0.19 s "cost" turned out to be 95% sharedscipy.sparse. Deferringscipy.spatialtoo is what actually removes both, and is required to meet the task's own acceptance criterion.That also means the win is ~280 ms rather than the ~100 ms the task predicted, because
scipy.spatial's own cost comes off as well.API Changes
None — internal changes only. No public symbol, signature, or behaviour changes; only the point at which two third-party modules are imported.
Test Plan
python -X importtime -c "import autoarray" | grep scipy.sparse— emptypython -X importtime -c "import autoarray" | grep scipy.spatial— emptyimport autoarray: 464.4 ms → 183.7 ms (medians of 15; main min/max 445.1/511.9, branch 172.8/204.9)pytest test_autoarray— 1179 passedChanges by file
inversion/mesh/mesh_geometry/delaunay.pyDrops the module-scope
import scipy.spatial. Two of its three use sites (lines 19 and 179 on main) already had local imports — this deferral had been started and left half-done; onlyvoronoi_neighbors_fromneeded one added. The"scipy.spatial.Voronoi"return annotation is already a string literal, so it needs no import at definition time.operators/derivative_util.py,operators/coarse_interp_util.pyMove
from scipy.sparse import csr_matrixinto the four functions that use it —derivative_1st_operators_from,derivative_2nd_operators_from,forward_difference_operators_from,coarse_interp_matrix_from. Not load-bearing for import time on its own any more, but it keepsscipy.sparseoff the import path independently of whatscipy.spatialhappens to pull in, so this does not silently regress if scipy reshuffles its internals.On the deferral pattern
Every use site is inside a function, so a plain local import suffices. No module-level cache as in
transformer.py:_load_nufftax()— that one needed a cache only because unpickled instances in multiprocessing workers never re-run__init__. Function-local imports run on every call and hitsys.modulesafter the first, so the cost is a dict lookup.Generated by Claude Code