GeezerVisonThe World Wide Web, restored to 800 pixels of glory. | ||||||||||||||
| Home|About|Gaming cabinet|Archives|How it works|Starter sites | ||||||||||||||
GeezerVison is showing github.com — Types with `mro()` unconditionally disables type attribute cache, causing performance regression for compatible MRO implementations, such as Zope · Issue #156096 · python/cpython Open Labels type-bugAn unexpected behavior, bug, or errorAn unexpected behavior, bug, or error DescriptionBug reportBug description:Since #127773 / #127924, any type whose metaclass defines a custom This affects Zope's ExtensionClass, used as base class for most classes the Zope ecosystem. This package has 5000 daily downloads on pypi ( https://pypistats.org/packages/extensionclass ). ExtensionClass defines a custom Compared to python3.11, this seems to causes a ~3x performance regression for attribute lookups on all zope classes: it's hard to tell how much this affect applications, this is just a micro benchmark, but we are also observing that running ERP5 test suites (a complex application based on zope) is slower on python3.13 than it was on python3.11. AnalysisIn Lines 1261 to 1262 in af49df9
which sets Lines 1279 to 1283 in af49df9
This was introduced to fix #127773, where a custom Although these are not much relevant here, ExtensionClass's Suggested fixCheck whether the custom MRO actually contains non-base entries before disabling the cache. This requires an // Return true if `super` is reachable from `sub` through `tp_bases`, // as opposed to a type merely injected into the MRO by a custom `mro()` // implementation. Only entries reachable through `tp_bases` are covered // by the version-tag invalidation that _PyType_Modified_Unlocked() // propagates through `tp_subclasses`. static int is_superclass(PyTypeObject *super, PyTypeObject *sub) { PyObject *bases = lookup_tp_bases(sub); if (bases == NULL) { return 0; } Py_ssize_t n = PyTuple_GET_SIZE(bases); for (Py_ssize_t i = 0; i < n; i++) { PyTypeObject *base = _PyType_CAST(PyTuple_GET_ITEM(bases, i)); if (base == super || is_superclass(super, base)) { return 1; } } return 0; } Then in if (!Py_IS_TYPE(type, &PyType_Type) && has_custom_mro(type)) { // Only disable cache if the custom mro() injected an entry that // is not reachable through tp_bases; PyObject *mro = lookup_tp_mro(type); if (mro == NULL) { goto clear; } Py_ssize_t mro_size = PyTuple_GET_SIZE(mro); for (Py_ssize_t m = 0; m < mro_size; m++) { PyTypeObject *entry = _PyType_CAST(PyTuple_GET_ITEM(mro, m)); if (entry != type && !is_superclass(entry, type)) { goto clear; } } } I'm not at all familiar with this, but it seems to me that this would preserve the correctness of #127924 for types that do inject non-base entries into their MRO (because cache would still be disabled for those), while restoring cache performance for compatible custom MRO implementations like Zope's ExtensionClass. If this approach makes sense, I already have a draft commit implementing this: perrinjerome@89af338 and I would be happy to clean it up and make a pull request. CPython versions tested on:3.13 Operating systems tested on:Linux Reacted by Jens W. Klein Activity
MetadataMetadataAssigneesLabelstype-bugAn unexpected behavior, bug, or errorAn unexpected behavior, bug, or error ProjectsMilestoneRelationshipsDevelopmentIssue actions | ||||||||||||||
| You are visitor 01402520 — GeezerVison does not store the pages you look up. Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required. |