diff --git a/Include/internal/pycore_interp_structs.h b/Include/internal/pycore_interp_structs.h index f1f427d99dea69..f7f4ac4015a338 100644 --- a/Include/internal/pycore_interp_structs.h +++ b/Include/internal/pycore_interp_structs.h @@ -678,11 +678,6 @@ struct _Py_interp_cached_objects { /* object.__reduce__ */ PyObject *objreduce; -#ifndef Py_GIL_DISABLED - /* resolve_slotdups() */ - PyObject *type_slots_pname; - pytype_slotdef *type_slots_ptrs[MAX_EQUIV]; -#endif /* TypeVar and related types */ PyTypeObject *generic_type; diff --git a/Include/internal/pycore_typeobject.h b/Include/internal/pycore_typeobject.h index 0ee7d555c56cdd..3d36cf9c23d557 100644 --- a/Include/internal/pycore_typeobject.h +++ b/Include/internal/pycore_typeobject.h @@ -148,6 +148,9 @@ typedef int (*_py_validate_type)(PyTypeObject *); extern int _PyType_Validate(PyTypeObject *ty, _py_validate_type validate, unsigned int *tp_version); extern int _PyType_CacheGetItemForSpecialization(PyHeapTypeObject *ht, PyObject *descriptor, uint32_t tp_version); +// Precalculates count of non-unique slots and fills wrapperbase::name_count. +extern int _PyType_InitSlotDefs(PyInterpreterState *interp); + #ifdef __cplusplus } #endif diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-04-10-01-52-42.gh-issue-132042.fePwlj.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-04-10-01-52-42.gh-issue-132042.fePwlj.rst new file mode 100644 index 00000000000000..b1e4c6a5d30bb6 --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2025-04-10-01-52-42.gh-issue-132042.fePwlj.rst @@ -0,0 +1 @@ +Improve class creation times by up to 40% by pre-computing type slots just once. Patch by Sergey Miryanov. diff --git a/Objects/typeobject.c b/Objects/typeobject.c index b9d549610693c1..07f29062eecab3 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -11309,6 +11309,11 @@ static pytype_slotdef slotdefs[] = { {NULL} }; +/* Stores the number of times where slotdefs has elements with same name. + This counter precalculated by _PyType_InitSlotDefs when main + interprepter starts. */ +static uint8_t slotdefs_name_counts[Py_ARRAY_LENGTH(slotdefs)]; + /* Given a type pointer and an offset gotten from a slotdef entry, return a pointer to the actual slot. This is not quite the same as simply adding the offset to the type pointer, since it takes care to indirect through the @@ -11351,61 +11356,6 @@ slotptr(PyTypeObject *type, int ioffset) return (void **)ptr; } -/* Return a slot pointer for a given name, but ONLY if the attribute has - exactly one slot function. The name must be an interned string. */ -static void ** -resolve_slotdups(PyTypeObject *type, PyObject *name) -{ - /* XXX Maybe this could be optimized more -- but is it worth it? */ - -#ifdef Py_GIL_DISABLED - pytype_slotdef *ptrs[MAX_EQUIV]; - pytype_slotdef **pp = ptrs; - /* Collect all slotdefs that match name into ptrs. */ - for (pytype_slotdef *p = slotdefs; p->name_strobj; p++) { - if (p->name_strobj == name) - *pp++ = p; - } - *pp = NULL; -#else - /* pname and ptrs act as a little cache */ - PyInterpreterState *interp = _PyInterpreterState_GET(); -#define pname _Py_INTERP_CACHED_OBJECT(interp, type_slots_pname) -#define ptrs _Py_INTERP_CACHED_OBJECT(interp, type_slots_ptrs) - pytype_slotdef *p, **pp; - - if (pname != name) { - /* Collect all slotdefs that match name into ptrs. */ - pname = name; - pp = ptrs; - for (p = slotdefs; p->name_strobj; p++) { - if (p->name_strobj == name) - *pp++ = p; - } - *pp = NULL; - } -#endif - - /* Look in all slots of the type matching the name. If exactly one of these - has a filled-in slot, return a pointer to that slot. - Otherwise, return NULL. */ - void **res, **ptr; - res = NULL; - for (pp = ptrs; *pp; pp++) { - ptr = slotptr(type, (*pp)->offset); - if (ptr == NULL || *ptr == NULL) - continue; - if (res != NULL) - return NULL; - res = ptr; - } -#ifndef Py_GIL_DISABLED -#undef pname -#undef ptrs -#endif - return res; -} - // Return true if "name" corresponds to at least one slot definition. This is // a more accurate but more expensive test compared to is_dunder_name(). static bool @@ -11532,7 +11482,10 @@ update_one_slot(PyTypeObject *type, pytype_slotdef *p, pytype_slotdef **next_p, } if (Py_IS_TYPE(descr, &PyWrapperDescr_Type) && ((PyWrapperDescrObject *)descr)->d_base->name_strobj == p->name_strobj) { - void **tptr = resolve_slotdups(type, p->name_strobj); + void **tptr = NULL; + if (slotdefs_name_counts[(p - slotdefs) / sizeof(pytype_slotdef)] == 1) + tptr = slotptr(type, p->offset); + if (tptr == NULL || tptr == ptr) generic = p->function; d = (PyWrapperDescrObject *)descr; @@ -11742,9 +11695,79 @@ update_all_slots(PyTypeObject* type) } return 0; } - #endif +int +_PyType_InitSlotDefs(PyInterpreterState *interp) +{ + if (interp != interp->runtime->interpreters.main) { + return 0; + } + PyObject *bytearray = NULL; + PyObject *cache = PyDict_New(); + if (!cache) { + return -1; + } + + pytype_slotdef *p; + Py_ssize_t idx = 0; + for (p = slotdefs; p->name_strobj; p++, idx++) { + assert(idx < 255); + + if (PyDict_GetItemRef(cache, p->name_strobj, &bytearray) < 0) { + goto error; + } + + if (!bytearray) { + Py_ssize_t size = sizeof(uint8_t) * (1 + MAX_EQUIV); + bytearray = PyByteArray_FromStringAndSize(NULL, size); + if (!bytearray) { + goto error; + } + + uint8_t *data = (uint8_t *)PyByteArray_AS_STRING(bytearray); + data[0] = 0; + + if (PyDict_SetItem(cache, p->name_strobj, bytearray) < 0) { + goto error; + } + } + + assert(PyByteArray_CheckExact(bytearray)); + uint8_t *data = (uint8_t *)PyByteArray_AS_STRING(bytearray); + + data[0] += 1; + assert(data[0] < MAX_EQUIV); + + data[data[0]] = (uint8_t)idx; + + Py_CLEAR(bytearray); + } + + memset(slotdefs_name_counts, 0, sizeof(slotdefs_name_counts)); + + Py_ssize_t pos = 0; + PyObject *key = NULL; + PyObject *value = NULL; + while (PyDict_Next(cache, &pos, &key, &value)) { + uint8_t *data = (uint8_t *)PyByteArray_AS_STRING(value); + uint8_t n = data[0]; + uint8_t i = 0; + for(; i < n; i++) { + uint8_t idx = data[i + 1]; + slotdefs_name_counts[idx] = n; + } + } + + Py_DECREF(cache); + return 0; + +error: + Py_XDECREF(bytearray); + Py_DECREF(cache); + return -1; +} + PyObject * _PyType_GetSlotWrapperNames(void) diff --git a/Python/pylifecycle.c b/Python/pylifecycle.c index 724fda63511282..7cd076df3d89fa 100644 --- a/Python/pylifecycle.c +++ b/Python/pylifecycle.c @@ -828,6 +828,10 @@ pycore_init_builtins(PyThreadState *tstate) } interp->callable_cache.object__getattribute__ = object__getattribute__; + if (_PyType_InitSlotDefs(interp) < 0) { + return _PyStatus_ERR("failed to init slotdefs"); + } + if (_PyBuiltins_AddExceptions(bimod) < 0) { return _PyStatus_ERR("failed to add exceptions to builtins"); } diff --git a/Tools/c-analyzer/cpython/_parser.py b/Tools/c-analyzer/cpython/_parser.py index 037fe11ea223c7..682512801cd138 100644 --- a/Tools/c-analyzer/cpython/_parser.py +++ b/Tools/c-analyzer/cpython/_parser.py @@ -323,7 +323,7 @@ def clean_lines(text): _abs('Modules/_testcapimodule.c'): (20_000, 400), _abs('Modules/expat/expat.h'): (10_000, 400), _abs('Objects/stringlib/unicode_format.h'): (10_000, 400), - _abs('Objects/typeobject.c'): (35_000, 200), + _abs('Objects/typeobject.c'): (380_000, 13_000), _abs('Python/compile.c'): (20_000, 500), _abs('Python/optimizer.c'): (100_000, 5_000), _abs('Python/parking_lot.c'): (40_000, 1000), diff --git a/Tools/c-analyzer/cpython/ignored.tsv b/Tools/c-analyzer/cpython/ignored.tsv index 15b18f5286b399..3cdd7f5eaa23ff 100644 --- a/Tools/c-analyzer/cpython/ignored.tsv +++ b/Tools/c-analyzer/cpython/ignored.tsv @@ -341,6 +341,8 @@ Objects/obmalloc.c - obmalloc_state_main - Objects/obmalloc.c - obmalloc_state_initialized - Objects/typeobject.c - name_op - Objects/typeobject.c - slotdefs - +# It initialized only once when main interpeter starts +Objects/typeobject.c - slotdefs_name_counts - Objects/unicodeobject.c - stripfuncnames - Objects/unicodeobject.c - utf7_category - Objects/unicodeobject.c unicode_decode_call_errorhandler_wchar argparse -