Improve import time UltraPlot through deference #402
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.
Closes #395
Summary
After profiling ultraplot's import performance, I found that matplotlib.pyplot was being loaded eagerly even when users didn't need it right away. This PR makes pyplot load lazily - only when you actually need it - while keeping everything backward compatible.
Performance Results (10 test runs, median values)
Both scenarios are faster now! The second result surprised me - I expected a slight slowdown for the import+plot case due to lazy loading overhead, but it's actually faster.
What Changed
Three files were modified:
_get_pyplot()function that imports pyplot only when needed__getattr__souplt.pyplotstill works (but loads lazily)Backward Compatibility
Everything still works exactly as before:
uplt.pyplot.show()worksuplt.show()worksuplt.subplots()worksuplt.pyplotworks (just loads on first access)The Road Not Taken: Why I Stopped Here
I explored going further with optimizations, but decided against it. Here's my thought process:
Attempt 1: Deferring Resource Registration + RC Validation
What I tried: Move colormap, color, cycle, and font registration to happen only when creating plots.
Results:
Decision: Not worth it. Most users import ultraplot specifically to make plots, so optimizing import-only at the expense of the plotting workflow is the wrong tradeoff.
Attempt 2: Aggressive Lazy Loading (chasing ~400ms)
What I considered: Lazy-load everything possible using
__getattr__tricks, optimize the rcsetup module (380ms, 55% of import time).Why I didn't do it:
The reality check: Profiling showed that 55% of our import time is in
internals.rcsetup, which creates all the RC parameter validators. This is fundamental infrastructure that's hard to defer without breaking things. Further optimization would be fighting the framework, not working with it.Profiling Data
For context, here's where import time is actually spent:
My Recommendation
Ship this PR and call it good. Here's why:
Going from 820ms to 400ms would require massive complexity for marginal real-world benefit. Most users won't notice the difference, and those who import without plotting (config scripts, CLI tools) already benefit from this PR.
Bottom line: This hits the sweet spot of meaningful improvement without unnecessary complexity.