Skip to content

Startup performance regression #880

Closed

Description

@ofek

Description

#852 introduced a large regression in terms of startup overhead:

❯ docker run --rm -it python:3.13 bash
root@fe224bc3d8c0:/# pip install -qqq msgspec
root@fe224bc3d8c0:/# python -m timeit -n 1 -r 1 "import msgspec"
1 loop, best of 1: 11.7 msec per loop
root@fe224bc3d8c0:/# python -m timeit -n 1 -r 1 "import inspect"
1 loop, best of 1: 7.84 msec per loop

It's worth noting that as of 3.14 the inspect.get_annotations function moved to annotationlib (which the C code in that PR imports directly) and inspect merely re-exports it. That module loads twice as fast but ideally we wouldn't incur any unnecessary overhead:

❯ docker run --rm -it python:3.14 bash
root@8130942e2641:/# python -m timeit -n 1 -r 1 "import annotationlib"
1 loop, best of 1: 3.34 msec per loop

We need to investigate if removing that import is feasible in terms of what duplicates of logic would have to be maintained here.

Activity

  1. JelleZijlstra commented on Oct 19, 2025

    @JelleZijlstra
    Contributor

    I submitted #882 that mitigates this, especially on older versions. It still imports annotationlib on 3.14 though. If you really want to avoid that import too, it is perhaps possible to inline the logic—though in that case it might make more sense to first focus on removing the eager import of typing, a much bigger module than annotationlib.

    We need annotationlib.get_annotations on 3.14 primarily to avoid the class of issues discussed in https://peps.python.org/pep-0749/#annotations-and-metaclasses , which means that accessing cls.__annotations__ is not always safe. The implementation that is now in CPython mostly mitigates this (so that using cls.__annotations__ is usually safe), except in certain cases involving from __future__ import annotations. Usually is not always, so it's more correct to keep using annotationlib.get_annotations.

    The C code also imports annotationlib where needed. Replacing that would require inlining the logic it uses (get_annotate_from_class_namespace); that would be somewhat tedious to do in C and make the implementation less stable, increasing the chance it will have to change in a future version of Python.

  2. ofek commented on Oct 19, 2025

    @ofek
    MemberAuthor

    I merged that improvement, thanks! You've definitely sold me on permanently keeping annotationlib.

    I'm still learning the codebase but isn't it impossible to remove typing imports since the runtime behavior (I think) depends directly on certain types that module exposes?

  3. JelleZijlstra commented on Oct 19, 2025

    @JelleZijlstra
    Contributor

    It's definitely difficult but maybe the imports can be made lazily. For example, there is a check if something is typing.Required; maybe you can import typing only if the thing is an object with __module__ == "typing". It's likely complicated, and it might make runtime performance worse, but if you really want to minimize import time, it might be worth it.

  4. ofek commented on Oct 20, 2025

    @ofek
    MemberAuthor

    I split further work here into 2 issues:

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions