-
-
Notifications
You must be signed in to change notification settings - Fork 30.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Deep Lazy Imports - Interpreter-level deferred module loading #91119
Comments
As the size of a Python project increases, the number of modules and the complexity of its dependencies increases too, producing two problems in large codebases: increased risk of import cycles and slow start times due to the number of modules that tend to need getting loaded. We propose implementing a robust and transparent lazy loader in CPython, based on an existing implementation we have in Cinder that proved to be extremely valuable to thousands of developers across Meta, and battle-tested in Instagram Server in production (https://docs.google.com/document/d/1l8I-FDE1xrIShm9eSNJqsGmY_VanMDX5-aK_gujhYBI/edit#heading=h.pu7ja6wu0ib). Our internal implementation is based on introducing deferred objects and converting top-level imported names to these deferred objects that are evaluated on first use. Enabling this across multiple types of workloads in Meta consistently shown improvements in startup times (up to 70%) and memory footprint (up to 40%), while virtually eliminating occurrences of import cycles. Converting imported names to deferred objects is a semantic change, and the benefits are usually meaningful only at very large scale, so we propose not changing the default behavior, and making it possible for users to opt-in to this lazy loading mechanism via -x flags or environment variables. This change would require related documentation changes that would cover the gotchas and edge cases, such as: some packages rely and import-time side-effects for correct operation; nested modules can’t be accessed unless imported explicitly; deferred loading may also defer exceptions from import time and first-access time which could be confusing. The import-time side effects gotcha can be mitigated using eager-import lists and directives, though it could be desirable to use this as a forcing function to discourage package maintainers from relying on these side-effects. |
From the linked document:
If I'm reading this correctly, once enabled all imports are lazy. Have you done any analysis to see which modules in a CLI actually need to be lazily imported? IOW, not all modules actually affect start up time, but certainly entry points would win from lazy imports. Have you done any research to see the effect of decorators (I'm thinking like click overhead). How does it affect the way CLI's are written? Do you have any code examples you can share? Have you written a PR against upstream Python for porting this to CPython? |
@warsaw, when you enable via Any code that gets loaded is always slower than no code being loaded, so in my experience there are quite a lot of things that do benefit from globally enabled Lazy Imports. Decorators work the same way, almost everything works transparently, as long as it doesn't rely in import side effects. It was surprising to find that there really weren't that many issues and not too many modules relying in side effects when enabling Lazy Imports globally in our codebases, so we don't currently use the per-module option anymore. The reliance in side effects is mainly used for global registration of something or patching of things. We found out it's very easy to just enable it everywhere and fix the few modules that rely in import side effects for them to register or patch things explicit by calling functions designated for that purpose. There's a lot left to do, but the results we've seen and the easiness of use is overwhelmingly promising to say the least. We'll work in a PR and a PEP for porting this to CPython. it should be really straightforward since it doesn't really use any Cinder specific features or specifics. |
How does this feature interact with the semi-common "try to import, otherwise define" pattern? What happens here: from __future__ import lazy_imports
try:
from _c_accelerator import foo
except ImportError:
def foo(): ... Also, how does it work with starred imports? |
@brandtbucher, in the cases where imports are inside try/except/finally, imports are always eager (precisely for that reason) and star imports are also eager. These two cases are not too common, so making the imports eager doesn't hurt the benefits too much. |
Any other special cases? I can't think of any other reasonably-common ones, besides maybe if condition:
from foo import bar
else:
def bar(): ... |
|
Got it, thanks! |
This is now (or is about to be) PEP 690: python/peps#2569 |
Note: these values reflect the state of the issue at the time it was migrated and might not reflect the current state.
Show more details
GitHub fields:
bugs.python.org fields:
The text was updated successfully, but these errors were encountered: