Deferred loading of NumPy and SciPy dependencies - #1521
Conversation
tomvanmele
left a comment
There was a problem hiding this comment.
i think the PR makes sense. however, before merging can we add a benchmark to check the impact on repeated calls to functions like normrow, transform_points_numpy, trimesh_pull_points_numpy, ...?
There was a problem hiding this comment.
i guess this is included by accident?
There was a problem hiding this comment.
No, this is intentional. The linter of the repository applied those corrections during the pre-commit hook.
I put them in a separate commit (2f59322) in case you want to cherry pick them.
There was a problem hiding this comment.
i guess this is included by accident?
Thanks for raising this point! I don't think a benchmark is necessary here because Python caches all previously imported modules in Unless functions like |
There was a problem hiding this comment.
So where is the heavy import time deferred to? would it be on the first call to a numpy-backed function?
In apps that make no use of numpy features this is a clear improvement, but in apps that do use numpy it's hard to anticipate when that might be - especially since a user might not even know.
I'd maybe add an explicit warm-up function and documentation for this behavior.
also I'd try to add some test for this; seems easy to re-introduce an import to numpy somewhere and re-introduce this import delay.
|
Hi @chenkasirer,
Yep, it will load the module on the first call to a function importing NumPy/SciPy.
Luckily for us, there's a Ruff rule precisely for this! https://docs.astral.sh/ruff/rules/banned-module-level-imports/ If you're happy with this approach, I can update the PR to modify the pyproject.toml file: [tool.ruff.lint]
select = ["E", "F", "I", "TID253"]
[tool.ruff.lint.flake8-tidy-imports]
banned-module-level-imports = ["numpy", "scipy"] |
Problem
import compas.geometrytakes several seconds due to top-level NumPy and SciPy imports incompas.geometryandcompas.data. This triggers a cascade of around 470 transitive imports at startup, even when those libraries are not used.While the COMPAS codebase already defers heavy imports in several places123, some modules don't follow this practice, which defeats the effort made elsewhere.
Impact of the PR
How to reproduce
The profile will show
_io.open_codedominating runtime across 500+ file reads. This is Python pulling in source/bytecode files from NumPy/SciPy from disk.For my particular device, it took 2.21 seconds to run
import compas.geometry. Note this time may vary significant depending on the machine hardware and cache status.The log will contain around 470 NumPy/SciPy imports with high cumulative times. For example:
How to verify fix
Repeat the steps above after switching to this branch:
uv add "git+https://github.com/josemmo/compas@performance/deferred-loading"The profile should be significantly shorter (0.14 seconds in my case) and the import log should contain no NumPy or SciPy entries.
Alternatives considered
lazy_loader(SPEC 1): The approach used by NumPy, scikit-image, NetworkX, and other libraries. Compatible with Python 3.9+, but would require restructuring large parts of the codebase.lazykeyword (PEP 810): Native language support, but requires Python 3.15+, which is outside COMPAS's current support range.Footnotes
https://github.com/compas-dev/compas/blob/e03e40975ebec56ab98b4cfaead1eedf70241cb4/src/compas/geometry/polyhedron.py#L395 ↩
https://github.com/compas-dev/compas/blob/e03e40975ebec56ab98b4cfaead1eedf70241cb4/src/compas/geometry/_core/distance.py#L595 ↩
https://github.com/compas-dev/compas/blob/e03e40975ebec56ab98b4cfaead1eedf70241cb4/src/compas/datastructures/mesh/mesh.py#L404 ↩