Skip to content
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

Use __getattr__ for all package imports, improve import time #7947

Merged
merged 7 commits into from Oct 27, 2023

Conversation

samuelcolvin
Copy link
Member

@samuelcolvin samuelcolvin commented Oct 27, 2023

Change Summary

This means pydantic/__init__.py is now lazy about all imports using __getattr__.

Import performance changes:

Before

code/pydantic 0 ➤ pdm run python -m timeit -n 1 -r 1 "import pydantic"
1 loop, best of 1: 29.7 msec per loop
code/pydantic 0 ➤ pdm run python -m timeit -n 1 -r 1 "from pydantic import BaseModel"
1 loop, best of 1: 30.4 msec per loop

After

code/pydantic 0 ➤ pdm run python -m timeit -n 1 -r 1 "import pydantic"
1 loop, best of 1: 4.8 msec per loop
(env3112) code/pydantic 0 ➤ pdm run python -m timeit -n 1 -r 1 "from pydantic import BaseModel"           
1 loop, best of 1: 18.7 msec per loop

Tuna output:

image

Related issue number

As promised in #7423 (comment).

Checklist

  • The pull request title is a good summary of the changes - it will be used in the changelog
  • Unit tests for the changes exist
  • Tests pass on CI
  • Documentation reflects the changes where applicable
  • My PR is ready to review, please add a comment including the phrase "please review" to assign reviewers

Selected Reviewer: @sydney-runkle

@cloudflare-pages
Copy link

cloudflare-pages bot commented Oct 27, 2023

Deploying with  Cloudflare Pages  Cloudflare Pages

Latest commit: 9733589
Status: ✅  Deploy successful!
Preview URL: https://827630e5.pydantic-docs2.pages.dev
Branch Preview URL: https://defer-init-imports.pydantic-docs2.pages.dev

View logs

@samuelcolvin samuelcolvin added the relnotes-performance Used for performance improvements. label Oct 27, 2023
@samuelcolvin samuelcolvin changed the title use __getattr__ for all imports from __init__.py use __getattr__ for all package imports, improve import time Oct 27, 2023
@samuelcolvin samuelcolvin changed the title use __getattr__ for all package imports, improve import time Use __getattr__ for all package imports, improve import time Oct 27, 2023
@ofek
Copy link
Contributor

ofek commented Oct 27, 2023

Awesome thank you, definitely a step in the right direction!

❯ pip install -Uq pydantic
❯ python -m timeit -n 1 -r 1 "import pydantic"
1 loop, best of 1: 83.4 msec per loop
❯ python -m timeit -n 1 -r 1 "from pydantic import BaseModel"
1 loop, best of 1: 89.6 msec per loop
❯ pip install -Iq "pydantic @ git+https://github.com/pydantic/pydantic.git@defer-init-imports"
❯ python -m timeit -n 1 -r 1 "import pydantic"
1 loop, best of 1: 15.7 msec per loop
❯ python -m timeit -n 1 -r 1 "from pydantic import BaseModel"
1 loop, best of 1: 79 msec per loop

Do you see a way to reduce the import cost of BaseModel any further? That is what I, and I think most others, are optimizing for because that is the main interface that classes in one's code will inherit at import time.

@ofek
Copy link
Contributor

ofek commented Oct 27, 2023

That last commit helped a little!

❯ python -m timeit -n 1 -r 1 "from pydantic import BaseModel"
1 loop, best of 1: 63.9 msec per loop

@samuelcolvin
Copy link
Member Author

@ofek that's great, I was able to avoid importing pydantic.fields in that which should have helped a fair bit.

Could you show me the output of importtime using tuna from your system, that might help me know where to look.

@ofek
Copy link
Contributor

ofek commented Oct 27, 2023

image

@samuelcolvin
Copy link
Member Author

@ofek I've got rid of import time import of annotated-types, with that I see a >38% improvement. I really don't think there's much more I can do from there.

If you see anything else, let me know.

@samuelcolvin
Copy link
Member Author

I think this is ready.

Please review @sydney-runkle

@ofek
Copy link
Contributor

ofek commented Oct 27, 2023

Thank you!

❯ python -m timeit -n 1 -r 1 "from pydantic import BaseModel"
1 loop, best of 1: 56.8 msec per loop

image

Copy link
Member

@sydney-runkle sydney-runkle left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice! LGTM. Solid performance improvements 🏎️

One question - does this merit any update to the docs? I'm guessing not, that a change log entry is enough, but just wanted to check. I'm pretty sure 99% of users aren't going to be digging around for this kind of info.

@samuelcolvin samuelcolvin merged commit 6ed90da into main Oct 27, 2023
60 checks passed
@samuelcolvin samuelcolvin deleted the defer-init-imports branch October 27, 2023 16:41
@samuelcolvin
Copy link
Member Author

I don't think anything in logs, I think it merits a prominent notice in the release notes and a tweet from me now saying we're doing it.

AFAIK this shouldn't affect anyone and I made the change in few steps to see, but the only real answer is to release it and see.

@ofek
Copy link
Contributor

ofek commented Oct 27, 2023

Out of curiosity, how will the practice of being conscious about import time be maintained during continued development?

@samuelcolvin
Copy link
Member Author

I've added unit tests that that those modules aren't imported in specific cases, e.g. from pydantic import BaseModel.

The longer term solution is all down to @art049 - we should use Codspeed for continuous profile of stuff we care about like import time, as we do on pydantic-core.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
ready for review relnotes-performance Used for performance improvements.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants