Skip to content

functools.cached_property doc warnings about key-sharing are obsolete #101815

Open

Description

@carljm

Documentation

The documentation for functools.cached_property currently says "this decorator interferes with the operation of PEP 412 key-sharing dictionaries. This means that instance dictionaries can take more space than usual." This warning is no longer accurate as of Python 3.12; dict key-sharing is now resilient to instance attributes being created in different orders across different instances of the same type, as happens with cached properties; this no longer causes keys to become unshared for the type. The different keys are just appended to the shared keys for the type and some instances may store a NULL for some keys.

It remains true that cached_property can cause instances to use slightly more memory, but the reason for this has changed (and the extra memory used will be significantly less.) The new reason is that any access of __dict__ on an instance will force creation of a real dictionary object for that instance, rather than just a PyDictValues. The keys remain shared in the created dict, though. So the added memory use is no longer "all the keys" but rather just "a PyDictObject" (which consists of only three fields.)

I'll file a PR to update the wording in the docs so that it is accurate to the new behavior.

(Accessing __dict__ in cached_property is probably not strictly required, but that's a topic for a separate issue/PR.)

Linked PRs

Activity

  1. carljm commented on Feb 10, 2023

    @carljm
    MemberAuthor

    cc @markshannon to confirm the accuracy of what I've said above re key-sharing, dict-creation, etc

  2. added a commit that references this issue on Feb 10, 2023
  3. rhettinger commented on Feb 11, 2023

    @rhettinger
    Contributor

    This is a nice improvement. It was irritating to lose key-sharing.

  4. carljm commented on May 3, 2023

    @carljm
    MemberAuthor

    There's a subtlety here I hadn't yet understood when I initially wrote this issue. In order to reduce wasted space in instances' value arrays, each new instance created shrinks the dk_usable counter in the type's shared keys by 1 (if there is unused space remaining in the shared keys) before creating the instance's values array (with a size matching the current total size of the shared keys). (See init_inline_values in dictobject.c.) This means that eventually, as more instances are created, the size of the shared keys will shrink to match its used entries: the available room for growth in the shared keys will shrink to zero.

    So if the cached property is accessed on one of the early instances created, there will be no problem with key sharing; an entry will be made for the cached property in the shared keys while it still has extra room. But if many instances are created before the cached property is ever accessed on any of them, the "extra room" in the shared keys will shrink to zero, and then accessing the cached property on any instance will remove that instance from key sharing. So whether cached_property use interferes with key sharing or not is now heavily dependent on the specific usage pattern of the application.

    These details of how instance attributes are managed are not documented or stable; it has changed significantly in recent versions and may continue to change over time. In general, CPython performance optimizations should adapt to how Python code authors write their code, more than the reverse. So I'm still inclined to think that the cached_property docs shouldn't venture into discussion of key-sharing.

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

    docsDocumentation in the Doc dir

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions