Perf: defer heavy startup imports to reduce launch time by ~500ms#25976
Perf: defer heavy startup imports to reduce launch time by ~500ms#25976eendebakpt wants to merge 4 commits into
Conversation
- Remove top-level `requests` import in programs.py (saved ~250ms): only `CaseInsensitiveDict` was used in one Windows-only branch; replaced with an inline case-insensitive dict lookup using stdlib. - Defer `keyring` import in config/manager.py (saved ~200ms): keyring is only needed inside `if secure:` branches, which are hit only when remote credentials are accessed — import moved to those call sites. - Avoid importing sphinxify (sphinx+docutils+jinja2) in config/appearance.py (saved ~300ms): `CSS_PATH` is just a directory path that we now compute directly via `importlib.util.find_spec` without loading the Sphinx stack. - Defer `asyncio` import in programs.py: moved inside the `asynchronous=True` branch of `run_shell_command`, which is called only from environ.py. Total wall-clock reduction on the critical import path: spyder.utils.programs: ~440ms → ~130ms spyder.config.manager: ~616ms → ~220ms spyder.app.start: ~431ms → ~167ms Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
Hey @eendebakpt, why did you close this PR? The gains in startup time seem significant enough to merit its inclusion in our next version. |
|
@ccordoba12 PR was opened too soon by Claude. I wanted to review the PR myself first to avoid wasting reviewers resources and I wanted to validate the actual gains. Now that you are looking anyway: if change makes sense just reopen it and we can iterate until the PR is good. For python 3.15 I would recommend to adopt PEP 810 (it will help with more imports). |
- Replace importlib.util.find_spec (which can return None, triggering a type error) with osp.dirname(spyder.__file__) to derive CSS_PATH. The spyder package is always in sys.modules at this point, so this is both safe and simpler. - Remove stray # lazy import inline comments on keyring imports in config/manager.py. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
@dalthviz, please take a look at @eendebakpt's work. |
|
|
||
| CSS_PATH = osp.join( | ||
| osp.dirname(spyder.__file__), 'plugins', 'help', 'utils', 'static', 'css' | ||
| ) |
There was a problem hiding this comment.
I think here we could use something like what is done over the IPython Console to prevent having to do a import spyder:
spyder/spyder/plugins/ipythonconsole/widgets/client.py
Lines 73 to 75 in 127e840
However, maybe it could be worthy to move the CSS_PATH constant definition somewhere from where every other module could import it and without causing other unwanted modules to get imported? 🤔
Also. as a side note, checking the CSS_PATH constant usage seems like we have a TODO related with it around the mainwindow setup method (import that maybe could be affecting startup times too?):
spyder/spyder/app/mainwindow.py
Lines 659 to 662 in 127e840
What do you think @ccordoba12 ?
There was a problem hiding this comment.
@dalthviz Do you want me to look into this?
|
/show binder |
Co-authored-by: Daniel Althviz Moré <16781833+dalthviz@users.noreply.github.com>
Problem
Spyder's startup time was unnecessarily slow due to several third-party libraries being imported eagerly at module level, even though they are only needed lazily or in rare code paths.
Profiled using
python -X importtimeand subprocess timing.Changes
spyder/utils/programs.py— removesrequestsand defersasynciorequests(~250ms saved):CaseInsensitiveDictfromrequests.structureswas imported at module level but only used in one Windows-only branch insidealter_subprocess_kwargs_by_platform(). Replaced with an inline case-insensitive key lookup using only stdlib ({k.lower(): k for k in env}).asyncio(~100ms saved, deferred): Moved inside theif asynchronous:branch ofrun_shell_command(), which is called only fromenviron.pywithasynchronous=True.spyder/config/manager.py— deferskeyring(~200ms saved)keyringandkeyring.errors.NoKeyringErrorwere imported at the top of the module. These are only needed insideif secure:branches (get,set,remove_option), which are only hit when remote/credential-secured config options are accessed. Moved the imports to those three call sites.spyder/config/appearance.py— avoids importing sphinxify (~300ms saved)from spyder.plugins.help.utils.sphinxify import CSS_PATHwas triggering the fullsphinx+docutils+jinja2import chain at startup, just to get a path string constant. Replaced withimportlib.util.find_spec('spyder.plugins.help.utils')which resolves the path without loading the Sphinx stack.Measured improvement
spyder.utils.programsspyder.config.managerspyder.app.start🤖 Generated with Claude Code