From 56d13fcf5259c6fc9fd0fe4d0fa2d73730dc5918 Mon Sep 17 00:00:00 2001 From: Sergey Miryanov Date: Sun, 6 Apr 2025 17:08:06 +0500 Subject: [PATCH 01/25] Prebuild mro_dict for find_name_in_mro --- Objects/typeobject.c | 59 +++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 55 insertions(+), 4 deletions(-) diff --git a/Objects/typeobject.c b/Objects/typeobject.c index b92eaefc90d0af..38bd9b2b7438d6 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -5636,6 +5636,26 @@ find_name_in_mro(PyTypeObject *type, PyObject *name, int *error) return res; } +static PyObject * +find_name_in_mro_new(PyObject *mro_dict, PyObject *name, int *error) +{ + ASSERT_TYPE_LOCK_HELD(); + + Py_hash_t hash = _PyObject_HashFast(name); + if (hash == -1) { + *error = -1; + return NULL; + } + + PyObject *res = NULL; + if (_PyDict_GetItemRef_KnownHash((PyDictObject *)mro_dict, name, hash, &res) < 0) { + *error = -1; + } else { + *error = 0; + } + return res; +} + /* Check if the "readied" PyUnicode name is a double-underscore special name. */ static int @@ -11086,7 +11106,7 @@ resolve_slotdups(PyTypeObject *type, PyObject *name) * because that's convenient for fixup_slot_dispatchers(). This function never * sets an exception: if an internal error happens (unlikely), it's ignored. */ static pytype_slotdef * -update_one_slot(PyTypeObject *type, pytype_slotdef *p) +update_one_slot(PyTypeObject *type, pytype_slotdef *p, PyObject *mro_dict) { ASSERT_TYPE_LOCK_HELD(); @@ -11117,7 +11137,11 @@ update_one_slot(PyTypeObject *type, pytype_slotdef *p) assert(!PyErr_Occurred()); do { /* Use faster uncached lookup as we won't get any cache hits during type setup. */ - descr = find_name_in_mro(type, p->name_strobj, &error); + if (mro_dict == NULL) { + descr = find_name_in_mro(type, p->name_strobj, &error); + } else { + descr = find_name_in_mro_new(mro_dict, p->name_strobj, &error); + } if (descr == NULL) { if (error == -1) { /* It is unlikely but not impossible that there has been an exception @@ -11208,7 +11232,7 @@ update_slots_callback(PyTypeObject *type, void *data) pytype_slotdef **pp = (pytype_slotdef **)data; for (; *pp; pp++) { - update_one_slot(type, *pp); + update_one_slot(type, *pp, NULL); } return 0; } @@ -11261,11 +11285,38 @@ fixup_slot_dispatchers(PyTypeObject *type) // where we'd like to assert that the type is locked. BEGIN_TYPE_LOCK(); + PyObject *mro = lookup_tp_mro(type); + assert(mro); + + PyObject *mro_dict = NULL; + Py_ssize_t n = PyTuple_GET_SIZE(mro); + for (Py_ssize_t i = 0; i < n; i++) { + PyObject *base = PyTuple_GET_ITEM(mro, n-i-1); + PyObject *dict = lookup_tp_dict(_PyType_CAST(base)); + assert(dict && PyDict_Check(dict)); + + if (i == 0) { + mro_dict = PyDict_Copy(dict); + if (!mro_dict) { + PyErr_Clear(); + break; + } + } else { + if (PyDict_Merge(mro_dict, dict, 1) < 0) { + Py_CLEAR(mro_dict); + PyErr_Clear(); + break; + } + } + } + assert(!PyErr_Occurred()); for (pytype_slotdef *p = slotdefs; p->name; ) { - p = update_one_slot(type, p); + p = update_one_slot(type, p, mro_dict); } + Py_XDECREF(mro_dict); + END_TYPE_LOCK(); } From 1eed75d6de7efa316241126854337565f4e82aa0 Mon Sep 17 00:00:00 2001 From: Sergey Miryanov Date: Sun, 6 Apr 2025 17:08:34 +0500 Subject: [PATCH 02/25] Preget tp_dict --- Objects/typeobject.c | 40 ++++++++++++++++++---------------------- 1 file changed, 18 insertions(+), 22 deletions(-) diff --git a/Objects/typeobject.c b/Objects/typeobject.c index 38bd9b2b7438d6..d7c6bb34837023 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -4144,9 +4144,8 @@ type_new_set_name(const type_new_ctx *ctx, PyTypeObject *type) /* Set __module__ in the dict */ static int -type_new_set_module(PyTypeObject *type) +type_new_set_module(PyObject *dict) { - PyObject *dict = lookup_tp_dict(type); int r = PyDict_Contains(dict, &_Py_ID(__module__)); if (r < 0) { return -1; @@ -4173,10 +4172,9 @@ type_new_set_module(PyTypeObject *type) /* Set ht_qualname to dict['__qualname__'] if available, else to __name__. The __qualname__ accessor will look for ht_qualname. */ static int -type_new_set_ht_name(PyTypeObject *type) +type_new_set_ht_name(PyTypeObject *type, PyObject *dict) { PyHeapTypeObject *et = (PyHeapTypeObject *)type; - PyObject *dict = lookup_tp_dict(type); PyObject *qualname; if (PyDict_GetItemRef(dict, &_Py_ID(__qualname__), &qualname) < 0) { return -1; @@ -4205,9 +4203,8 @@ type_new_set_ht_name(PyTypeObject *type) and is a string. The __doc__ accessor will first look for tp_doc; if that fails, it will still look into __dict__. */ static int -type_new_set_doc(PyTypeObject *type) +type_new_set_doc(PyTypeObject *type, PyObject* dict) { - PyObject *dict = lookup_tp_dict(type); PyObject *doc = PyDict_GetItemWithError(dict, &_Py_ID(__doc__)); if (doc == NULL) { if (PyErr_Occurred()) { @@ -4241,9 +4238,8 @@ type_new_set_doc(PyTypeObject *type) static int -type_new_staticmethod(PyTypeObject *type, PyObject *attr) +type_new_staticmethod(PyObject *dict, PyObject *attr) { - PyObject *dict = lookup_tp_dict(type); PyObject *func = PyDict_GetItemWithError(dict, attr); if (func == NULL) { if (PyErr_Occurred()) { @@ -4269,9 +4265,8 @@ type_new_staticmethod(PyTypeObject *type, PyObject *attr) static int -type_new_classmethod(PyTypeObject *type, PyObject *attr) +type_new_classmethod(PyObject *dict, PyObject *attr) { - PyObject *dict = lookup_tp_dict(type); PyObject *func = PyDict_GetItemWithError(dict, attr); if (func == NULL) { if (PyErr_Occurred()) { @@ -4372,9 +4367,8 @@ type_new_set_slots(const type_new_ctx *ctx, PyTypeObject *type) /* store type in class' cell if one is supplied */ static int -type_new_set_classcell(PyTypeObject *type) +type_new_set_classcell(PyTypeObject *type, PyObject *dict) { - PyObject *dict = lookup_tp_dict(type); PyObject *cell = PyDict_GetItemWithError(dict, &_Py_ID(__classcell__)); if (cell == NULL) { if (PyErr_Occurred()) { @@ -4399,9 +4393,8 @@ type_new_set_classcell(PyTypeObject *type) } static int -type_new_set_classdictcell(PyTypeObject *type) +type_new_set_classdictcell(PyObject *dict) { - PyObject *dict = lookup_tp_dict(type); PyObject *cell = PyDict_GetItemWithError(dict, &_Py_ID(__classdictcell__)); if (cell == NULL) { if (PyErr_Occurred()) { @@ -4432,30 +4425,33 @@ type_new_set_attrs(const type_new_ctx *ctx, PyTypeObject *type) return -1; } - if (type_new_set_module(type) < 0) { + PyObject *dict = lookup_tp_dict(type); + assert(dict); + + if (type_new_set_module(dict) < 0) { return -1; } - if (type_new_set_ht_name(type) < 0) { + if (type_new_set_ht_name(type, dict) < 0) { return -1; } - if (type_new_set_doc(type) < 0) { + if (type_new_set_doc(type, dict) < 0) { return -1; } /* Special-case __new__: if it's a plain function, make it a static function */ - if (type_new_staticmethod(type, &_Py_ID(__new__)) < 0) { + if (type_new_staticmethod(dict, &_Py_ID(__new__)) < 0) { return -1; } /* Special-case __init_subclass__ and __class_getitem__: if they are plain functions, make them classmethods */ - if (type_new_classmethod(type, &_Py_ID(__init_subclass__)) < 0) { + if (type_new_classmethod(dict, &_Py_ID(__init_subclass__)) < 0) { return -1; } - if (type_new_classmethod(type, &_Py_ID(__class_getitem__)) < 0) { + if (type_new_classmethod(dict, &_Py_ID(__class_getitem__)) < 0) { return -1; } @@ -4465,10 +4461,10 @@ type_new_set_attrs(const type_new_ctx *ctx, PyTypeObject *type) type_new_set_slots(ctx, type); - if (type_new_set_classcell(type) < 0) { + if (type_new_set_classcell(type, dict) < 0) { return -1; } - if (type_new_set_classdictcell(type) < 0) { + if (type_new_set_classdictcell(dict) < 0) { return -1; } return 0; From bba66d611128531358c5093dca3a51758f8d0046 Mon Sep 17 00:00:00 2001 From: Sergey Miryanov Date: Tue, 8 Apr 2025 01:40:01 +0500 Subject: [PATCH 03/25] Slotdefs cache --- Objects/typeobject.c | 124 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 122 insertions(+), 2 deletions(-) diff --git a/Objects/typeobject.c b/Objects/typeobject.c index d7c6bb34837023..42e72f1e04c461 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -10962,6 +10962,11 @@ static pytype_slotdef slotdefs[] = { {NULL} }; +/* {name: [pytype_slotdef]} +*/ +static PyObject *slotdefs_cache = NULL; + + /* 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 @@ -11010,13 +11015,63 @@ static void ** resolve_slotdups(PyTypeObject *type, PyObject *name) { /* XXX Maybe this could be optimized more -- but is it worth it? */ + void **res, **ptr; + res = NULL; + + if (slotdefs_cache) { + int rc = 0; + // PyObject *cache = Py_NewRef(slotdefs_cache); + Py_BEGIN_CRITICAL_SECTION(slotdefs_cache); + PyObject *cache = slotdefs_cache; + assert(Py_REFCNT(cache) >= 1); + + Py_INCREF(cache); + assert(Py_REFCNT(cache) > 1); + + PyObject* list=NULL; + rc = PyDict_GetItemRef(cache, name, &list); + assert(Py_REFCNT(cache) > 1); + if (rc > 0) { + + // assert(list); + // Py_ssize_t n = PyList_Size(list); + // assert(n >= 0); + // Py_ssize_t i; + // for(i = 0; i < n; i++) { + // PyObject *py_idx = PyList_GET_ITEM(list, i); + // assert(PyLong_Check(py_idx)); + // Py_ssize_t idx = PyLong_AsSsize_t(py_idx); + // assert (idx < Py_ARRAY_LENGTH(slotdefs)); + // pytype_slotdef *x = &slotdefs[idx]; + // ptr = slotptr(type, x->offset); + // if (ptr == NULL || *ptr == NULL) { + // continue; + // } + // if (res != NULL) { + // res = NULL; + // break; + // } + // res = ptr; + // } + assert(Py_REFCNT(list) > 1); + Py_DECREF(list); + } else if (rc < 0) { + PyErr_Clear(); + } + + assert(Py_REFCNT(cache) > 1); + Py_DECREF(cache); + Py_END_CRITICAL_SECTION(); + if (rc > 0) { + return res; + } + } /* 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; - void **res, **ptr; if (pname != name) { /* Collect all slotdefs that match name into ptrs. */ @@ -11032,7 +11087,6 @@ resolve_slotdups(PyTypeObject *type, PyObject *name) /* 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. */ - res = NULL; for (pp = ptrs; *pp; pp++) { ptr = slotptr(type, (*pp)->offset); if (ptr == NULL || *ptr == NULL) @@ -11306,6 +11360,72 @@ fixup_slot_dispatchers(PyTypeObject *type) } } + if (!slotdefs_cache) { + PyObject *cache = PyDict_New(); + if (cache) { + pytype_slotdef *p; + Py_ssize_t idx = 0; + for (p = slotdefs; p->name_strobj; p++, idx++) { + // Py_hash_t hash = _PyObject_HashFast(p->name_strobj); + // if (hash == -1) { + // Py_CLEAR(cache); + // break; + // } + + // PyObject *list; + // if (_PyDict_GetItemRef_KnownHash_LockHeld((PyDictObject *)cache, p->name_strobj, hash, &list) < 0) { + // Py_CLEAR(cache); + // break; + // } + + // if (!list) { + // list = PyList_New(0); + // if (!list) { + // Py_CLEAR(cache); + // break; + // } + + // if (_PyDict_SetItem_KnownHash_LockHeld((PyDictObject *)cache, p->name_strobj, list, hash) < 0) { + // Py_DECREF(list); + // Py_CLEAR(cache); + // break; + // } + // } + + // PyObject *py_idx = PyLong_FromSsize_t(idx); + // if (!py_idx) { + // Py_DECREF(list); + // Py_CLEAR(cache); + // break; + // } + + // if (PyList_Append(list, py_idx) < 0) { + // Py_DECREF(py_idx); + // Py_DECREF(list); + // Py_CLEAR(cache); + // break; + // } + + // Py_DECREF(py_idx); + // Py_DECREF(list); + } + } + + if (cache) { + Py_ssize_t pos = 0; + PyObject* key=NULL; + PyObject* value=NULL; + while (PyDict_Next(cache, &pos, &key, &value)) { + assert(Py_REFCNT(key) > 1); + assert(Py_REFCNT(value) == 1); + } + + Py_XSETREF(slotdefs_cache, cache); + } else { + PyErr_Clear(); + } + } + assert(!PyErr_Occurred()); for (pytype_slotdef *p = slotdefs; p->name; ) { p = update_one_slot(type, p, mro_dict); From bac95a56806a5a9de5eb5ce3cf7dbd1520662cbe Mon Sep 17 00:00:00 2001 From: Sergey Miryanov Date: Wed, 9 Apr 2025 00:43:04 +0500 Subject: [PATCH 04/25] Move slotdefs_cache to interp --- Include/internal/pycore_interp_structs.h | 1 + Objects/typeobject.c | 165 +++++++++++------------ Python/pystate.c | 4 + 3 files changed, 80 insertions(+), 90 deletions(-) diff --git a/Include/internal/pycore_interp_structs.h b/Include/internal/pycore_interp_structs.h index 44a4231fbc5e30..92367e2620829c 100644 --- a/Include/internal/pycore_interp_structs.h +++ b/Include/internal/pycore_interp_structs.h @@ -677,6 +677,7 @@ struct _Py_interp_cached_objects { PyTypeObject *paramspecargs_type; PyTypeObject *paramspeckwargs_type; PyTypeObject *constevaluator_type; + PyObject *slotdefs_cache; }; struct _Py_interp_static_objects { diff --git a/Objects/typeobject.c b/Objects/typeobject.c index 42e72f1e04c461..228cbebb3f2ac6 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -10962,10 +10962,6 @@ static pytype_slotdef slotdefs[] = { {NULL} }; -/* {name: [pytype_slotdef]} -*/ -static PyObject *slotdefs_cache = NULL; - /* 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 @@ -11014,61 +11010,55 @@ slotptr(PyTypeObject *type, int ioffset) static void ** resolve_slotdups(PyTypeObject *type, PyObject *name) { + PyInterpreterState *interp = _PyInterpreterState_GET(); + /* XXX Maybe this could be optimized more -- but is it worth it? */ void **res, **ptr; res = NULL; - if (slotdefs_cache) { + PyObject *cache = Py_XNewRef(interp->cached_objects.slotdefs_cache); + if (cache) { int rc = 0; - // PyObject *cache = Py_NewRef(slotdefs_cache); - Py_BEGIN_CRITICAL_SECTION(slotdefs_cache); - PyObject *cache = slotdefs_cache; - assert(Py_REFCNT(cache) >= 1); - - Py_INCREF(cache); - assert(Py_REFCNT(cache) > 1); + // Py_BEGIN_CRITICAL_SECTION(cache); PyObject* list=NULL; rc = PyDict_GetItemRef(cache, name, &list); - assert(Py_REFCNT(cache) > 1); if (rc > 0) { - // assert(list); - // Py_ssize_t n = PyList_Size(list); - // assert(n >= 0); - // Py_ssize_t i; - // for(i = 0; i < n; i++) { - // PyObject *py_idx = PyList_GET_ITEM(list, i); - // assert(PyLong_Check(py_idx)); - // Py_ssize_t idx = PyLong_AsSsize_t(py_idx); - // assert (idx < Py_ARRAY_LENGTH(slotdefs)); - // pytype_slotdef *x = &slotdefs[idx]; - // ptr = slotptr(type, x->offset); - // if (ptr == NULL || *ptr == NULL) { - // continue; - // } - // if (res != NULL) { - // res = NULL; - // break; - // } - // res = ptr; - // } + assert(list); + Py_ssize_t n = PyList_Size(list); + assert(n >= 0); + Py_ssize_t i; + for(i = 0; i < n; i++) { + PyObject *py_idx = PyList_GET_ITEM(list, i); + assert(PyLong_Check(py_idx)); + Py_ssize_t idx = PyLong_AsSsize_t(py_idx); + assert (idx < Py_ARRAY_LENGTH(slotdefs)); + pytype_slotdef *x = &slotdefs[idx]; + ptr = slotptr(type, x->offset); + if (ptr == NULL || *ptr == NULL) { + continue; + } + if (res != NULL) { + res = NULL; + break; + } + res = ptr; + } assert(Py_REFCNT(list) > 1); Py_DECREF(list); } else if (rc < 0) { PyErr_Clear(); } - assert(Py_REFCNT(cache) > 1); Py_DECREF(cache); - Py_END_CRITICAL_SECTION(); + // Py_END_CRITICAL_SECTION(); if (rc > 0) { return res; } } /* 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; @@ -11360,68 +11350,63 @@ fixup_slot_dispatchers(PyTypeObject *type) } } - if (!slotdefs_cache) { - PyObject *cache = PyDict_New(); + PyInterpreterState *interp = _PyInterpreterState_GET(); + if (!interp->cached_objects.slotdefs_cache) { + + PyObject* cache = PyDict_New(); if (cache) { pytype_slotdef *p; Py_ssize_t idx = 0; for (p = slotdefs; p->name_strobj; p++, idx++) { - // Py_hash_t hash = _PyObject_HashFast(p->name_strobj); - // if (hash == -1) { - // Py_CLEAR(cache); - // break; - // } - - // PyObject *list; - // if (_PyDict_GetItemRef_KnownHash_LockHeld((PyDictObject *)cache, p->name_strobj, hash, &list) < 0) { - // Py_CLEAR(cache); - // break; - // } - - // if (!list) { - // list = PyList_New(0); - // if (!list) { - // Py_CLEAR(cache); - // break; - // } - - // if (_PyDict_SetItem_KnownHash_LockHeld((PyDictObject *)cache, p->name_strobj, list, hash) < 0) { - // Py_DECREF(list); - // Py_CLEAR(cache); - // break; - // } - // } - - // PyObject *py_idx = PyLong_FromSsize_t(idx); - // if (!py_idx) { - // Py_DECREF(list); - // Py_CLEAR(cache); - // break; - // } - - // if (PyList_Append(list, py_idx) < 0) { - // Py_DECREF(py_idx); - // Py_DECREF(list); - // Py_CLEAR(cache); - // break; - // } - - // Py_DECREF(py_idx); - // Py_DECREF(list); + Py_hash_t hash = _PyObject_HashFast(p->name_strobj); + if (hash == -1) { + Py_CLEAR(cache); + break; + } + + PyObject *list; + if (_PyDict_GetItemRef_KnownHash_LockHeld((PyDictObject *)cache, p->name_strobj, hash, &list) < 0) { + Py_CLEAR(cache); + break; + } + + if (!list) { + list = PyList_New(0); + if (!list) { + Py_CLEAR(cache); + break; + } + + if (_PyDict_SetItem_KnownHash_LockHeld((PyDictObject *)cache, p->name_strobj, list, hash) < 0) { + Py_DECREF(list); + Py_CLEAR(cache); + break; + } + } + + PyObject *py_idx = PyLong_FromSsize_t(idx); + if (!py_idx) { + Py_DECREF(list); + Py_CLEAR(cache); + break; + } + + if (PyList_Append(list, py_idx) < 0) { + Py_DECREF(py_idx); + Py_DECREF(list); + Py_CLEAR(cache); + break; + } + + Py_DECREF(py_idx); + Py_DECREF(list); } } if (cache) { - Py_ssize_t pos = 0; - PyObject* key=NULL; - PyObject* value=NULL; - while (PyDict_Next(cache, &pos, &key, &value)) { - assert(Py_REFCNT(key) > 1); - assert(Py_REFCNT(value) == 1); - } - - Py_XSETREF(slotdefs_cache, cache); - } else { + Py_XSETREF(interp->cached_objects.slotdefs_cache, cache); + } + else { PyErr_Clear(); } } diff --git a/Python/pystate.c b/Python/pystate.c index ee35f0fa945f8b..8b7583eea6b76f 100644 --- a/Python/pystate.c +++ b/Python/pystate.c @@ -699,6 +699,8 @@ init_interpreter(PyInterpreterState *interp, _Py_stackref_associate(interp, Py_True, PyStackRef_True); #endif + + interp->cached_objects.slotdefs_cache = NULL; interp->_initialized = 1; return _PyStatus_OK(); } @@ -829,6 +831,8 @@ interpreter_clear(PyInterpreterState *interp, PyThreadState *tstate) _PyErr_Clear(tstate); } + Py_CLEAR(interp->cached_objects.slotdefs_cache); + // Clear the current/main thread state last. _Py_FOR_EACH_TSTATE_BEGIN(interp, p) { // See https://github.com/python/cpython/issues/102126 From 8d1f5be0998ab6817b972a65385f78e04e992a1f Mon Sep 17 00:00:00 2001 From: Sergey Miryanov Date: Wed, 9 Apr 2025 00:43:22 +0500 Subject: [PATCH 05/25] Use bytes for slotdefs_cache --- Objects/typeobject.c | 63 ++++++++++++++++++++++---------------------- 1 file changed, 32 insertions(+), 31 deletions(-) diff --git a/Objects/typeobject.c b/Objects/typeobject.c index 228cbebb3f2ac6..6c9b93ed5332ec 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -11021,19 +11021,23 @@ resolve_slotdups(PyTypeObject *type, PyObject *name) int rc = 0; // Py_BEGIN_CRITICAL_SECTION(cache); - PyObject* list=NULL; - rc = PyDict_GetItemRef(cache, name, &list); + PyObject* bytes=NULL; + rc = PyDict_GetItemRef(cache, name, &bytes); if (rc > 0) { + assert(bytes); + assert(PyBytes_CheckExact(bytes)); + + uint8_t *data = (uint8_t *)PyBytes_AS_STRING(bytes); + uint8_t n = data[0]; - assert(list); - Py_ssize_t n = PyList_Size(list); assert(n >= 0); - Py_ssize_t i; + assert(n < MAX_EQUIV); + + uint8_t i; for(i = 0; i < n; i++) { - PyObject *py_idx = PyList_GET_ITEM(list, i); - assert(PyLong_Check(py_idx)); - Py_ssize_t idx = PyLong_AsSsize_t(py_idx); + uint8_t idx = data[i + 1]; assert (idx < Py_ARRAY_LENGTH(slotdefs)); + pytype_slotdef *x = &slotdefs[idx]; ptr = slotptr(type, x->offset); if (ptr == NULL || *ptr == NULL) { @@ -11045,8 +11049,7 @@ resolve_slotdups(PyTypeObject *type, PyObject *name) } res = ptr; } - assert(Py_REFCNT(list) > 1); - Py_DECREF(list); + Py_DECREF(bytes); } else if (rc < 0) { PyErr_Clear(); } @@ -11363,44 +11366,42 @@ fixup_slot_dispatchers(PyTypeObject *type) Py_CLEAR(cache); break; } + assert (idx < 255); - PyObject *list; - if (_PyDict_GetItemRef_KnownHash_LockHeld((PyDictObject *)cache, p->name_strobj, hash, &list) < 0) { + PyObject *bytes; + if (_PyDict_GetItemRef_KnownHash_LockHeld((PyDictObject *)cache, p->name_strobj, hash, &bytes) < 0) { Py_CLEAR(cache); break; } - if (!list) { - list = PyList_New(0); - if (!list) { + if (!bytes) { + bytes = PyBytes_FromStringAndSize(NULL, sizeof(uint8_t) * (1 + MAX_EQUIV)); + if (!bytes) { Py_CLEAR(cache); break; } - if (_PyDict_SetItem_KnownHash_LockHeld((PyDictObject *)cache, p->name_strobj, list, hash) < 0) { - Py_DECREF(list); + uint8_t *data = (uint8_t *)PyBytes_AS_STRING(bytes); + data[0] = 0; + + if (_PyDict_SetItem_KnownHash_LockHeld((PyDictObject *)cache, p->name_strobj, bytes, hash) < 0) { + Py_DECREF(bytes); Py_CLEAR(cache); break; } } - PyObject *py_idx = PyLong_FromSsize_t(idx); - if (!py_idx) { - Py_DECREF(list); - Py_CLEAR(cache); - break; - } + assert (PyBytes_CheckExact(bytes)); + uint8_t *data = (uint8_t *)PyBytes_AS_STRING(bytes); - if (PyList_Append(list, py_idx) < 0) { - Py_DECREF(py_idx); - Py_DECREF(list); - Py_CLEAR(cache); - break; - } + data[0] += 1; + assert (data[0] < MAX_EQUIV); + + data[data[0]] = (uint8_t)idx; - Py_DECREF(py_idx); - Py_DECREF(list); + Py_DECREF(bytes); } + } if (cache) { From 8cf19e8252700e91ce00470f7f8a3d63f65c2ccb Mon Sep 17 00:00:00 2001 From: Sergey Miryanov Date: Wed, 9 Apr 2025 01:08:41 +0500 Subject: [PATCH 06/25] Use type_slots_ptrs cache --- Objects/typeobject.c | 67 ++++++++++++++++++++++---------------------- 1 file changed, 34 insertions(+), 33 deletions(-) diff --git a/Objects/typeobject.c b/Objects/typeobject.c index 6c9b93ed5332ec..90b42a6039457c 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -11005,22 +11005,15 @@ 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) +static int +fill_type_slots_cache_from_slotdefs_cache(PyInterpreterState *interp, + PyObject *name) { - PyInterpreterState *interp = _PyInterpreterState_GET(); - - /* XXX Maybe this could be optimized more -- but is it worth it? */ - void **res, **ptr; - res = NULL; +#define ptrs _Py_INTERP_CACHED_OBJECT(interp, type_slots_ptrs) + int rc = 0; PyObject *cache = Py_XNewRef(interp->cached_objects.slotdefs_cache); if (cache) { - int rc = 0; - // Py_BEGIN_CRITICAL_SECTION(cache); - PyObject* bytes=NULL; rc = PyDict_GetItemRef(cache, name, &bytes); if (rc > 0) { @@ -11033,53 +11026,61 @@ resolve_slotdups(PyTypeObject *type, PyObject *name) assert(n >= 0); assert(n < MAX_EQUIV); - uint8_t i; - for(i = 0; i < n; i++) { + pytype_slotdef **pp = ptrs; + for(uint8_t i = 0; i < n; i++) { uint8_t idx = data[i + 1]; assert (idx < Py_ARRAY_LENGTH(slotdefs)); - pytype_slotdef *x = &slotdefs[idx]; - ptr = slotptr(type, x->offset); - if (ptr == NULL || *ptr == NULL) { - continue; - } - if (res != NULL) { - res = NULL; - break; - } - res = ptr; + *pp++ = &slotdefs[idx]; } + *pp = NULL; + Py_DECREF(bytes); } else if (rc < 0) { PyErr_Clear(); } Py_DECREF(cache); - // Py_END_CRITICAL_SECTION(); - if (rc > 0) { - return res; - } } + return rc; + +#undef ptrs +} + +/* 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? */ + /* 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; + void **res, **ptr; 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; + + int rc = fill_type_slots_cache_from_slotdefs_cache(interp, name); + if (rc <= 0) { + pp = ptrs; + for (p = slotdefs; p->name_strobj; p++) { + if (p->name_strobj == name) + *pp++ = p; + } + *pp = NULL; } - *pp = NULL; } /* 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. */ + res = NULL; for (pp = ptrs; *pp; pp++) { ptr = slotptr(type, (*pp)->offset); if (ptr == NULL || *ptr == NULL) From b0ad8754b45a79fe5b45063363384b9c236228d3 Mon Sep 17 00:00:00 2001 From: Sergey Miryanov Date: Wed, 9 Apr 2025 01:56:10 +0500 Subject: [PATCH 07/25] Move slotdefs_cache init to pycore_init_builtins --- Include/internal/pycore_typeobject.h | 2 + Objects/typeobject.c | 145 ++++++++++++++------------- Python/pylifecycle.c | 4 + 3 files changed, 79 insertions(+), 72 deletions(-) diff --git a/Include/internal/pycore_typeobject.h b/Include/internal/pycore_typeobject.h index 1a4f89fd2449a0..c9e9d9cb344160 100644 --- a/Include/internal/pycore_typeobject.h +++ b/Include/internal/pycore_typeobject.h @@ -149,6 +149,8 @@ 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); +extern int _PyType_InitSlotDefsCache(PyInterpreterState *interp); + #ifdef __cplusplus } #endif diff --git a/Objects/typeobject.c b/Objects/typeobject.c index 90b42a6039457c..b3351140e10162 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -11011,19 +11011,21 @@ fill_type_slots_cache_from_slotdefs_cache(PyInterpreterState *interp, { #define ptrs _Py_INTERP_CACHED_OBJECT(interp, type_slots_ptrs) - int rc = 0; - PyObject *cache = Py_XNewRef(interp->cached_objects.slotdefs_cache); + PyObject *cache = _Py_INTERP_CACHED_OBJECT(interp, slotdefs_cache); if (cache) { - PyObject* bytes=NULL; - rc = PyDict_GetItemRef(cache, name, &bytes); - if (rc > 0) { - assert(bytes); + + PyObject* bytes = PyDict_GetItemWithError(cache, name); + if (PyErr_Occurred()) { + PyErr_Clear(); + return -1; + } + + if (bytes != NULL) { assert(PyBytes_CheckExact(bytes)); uint8_t *data = (uint8_t *)PyBytes_AS_STRING(bytes); uint8_t n = data[0]; - assert(n >= 0); assert(n < MAX_EQUIV); pytype_slotdef **pp = ptrs; @@ -11035,15 +11037,11 @@ fill_type_slots_cache_from_slotdefs_cache(PyInterpreterState *interp, } *pp = NULL; - Py_DECREF(bytes); - } else if (rc < 0) { - PyErr_Clear(); + return 1; } - - Py_DECREF(cache); } - return rc; + return 0; #undef ptrs } @@ -11354,65 +11352,6 @@ fixup_slot_dispatchers(PyTypeObject *type) } } - PyInterpreterState *interp = _PyInterpreterState_GET(); - if (!interp->cached_objects.slotdefs_cache) { - - PyObject* cache = PyDict_New(); - if (cache) { - pytype_slotdef *p; - Py_ssize_t idx = 0; - for (p = slotdefs; p->name_strobj; p++, idx++) { - Py_hash_t hash = _PyObject_HashFast(p->name_strobj); - if (hash == -1) { - Py_CLEAR(cache); - break; - } - assert (idx < 255); - - PyObject *bytes; - if (_PyDict_GetItemRef_KnownHash_LockHeld((PyDictObject *)cache, p->name_strobj, hash, &bytes) < 0) { - Py_CLEAR(cache); - break; - } - - if (!bytes) { - bytes = PyBytes_FromStringAndSize(NULL, sizeof(uint8_t) * (1 + MAX_EQUIV)); - if (!bytes) { - Py_CLEAR(cache); - break; - } - - uint8_t *data = (uint8_t *)PyBytes_AS_STRING(bytes); - data[0] = 0; - - if (_PyDict_SetItem_KnownHash_LockHeld((PyDictObject *)cache, p->name_strobj, bytes, hash) < 0) { - Py_DECREF(bytes); - Py_CLEAR(cache); - break; - } - } - - assert (PyBytes_CheckExact(bytes)); - uint8_t *data = (uint8_t *)PyBytes_AS_STRING(bytes); - - data[0] += 1; - assert (data[0] < MAX_EQUIV); - - data[data[0]] = (uint8_t)idx; - - Py_DECREF(bytes); - } - - } - - if (cache) { - Py_XSETREF(interp->cached_objects.slotdefs_cache, cache); - } - else { - PyErr_Clear(); - } - } - assert(!PyErr_Occurred()); for (pytype_slotdef *p = slotdefs; p->name; ) { p = update_one_slot(type, p, mro_dict); @@ -11439,6 +11378,68 @@ update_all_slots(PyTypeObject* type) } } +int +_PyType_InitSlotDefsCache(PyInterpreterState *interp) +{ + assert (!_Py_INTERP_CACHED_OBJECT(interp, slotdefs_cache)); + + PyObject *bytes = NULL; + PyObject* cache = PyDict_New(); + if (!cache) { + goto error; + } + pytype_slotdef *p; + Py_ssize_t idx = 0; + for (p = slotdefs; p->name_strobj; p++, idx++) { + Py_hash_t hash = _PyObject_HashFast(p->name_strobj); + if (hash == -1) { + goto error; + } + assert (idx < 255); + + if (_PyDict_GetItemRef_KnownHash_LockHeld((PyDictObject *)cache, + p->name_strobj, hash, + &bytes) < 0) { + goto error; + } + + if (!bytes) { + Py_ssize_t size = sizeof(uint8_t) * (1 + MAX_EQUIV); + bytes = PyBytes_FromStringAndSize(NULL, size); + if (!bytes) { + goto error; + } + + uint8_t *data = (uint8_t *)PyBytes_AS_STRING(bytes); + data[0] = 0; + + if (_PyDict_SetItem_KnownHash_LockHeld((PyDictObject *)cache, + p->name_strobj, + bytes, hash) < 0) { + goto error; + } + } + + assert (PyBytes_CheckExact(bytes)); + uint8_t *data = (uint8_t *)PyBytes_AS_STRING(bytes); + + data[0] += 1; + assert (data[0] < MAX_EQUIV); + + data[data[0]] = (uint8_t)idx; + + Py_CLEAR(bytes); + } + + Py_XSETREF(_Py_INTERP_CACHED_OBJECT(interp, slotdefs_cache), cache); + return 0; + +error: + Py_XDECREF(bytes); + Py_XDECREF(cache); + return -1; +} + PyObject * _PyType_GetSlotWrapperNames(void) diff --git a/Python/pylifecycle.c b/Python/pylifecycle.c index 934614e73b56f9..138136023484ee 100644 --- a/Python/pylifecycle.c +++ b/Python/pylifecycle.c @@ -822,6 +822,10 @@ pycore_init_builtins(PyThreadState *tstate) } interp->callable_cache.object__getattribute__ = object__getattribute__; + if (_PyType_InitSlotDefsCache(interp) < 0) { + return _PyStatus_ERR("failed to init slotdefs cache"); + } + if (_PyBuiltins_AddExceptions(bimod) < 0) { return _PyStatus_ERR("failed to add exceptions to builtins"); } From 79a165d4a6998f33e29c908b424ccb862fd191eb Mon Sep 17 00:00:00 2001 From: Sergey Miryanov Date: Thu, 10 Apr 2025 00:22:57 +0500 Subject: [PATCH 08/25] Create slotdefs_cache only for main interpreter --- Objects/typeobject.c | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/Objects/typeobject.c b/Objects/typeobject.c index b3351140e10162..d2fe08ff9b2e7b 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -11381,10 +11381,18 @@ update_all_slots(PyTypeObject* type) int _PyType_InitSlotDefsCache(PyInterpreterState *interp) { + PyObject *cache; + PyObject *bytes = NULL; + assert (!_Py_INTERP_CACHED_OBJECT(interp, slotdefs_cache)); + PyInterpreterState *main = interp->runtime->interpreters.main; + if (interp != main) { + cache = _Py_INTERP_CACHED_OBJECT(main, slotdefs_cache); + _Py_INTERP_CACHED_OBJECT(interp, slotdefs_cache) = Py_NewRef(cache); + return 0; + } - PyObject *bytes = NULL; - PyObject* cache = PyDict_New(); + cache = PyDict_New(); if (!cache) { goto error; } @@ -11431,7 +11439,7 @@ _PyType_InitSlotDefsCache(PyInterpreterState *interp) Py_CLEAR(bytes); } - Py_XSETREF(_Py_INTERP_CACHED_OBJECT(interp, slotdefs_cache), cache); + _Py_INTERP_CACHED_OBJECT(interp, slotdefs_cache) = cache; return 0; error: From a85329498afcf69111f9ecbc27b34f6788ca300b Mon Sep 17 00:00:00 2001 From: Sergey Miryanov Date: Thu, 10 Apr 2025 00:52:06 +0500 Subject: [PATCH 09/25] Do not iterate slotdefs_cache just check dups count --- Objects/typeobject.c | 91 ++++++++------------------------------------ 1 file changed, 16 insertions(+), 75 deletions(-) diff --git a/Objects/typeobject.c b/Objects/typeobject.c index d2fe08ff9b2e7b..00714394cb3e42 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -11005,47 +11005,6 @@ slotptr(PyTypeObject *type, int ioffset) return (void **)ptr; } -static int -fill_type_slots_cache_from_slotdefs_cache(PyInterpreterState *interp, - PyObject *name) -{ -#define ptrs _Py_INTERP_CACHED_OBJECT(interp, type_slots_ptrs) - - PyObject *cache = _Py_INTERP_CACHED_OBJECT(interp, slotdefs_cache); - if (cache) { - - PyObject* bytes = PyDict_GetItemWithError(cache, name); - if (PyErr_Occurred()) { - PyErr_Clear(); - return -1; - } - - if (bytes != NULL) { - assert(PyBytes_CheckExact(bytes)); - - uint8_t *data = (uint8_t *)PyBytes_AS_STRING(bytes); - uint8_t n = data[0]; - - assert(n < MAX_EQUIV); - - pytype_slotdef **pp = ptrs; - for(uint8_t i = 0; i < n; i++) { - uint8_t idx = data[i + 1]; - assert (idx < Py_ARRAY_LENGTH(slotdefs)); - - *pp++ = &slotdefs[idx]; - } - *pp = NULL; - - return 1; - } - } - - return 0; - -#undef ptrs -} - /* 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 ** @@ -11053,43 +11012,25 @@ resolve_slotdups(PyTypeObject *type, PyObject *name) { /* XXX Maybe this could be optimized more -- but is it worth it? */ - /* 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; - void **res, **ptr; - - if (pname != name) { - /* Collect all slotdefs that match name into ptrs. */ - pname = name; - - int rc = fill_type_slots_cache_from_slotdefs_cache(interp, name); - if (rc <= 0) { - pp = ptrs; - for (p = slotdefs; p->name_strobj; p++) { - if (p->name_strobj == name) - *pp++ = p; - } - *pp = NULL; - } - } + PyObject *cache = _Py_INTERP_CACHED_OBJECT(interp, slotdefs_cache); + assert(cache); - /* 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. */ - 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; + PyObject* bytes = PyDict_GetItemWithError(cache, name); + assert(!PyErr_Occurred()); + assert(bytes); + assert(PyBytes_CheckExact(bytes)); + + uint8_t *data = (uint8_t *)PyBytes_AS_STRING(bytes); + uint8_t n = data[0]; + + assert(n < MAX_EQUIV); + if (n == 1) { + pytype_slotdef *p = &slotdefs[data[1]]; + return slotptr(type, p->offset); } - return res; -#undef pname -#undef ptrs + + return NULL; } From 75c17fb087acb7585de89dc41d0948566d2f1ba8 Mon Sep 17 00:00:00 2001 From: Sergey Miryanov Date: Thu, 10 Apr 2025 01:20:43 +0500 Subject: [PATCH 10/25] Add name_count to pytype_slotdef and get rid of slotdefs_cache and resolve_slotdups --- Include/cpython/descrobject.h | 1 + Include/internal/pycore_interp_structs.h | 1 - Objects/typeobject.c | 66 +++++++++--------------- Python/pystate.c | 3 -- 4 files changed, 25 insertions(+), 46 deletions(-) diff --git a/Include/cpython/descrobject.h b/Include/cpython/descrobject.h index bbad8b59c225ab..84cf5f9f88d19e 100644 --- a/Include/cpython/descrobject.h +++ b/Include/cpython/descrobject.h @@ -16,6 +16,7 @@ struct wrapperbase { const char *doc; int flags; PyObject *name_strobj; + uint8_t name_count; }; /* Flags for above struct */ diff --git a/Include/internal/pycore_interp_structs.h b/Include/internal/pycore_interp_structs.h index 92367e2620829c..44a4231fbc5e30 100644 --- a/Include/internal/pycore_interp_structs.h +++ b/Include/internal/pycore_interp_structs.h @@ -677,7 +677,6 @@ struct _Py_interp_cached_objects { PyTypeObject *paramspecargs_type; PyTypeObject *paramspeckwargs_type; PyTypeObject *constevaluator_type; - PyObject *slotdefs_cache; }; struct _Py_interp_static_objects { diff --git a/Objects/typeobject.c b/Objects/typeobject.c index 00714394cb3e42..6e6034ac087e09 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -11005,34 +11005,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? */ - - PyInterpreterState *interp = _PyInterpreterState_GET(); - PyObject *cache = _Py_INTERP_CACHED_OBJECT(interp, slotdefs_cache); - assert(cache); - - PyObject* bytes = PyDict_GetItemWithError(cache, name); - assert(!PyErr_Occurred()); - assert(bytes); - assert(PyBytes_CheckExact(bytes)); - - uint8_t *data = (uint8_t *)PyBytes_AS_STRING(bytes); - uint8_t n = data[0]; - - assert(n < MAX_EQUIV); - if (n == 1) { - pytype_slotdef *p = &slotdefs[data[1]]; - return slotptr(type, p->offset); - } - - return NULL; -} - /* Common code for update_slots_callback() and fixup_slot_dispatchers(). * @@ -11139,7 +11111,10 @@ update_one_slot(PyTypeObject *type, pytype_slotdef *p, PyObject *mro_dict) } 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 (p->name_count == 1) + tptr = slotptr(type, p->offset); + if (tptr == NULL || tptr == ptr) generic = p->function; d = (PyWrapperDescrObject *)descr; @@ -11322,21 +11297,15 @@ update_all_slots(PyTypeObject* type) int _PyType_InitSlotDefsCache(PyInterpreterState *interp) { - PyObject *cache; - PyObject *bytes = NULL; - - assert (!_Py_INTERP_CACHED_OBJECT(interp, slotdefs_cache)); - PyInterpreterState *main = interp->runtime->interpreters.main; - if (interp != main) { - cache = _Py_INTERP_CACHED_OBJECT(main, slotdefs_cache); - _Py_INTERP_CACHED_OBJECT(interp, slotdefs_cache) = Py_NewRef(cache); + if (interp != interp->runtime->interpreters.main) { return 0; } - - cache = PyDict_New(); + PyObject *bytes = NULL; + PyObject *cache = PyDict_New(); if (!cache) { - goto error; + return -1; } + pytype_slotdef *p; Py_ssize_t idx = 0; for (p = slotdefs; p->name_strobj; p++, idx++) { @@ -11380,12 +11349,25 @@ _PyType_InitSlotDefsCache(PyInterpreterState *interp) Py_CLEAR(bytes); } - _Py_INTERP_CACHED_OBJECT(interp, slotdefs_cache) = cache; + Py_ssize_t pos=0; + PyObject *key=NULL; + PyObject *value=NULL; + while (PyDict_Next(cache, &pos, &key, &value)) { + uint8_t *data = (uint8_t *)PyBytes_AS_STRING(value); + uint8_t n = data[0]; + uint8_t i = 0; + for(; i < n; i++) { + uint8_t idx = data[i + 1]; + slotdefs[idx].name_count = n; + } + } + + Py_DECREF(cache); return 0; error: Py_XDECREF(bytes); - Py_XDECREF(cache); + Py_DECREF(cache); return -1; } diff --git a/Python/pystate.c b/Python/pystate.c index 8b7583eea6b76f..ba81a33e47c9ba 100644 --- a/Python/pystate.c +++ b/Python/pystate.c @@ -700,7 +700,6 @@ init_interpreter(PyInterpreterState *interp, #endif - interp->cached_objects.slotdefs_cache = NULL; interp->_initialized = 1; return _PyStatus_OK(); } @@ -831,8 +830,6 @@ interpreter_clear(PyInterpreterState *interp, PyThreadState *tstate) _PyErr_Clear(tstate); } - Py_CLEAR(interp->cached_objects.slotdefs_cache); - // Clear the current/main thread state last. _Py_FOR_EACH_TSTATE_BEGIN(interp, p) { // See https://github.com/python/cpython/issues/102126 From fc17a682affe7bd679cededc71a915698bbb6437 Mon Sep 17 00:00:00 2001 From: Sergey Miryanov Date: Thu, 10 Apr 2025 01:36:37 +0500 Subject: [PATCH 11/25] Rename _PyType_InitSlotDefsCache to _PyType_InitSlotDefsNameCounts --- Include/internal/pycore_typeobject.h | 3 ++- Objects/typeobject.c | 2 +- Python/pylifecycle.c | 4 ++-- Python/pystate.c | 1 - 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Include/internal/pycore_typeobject.h b/Include/internal/pycore_typeobject.h index c9e9d9cb344160..cd15938d1d9215 100644 --- a/Include/internal/pycore_typeobject.h +++ b/Include/internal/pycore_typeobject.h @@ -149,7 +149,8 @@ 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); -extern int _PyType_InitSlotDefsCache(PyInterpreterState *interp); +// Precalculates count of non-unique slots +extern int _PyType_InitSlotDefsNameCounts(PyInterpreterState *interp); #ifdef __cplusplus } diff --git a/Objects/typeobject.c b/Objects/typeobject.c index 6e6034ac087e09..7daa8dc0a7600f 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -11295,7 +11295,7 @@ update_all_slots(PyTypeObject* type) } int -_PyType_InitSlotDefsCache(PyInterpreterState *interp) +_PyType_InitSlotDefsNameCounts(PyInterpreterState *interp) { if (interp != interp->runtime->interpreters.main) { return 0; diff --git a/Python/pylifecycle.c b/Python/pylifecycle.c index 138136023484ee..bf4eda4ff21ecd 100644 --- a/Python/pylifecycle.c +++ b/Python/pylifecycle.c @@ -822,8 +822,8 @@ pycore_init_builtins(PyThreadState *tstate) } interp->callable_cache.object__getattribute__ = object__getattribute__; - if (_PyType_InitSlotDefsCache(interp) < 0) { - return _PyStatus_ERR("failed to init slotdefs cache"); + if (_PyType_InitSlotDefsNameCounts(interp) < 0) { + return _PyStatus_ERR("failed to precalculate slotdefs unique names"); } if (_PyBuiltins_AddExceptions(bimod) < 0) { diff --git a/Python/pystate.c b/Python/pystate.c index ba81a33e47c9ba..ee35f0fa945f8b 100644 --- a/Python/pystate.c +++ b/Python/pystate.c @@ -699,7 +699,6 @@ init_interpreter(PyInterpreterState *interp, _Py_stackref_associate(interp, Py_True, PyStackRef_True); #endif - interp->_initialized = 1; return _PyStatus_OK(); } From 736bca474d948d86ad9515849e3f6e82ff1a3556 Mon Sep 17 00:00:00 2001 From: Sergey Miryanov Date: Thu, 10 Apr 2025 01:46:51 +0500 Subject: [PATCH 12/25] Get rid of type_slots_ptrs and type_slots_pname from _Py_interp_cached_objects --- Include/internal/pycore_interp_structs.h | 2 -- 1 file changed, 2 deletions(-) diff --git a/Include/internal/pycore_interp_structs.h b/Include/internal/pycore_interp_structs.h index 44a4231fbc5e30..1620cb9d8a03ef 100644 --- a/Include/internal/pycore_interp_structs.h +++ b/Include/internal/pycore_interp_structs.h @@ -666,8 +666,6 @@ struct _Py_interp_cached_objects { /* object.__reduce__ */ PyObject *objreduce; - PyObject *type_slots_pname; - pytype_slotdef *type_slots_ptrs[MAX_EQUIV]; /* TypeVar and related types */ PyTypeObject *generic_type; From 1341ed98940310469243255190b1226e41b48115 Mon Sep 17 00:00:00 2001 From: Sergey Miryanov Date: Thu, 10 Apr 2025 01:52:59 +0500 Subject: [PATCH 13/25] Add news entry --- .../2025-04-10-01-52-42.gh-issue-132042.fePwlj.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 Misc/NEWS.d/next/Core_and_Builtins/2025-04-10-01-52-42.gh-issue-132042.fePwlj.rst 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..e853fbaebf7f69 --- /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%. Patch by Sergey Miryanov. From 3efb9cac7c74db3f7aff954b2999d9fdb26a4196 Mon Sep 17 00:00:00 2001 From: Sergey Miryanov Date: Wed, 16 Apr 2025 12:04:32 -0700 Subject: [PATCH 14/25] Rename _PyType_InitSlotDefsNameCounts Co-authored-by: Victor Stinner --- Objects/typeobject.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Objects/typeobject.c b/Objects/typeobject.c index 7daa8dc0a7600f..ba8e492a0d5c9a 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -11295,7 +11295,7 @@ update_all_slots(PyTypeObject* type) } int -_PyType_InitSlotDefsNameCounts(PyInterpreterState *interp) +_PyType_InitSlotDefs(PyInterpreterState *interp) { if (interp != interp->runtime->interpreters.main) { return 0; From a792e9dee3f4ed338a76719904ef72b3991a2328 Mon Sep 17 00:00:00 2001 From: Sergey Miryanov Date: Thu, 17 Apr 2025 00:52:49 +0500 Subject: [PATCH 15/25] Use --- Include/internal/pycore_typeobject.h | 2 +- Python/pylifecycle.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Include/internal/pycore_typeobject.h b/Include/internal/pycore_typeobject.h index cd15938d1d9215..1e5e5228b6f3ae 100644 --- a/Include/internal/pycore_typeobject.h +++ b/Include/internal/pycore_typeobject.h @@ -150,7 +150,7 @@ extern int _PyType_Validate(PyTypeObject *ty, _py_validate_type validate, unsign extern int _PyType_CacheGetItemForSpecialization(PyHeapTypeObject *ht, PyObject *descriptor, uint32_t tp_version); // Precalculates count of non-unique slots -extern int _PyType_InitSlotDefsNameCounts(PyInterpreterState *interp); +extern int _PyType_InitSlotDefs(PyInterpreterState *interp); #ifdef __cplusplus } diff --git a/Python/pylifecycle.c b/Python/pylifecycle.c index bf4eda4ff21ecd..26d6c10330ba0b 100644 --- a/Python/pylifecycle.c +++ b/Python/pylifecycle.c @@ -822,7 +822,7 @@ pycore_init_builtins(PyThreadState *tstate) } interp->callable_cache.object__getattribute__ = object__getattribute__; - if (_PyType_InitSlotDefsNameCounts(interp) < 0) { + if (_PyType_InitSlotDefs(interp) < 0) { return _PyStatus_ERR("failed to precalculate slotdefs unique names"); } From b6fafa9ce429f4835f76ed5b23a6ee3a76d1b602 Mon Sep 17 00:00:00 2001 From: Sergey Miryanov Date: Thu, 17 Apr 2025 00:54:05 +0500 Subject: [PATCH 16/25] Revert "Preget tp_dict" This reverts commit 1eed75d6de7efa316241126854337565f4e82aa0. --- Objects/typeobject.c | 40 ++++++++++++++++++++++------------------ 1 file changed, 22 insertions(+), 18 deletions(-) diff --git a/Objects/typeobject.c b/Objects/typeobject.c index ba8e492a0d5c9a..4d31ea6bf5b26a 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -4144,8 +4144,9 @@ type_new_set_name(const type_new_ctx *ctx, PyTypeObject *type) /* Set __module__ in the dict */ static int -type_new_set_module(PyObject *dict) +type_new_set_module(PyTypeObject *type) { + PyObject *dict = lookup_tp_dict(type); int r = PyDict_Contains(dict, &_Py_ID(__module__)); if (r < 0) { return -1; @@ -4172,9 +4173,10 @@ type_new_set_module(PyObject *dict) /* Set ht_qualname to dict['__qualname__'] if available, else to __name__. The __qualname__ accessor will look for ht_qualname. */ static int -type_new_set_ht_name(PyTypeObject *type, PyObject *dict) +type_new_set_ht_name(PyTypeObject *type) { PyHeapTypeObject *et = (PyHeapTypeObject *)type; + PyObject *dict = lookup_tp_dict(type); PyObject *qualname; if (PyDict_GetItemRef(dict, &_Py_ID(__qualname__), &qualname) < 0) { return -1; @@ -4203,8 +4205,9 @@ type_new_set_ht_name(PyTypeObject *type, PyObject *dict) and is a string. The __doc__ accessor will first look for tp_doc; if that fails, it will still look into __dict__. */ static int -type_new_set_doc(PyTypeObject *type, PyObject* dict) +type_new_set_doc(PyTypeObject *type) { + PyObject *dict = lookup_tp_dict(type); PyObject *doc = PyDict_GetItemWithError(dict, &_Py_ID(__doc__)); if (doc == NULL) { if (PyErr_Occurred()) { @@ -4238,8 +4241,9 @@ type_new_set_doc(PyTypeObject *type, PyObject* dict) static int -type_new_staticmethod(PyObject *dict, PyObject *attr) +type_new_staticmethod(PyTypeObject *type, PyObject *attr) { + PyObject *dict = lookup_tp_dict(type); PyObject *func = PyDict_GetItemWithError(dict, attr); if (func == NULL) { if (PyErr_Occurred()) { @@ -4265,8 +4269,9 @@ type_new_staticmethod(PyObject *dict, PyObject *attr) static int -type_new_classmethod(PyObject *dict, PyObject *attr) +type_new_classmethod(PyTypeObject *type, PyObject *attr) { + PyObject *dict = lookup_tp_dict(type); PyObject *func = PyDict_GetItemWithError(dict, attr); if (func == NULL) { if (PyErr_Occurred()) { @@ -4367,8 +4372,9 @@ type_new_set_slots(const type_new_ctx *ctx, PyTypeObject *type) /* store type in class' cell if one is supplied */ static int -type_new_set_classcell(PyTypeObject *type, PyObject *dict) +type_new_set_classcell(PyTypeObject *type) { + PyObject *dict = lookup_tp_dict(type); PyObject *cell = PyDict_GetItemWithError(dict, &_Py_ID(__classcell__)); if (cell == NULL) { if (PyErr_Occurred()) { @@ -4393,8 +4399,9 @@ type_new_set_classcell(PyTypeObject *type, PyObject *dict) } static int -type_new_set_classdictcell(PyObject *dict) +type_new_set_classdictcell(PyTypeObject *type) { + PyObject *dict = lookup_tp_dict(type); PyObject *cell = PyDict_GetItemWithError(dict, &_Py_ID(__classdictcell__)); if (cell == NULL) { if (PyErr_Occurred()) { @@ -4425,33 +4432,30 @@ type_new_set_attrs(const type_new_ctx *ctx, PyTypeObject *type) return -1; } - PyObject *dict = lookup_tp_dict(type); - assert(dict); - - if (type_new_set_module(dict) < 0) { + if (type_new_set_module(type) < 0) { return -1; } - if (type_new_set_ht_name(type, dict) < 0) { + if (type_new_set_ht_name(type) < 0) { return -1; } - if (type_new_set_doc(type, dict) < 0) { + if (type_new_set_doc(type) < 0) { return -1; } /* Special-case __new__: if it's a plain function, make it a static function */ - if (type_new_staticmethod(dict, &_Py_ID(__new__)) < 0) { + if (type_new_staticmethod(type, &_Py_ID(__new__)) < 0) { return -1; } /* Special-case __init_subclass__ and __class_getitem__: if they are plain functions, make them classmethods */ - if (type_new_classmethod(dict, &_Py_ID(__init_subclass__)) < 0) { + if (type_new_classmethod(type, &_Py_ID(__init_subclass__)) < 0) { return -1; } - if (type_new_classmethod(dict, &_Py_ID(__class_getitem__)) < 0) { + if (type_new_classmethod(type, &_Py_ID(__class_getitem__)) < 0) { return -1; } @@ -4461,10 +4465,10 @@ type_new_set_attrs(const type_new_ctx *ctx, PyTypeObject *type) type_new_set_slots(ctx, type); - if (type_new_set_classcell(type, dict) < 0) { + if (type_new_set_classcell(type) < 0) { return -1; } - if (type_new_set_classdictcell(dict) < 0) { + if (type_new_set_classdictcell(type) < 0) { return -1; } return 0; From 1459c1649dec6b9efd6a607ba06fa7b1b7cfc893 Mon Sep 17 00:00:00 2001 From: Sergey Miryanov Date: Thu, 17 Apr 2025 00:54:43 +0500 Subject: [PATCH 17/25] Revert "Prebuild mro_dict for find_name_in_mro" This reverts commit 56d13fcf5259c6fc9fd0fe4d0fa2d73730dc5918. --- Objects/typeobject.c | 59 +++----------------------------------------- 1 file changed, 4 insertions(+), 55 deletions(-) diff --git a/Objects/typeobject.c b/Objects/typeobject.c index 4d31ea6bf5b26a..4cb5f0404bcf98 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -5636,26 +5636,6 @@ find_name_in_mro(PyTypeObject *type, PyObject *name, int *error) return res; } -static PyObject * -find_name_in_mro_new(PyObject *mro_dict, PyObject *name, int *error) -{ - ASSERT_TYPE_LOCK_HELD(); - - Py_hash_t hash = _PyObject_HashFast(name); - if (hash == -1) { - *error = -1; - return NULL; - } - - PyObject *res = NULL; - if (_PyDict_GetItemRef_KnownHash((PyDictObject *)mro_dict, name, hash, &res) < 0) { - *error = -1; - } else { - *error = 0; - } - return res; -} - /* Check if the "readied" PyUnicode name is a double-underscore special name. */ static int @@ -11065,7 +11045,7 @@ slotptr(PyTypeObject *type, int ioffset) * because that's convenient for fixup_slot_dispatchers(). This function never * sets an exception: if an internal error happens (unlikely), it's ignored. */ static pytype_slotdef * -update_one_slot(PyTypeObject *type, pytype_slotdef *p, PyObject *mro_dict) +update_one_slot(PyTypeObject *type, pytype_slotdef *p) { ASSERT_TYPE_LOCK_HELD(); @@ -11096,11 +11076,7 @@ update_one_slot(PyTypeObject *type, pytype_slotdef *p, PyObject *mro_dict) assert(!PyErr_Occurred()); do { /* Use faster uncached lookup as we won't get any cache hits during type setup. */ - if (mro_dict == NULL) { - descr = find_name_in_mro(type, p->name_strobj, &error); - } else { - descr = find_name_in_mro_new(mro_dict, p->name_strobj, &error); - } + descr = find_name_in_mro(type, p->name_strobj, &error); if (descr == NULL) { if (error == -1) { /* It is unlikely but not impossible that there has been an exception @@ -11194,7 +11170,7 @@ update_slots_callback(PyTypeObject *type, void *data) pytype_slotdef **pp = (pytype_slotdef **)data; for (; *pp; pp++) { - update_one_slot(type, *pp, NULL); + update_one_slot(type, *pp); } return 0; } @@ -11247,38 +11223,11 @@ fixup_slot_dispatchers(PyTypeObject *type) // where we'd like to assert that the type is locked. BEGIN_TYPE_LOCK(); - PyObject *mro = lookup_tp_mro(type); - assert(mro); - - PyObject *mro_dict = NULL; - Py_ssize_t n = PyTuple_GET_SIZE(mro); - for (Py_ssize_t i = 0; i < n; i++) { - PyObject *base = PyTuple_GET_ITEM(mro, n-i-1); - PyObject *dict = lookup_tp_dict(_PyType_CAST(base)); - assert(dict && PyDict_Check(dict)); - - if (i == 0) { - mro_dict = PyDict_Copy(dict); - if (!mro_dict) { - PyErr_Clear(); - break; - } - } else { - if (PyDict_Merge(mro_dict, dict, 1) < 0) { - Py_CLEAR(mro_dict); - PyErr_Clear(); - break; - } - } - } - assert(!PyErr_Occurred()); for (pytype_slotdef *p = slotdefs; p->name; ) { - p = update_one_slot(type, p, mro_dict); + p = update_one_slot(type, p); } - Py_XDECREF(mro_dict); - END_TYPE_LOCK(); } From 08740af47997e0b0ff75f89ef494cd56c823c7b8 Mon Sep 17 00:00:00 2001 From: Sergey Miryanov Date: Thu, 17 Apr 2025 02:21:09 +0500 Subject: [PATCH 18/25] Update _PyType_InitSlotDefs and add comment for wrapperbase.name_count --- Include/cpython/descrobject.h | 5 +++++ Include/internal/pycore_typeobject.h | 2 +- Objects/typeobject.c | 32 +++++++++++----------------- 3 files changed, 18 insertions(+), 21 deletions(-) diff --git a/Include/cpython/descrobject.h b/Include/cpython/descrobject.h index 84cf5f9f88d19e..17b4686fc34307 100644 --- a/Include/cpython/descrobject.h +++ b/Include/cpython/descrobject.h @@ -16,6 +16,11 @@ struct wrapperbase { const char *doc; int flags; PyObject *name_strobj; + + /* Stores the number of times where slotdefs has elements with this name. + This counter precalculated by _PyType_InitSlotDefs when main + interprepter starts. + */ uint8_t name_count; }; diff --git a/Include/internal/pycore_typeobject.h b/Include/internal/pycore_typeobject.h index 1e5e5228b6f3ae..df9b8c9944fbe9 100644 --- a/Include/internal/pycore_typeobject.h +++ b/Include/internal/pycore_typeobject.h @@ -149,7 +149,7 @@ 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 +// Precalculates count of non-unique slots and fills wrapperbase::name_count. extern int _PyType_InitSlotDefs(PyInterpreterState *interp); #ifdef __cplusplus diff --git a/Objects/typeobject.c b/Objects/typeobject.c index 4cb5f0404bcf98..5ca369ccb790ba 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -11253,7 +11253,7 @@ _PyType_InitSlotDefs(PyInterpreterState *interp) if (interp != interp->runtime->interpreters.main) { return 0; } - PyObject *bytes = NULL; + PyObject *bytearray = NULL; PyObject *cache = PyDict_New(); if (!cache) { return -1; @@ -11262,51 +11262,43 @@ _PyType_InitSlotDefs(PyInterpreterState *interp) pytype_slotdef *p; Py_ssize_t idx = 0; for (p = slotdefs; p->name_strobj; p++, idx++) { - Py_hash_t hash = _PyObject_HashFast(p->name_strobj); - if (hash == -1) { - goto error; - } assert (idx < 255); - if (_PyDict_GetItemRef_KnownHash_LockHeld((PyDictObject *)cache, - p->name_strobj, hash, - &bytes) < 0) { + if (PyDict_GetItemRef(cache, p->name_strobj, &bytearray) < 0) { goto error; } - if (!bytes) { + if (!bytearray) { Py_ssize_t size = sizeof(uint8_t) * (1 + MAX_EQUIV); - bytes = PyBytes_FromStringAndSize(NULL, size); - if (!bytes) { + bytearray = PyByteArray_FromStringAndSize(NULL, size); + if (!bytearray) { goto error; } - uint8_t *data = (uint8_t *)PyBytes_AS_STRING(bytes); + uint8_t *data = (uint8_t *)PyByteArray_AS_STRING(bytearray); data[0] = 0; - if (_PyDict_SetItem_KnownHash_LockHeld((PyDictObject *)cache, - p->name_strobj, - bytes, hash) < 0) { + if (PyDict_SetItem(cache, p->name_strobj, bytearray) < 0) { goto error; } } - assert (PyBytes_CheckExact(bytes)); - uint8_t *data = (uint8_t *)PyBytes_AS_STRING(bytes); + 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(bytes); + Py_CLEAR(bytearray); } Py_ssize_t pos=0; PyObject *key=NULL; PyObject *value=NULL; while (PyDict_Next(cache, &pos, &key, &value)) { - uint8_t *data = (uint8_t *)PyBytes_AS_STRING(value); + uint8_t *data = (uint8_t *)PyByteArray_AS_STRING(value); uint8_t n = data[0]; uint8_t i = 0; for(; i < n; i++) { @@ -11319,7 +11311,7 @@ _PyType_InitSlotDefs(PyInterpreterState *interp) return 0; error: - Py_XDECREF(bytes); + Py_XDECREF(bytearray); Py_DECREF(cache); return -1; } From 0370d59c3faf5a2031472e25272b2d5a5bf39f7c Mon Sep 17 00:00:00 2001 From: Sergey Miryanov Date: Thu, 17 Apr 2025 02:40:06 +0500 Subject: [PATCH 19/25] Fix error message if _PyType_InitSlotDefs fails --- Python/pylifecycle.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Python/pylifecycle.c b/Python/pylifecycle.c index 26d6c10330ba0b..1afa62c1e5ea5e 100644 --- a/Python/pylifecycle.c +++ b/Python/pylifecycle.c @@ -823,7 +823,7 @@ pycore_init_builtins(PyThreadState *tstate) interp->callable_cache.object__getattribute__ = object__getattribute__; if (_PyType_InitSlotDefs(interp) < 0) { - return _PyStatus_ERR("failed to precalculate slotdefs unique names"); + return _PyStatus_ERR("failed to init slotdefs"); } if (_PyBuiltins_AddExceptions(bimod) < 0) { From 17d0265d987ef044de73bb520aacf069b922ca14 Mon Sep 17 00:00:00 2001 From: Sergey Miryanov Date: Wed, 23 Apr 2025 00:42:32 +0500 Subject: [PATCH 20/25] Use slotdefs_name_counts to check name duplicates --- Include/cpython/descrobject.h | 6 ------ Objects/typeobject.c | 10 ++++++++-- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/Include/cpython/descrobject.h b/Include/cpython/descrobject.h index 17b4686fc34307..bbad8b59c225ab 100644 --- a/Include/cpython/descrobject.h +++ b/Include/cpython/descrobject.h @@ -16,12 +16,6 @@ struct wrapperbase { const char *doc; int flags; PyObject *name_strobj; - - /* Stores the number of times where slotdefs has elements with this name. - This counter precalculated by _PyType_InitSlotDefs when main - interprepter starts. - */ - uint8_t name_count; }; /* Flags for above struct */ diff --git a/Objects/typeobject.c b/Objects/typeobject.c index 6efd274007a575..ed4fa99f3e530b 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -11002,6 +11002,10 @@ 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 @@ -11148,7 +11152,7 @@ update_one_slot(PyTypeObject *type, pytype_slotdef *p) if (Py_IS_TYPE(descr, &PyWrapperDescr_Type) && ((PyWrapperDescrObject *)descr)->d_base->name_strobj == p->name_strobj) { void **tptr = NULL; - if (p->name_count == 1) + if (slotdefs_name_counts[(p - slotdefs) / sizeof(pytype_slotdef)] == 1) tptr = slotptr(type, p->offset); if (tptr == NULL || tptr == ptr) @@ -11357,6 +11361,8 @@ _PyType_InitSlotDefs(PyInterpreterState *interp) Py_CLEAR(bytearray); } + memset(slotdefs_name_counts, 0, sizeof(slotdefs_name_counts)); + Py_ssize_t pos=0; PyObject *key=NULL; PyObject *value=NULL; @@ -11366,7 +11372,7 @@ _PyType_InitSlotDefs(PyInterpreterState *interp) uint8_t i = 0; for(; i < n; i++) { uint8_t idx = data[i + 1]; - slotdefs[idx].name_count = n; + slotdefs_name_counts[idx] = n; } } From 6d5589cc992094f389b4737c04e93749d6df9a11 Mon Sep 17 00:00:00 2001 From: Sergey Miryanov Date: Wed, 23 Apr 2025 01:14:30 +0500 Subject: [PATCH 21/25] Add slotdefs_name_counts to ignored.tsv to make c-analyzer happy --- Tools/c-analyzer/cpython/ignored.tsv | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Tools/c-analyzer/cpython/ignored.tsv b/Tools/c-analyzer/cpython/ignored.tsv index a33619b1b345e2..6cf11576410de1 100644 --- a/Tools/c-analyzer/cpython/ignored.tsv +++ b/Tools/c-analyzer/cpython/ignored.tsv @@ -340,6 +340,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 - From a7af5bda2810db795f12f5fc38a3c5d656365bac Mon Sep 17 00:00:00 2001 From: Sergey Miryanov Date: Fri, 25 Apr 2025 21:52:14 +0500 Subject: [PATCH 22/25] Adjust c-analyzer max_sizes --- Tools/c-analyzer/cpython/_parser.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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), From 7f62d59693b5ab44f1e59b7d04ce360639dfc88e Mon Sep 17 00:00:00 2001 From: Sergey Miryanov Date: Thu, 1 May 2025 00:18:18 +0500 Subject: [PATCH 23/25] Extend error messages if too much code to c-analyzer --- Tools/c-analyzer/c_parser/parser/__init__.py | 24 ++++++++++++++++++-- Tools/c-analyzer/c_parser/parser/_info.py | 10 ++++++-- 2 files changed, 30 insertions(+), 4 deletions(-) diff --git a/Tools/c-analyzer/c_parser/parser/__init__.py b/Tools/c-analyzer/c_parser/parser/__init__.py index ff4f303c4a2bec..e4a3d07a3c0d0f 100644 --- a/Tools/c-analyzer/c_parser/parser/__init__.py +++ b/Tools/c-analyzer/c_parser/parser/__init__.py @@ -208,7 +208,27 @@ def _iter_source(lines, *, maxtext=11_000, maxlines=200, showtext=False): return # At this point either the file ended prematurely # or there's "too much" text. - filename, lno, text = srcinfo.filename, srcinfo._start, srcinfo.text + filename, lno_from, lno_to = srcinfo.filename, srcinfo.start, srcinfo.end + text = srcinfo.text if len(text) > 500: text = text[:500] + '...' - raise Exception(f'unmatched text ({filename} starting at line {lno}):\n{text}') + + if srcinfo.too_much_text(maxtext): + msg = [ + 'too much text, try to increase MAX_SIZES[MAXTEXT] in cpython/_parser.py', + f'{filename} starting at line {lno_from} to {lno_to}', + f'has code with length {len(text)} greater than {maxtext}:', + text + ] + raise Exception('\n'.join(msg)) + + if srcinfo.too_much_lines(maxlines): + msg = [ + 'too much lines, try to increase MAX_SIZES[MAXLINES] in cpython/_parser.py', + f'{filename} starting at line {lno_from} to {lno_to}', + f'has code with number of lines {lno_to - lno_from} greater than {maxlines}:', + text + ] + raise Exception('\n'.join(msg)) + + raise Exception(f'unmatched text ({filename} starting at line {lno_from}):\n{text}') diff --git a/Tools/c-analyzer/c_parser/parser/_info.py b/Tools/c-analyzer/c_parser/parser/_info.py index 340223db933c90..fbfc1d74f9a27a 100644 --- a/Tools/c-analyzer/c_parser/parser/_info.py +++ b/Tools/c-analyzer/c_parser/parser/_info.py @@ -123,10 +123,16 @@ def resolve(self, kind, data, name, parent=None): def done(self): self._set_ready() + def too_much_text(self, maxtext): + return maxtext and len(self.text) > maxtext + + def too_much_lines(self, maxlines): + return maxlines and self.end - self.start > maxlines + def too_much(self, maxtext, maxlines): - if maxtext and len(self.text) > maxtext: + if self.too_much_text(maxtext): pass - elif maxlines and self.end - self.start > maxlines: + elif self.too_much_lines(maxlines): pass else: return False From b09deafb3317d00159ac9e663f0f02f295252fdd Mon Sep 17 00:00:00 2001 From: Sergey Miryanov Date: Thu, 1 May 2025 00:21:08 +0500 Subject: [PATCH 24/25] Revert "Extend error messages if too much code to c-analyzer" This reverts commit 7f62d59693b5ab44f1e59b7d04ce360639dfc88e. --- Tools/c-analyzer/c_parser/parser/__init__.py | 24 ++------------------ Tools/c-analyzer/c_parser/parser/_info.py | 10 ++------ 2 files changed, 4 insertions(+), 30 deletions(-) diff --git a/Tools/c-analyzer/c_parser/parser/__init__.py b/Tools/c-analyzer/c_parser/parser/__init__.py index e4a3d07a3c0d0f..ff4f303c4a2bec 100644 --- a/Tools/c-analyzer/c_parser/parser/__init__.py +++ b/Tools/c-analyzer/c_parser/parser/__init__.py @@ -208,27 +208,7 @@ def _iter_source(lines, *, maxtext=11_000, maxlines=200, showtext=False): return # At this point either the file ended prematurely # or there's "too much" text. - filename, lno_from, lno_to = srcinfo.filename, srcinfo.start, srcinfo.end - text = srcinfo.text + filename, lno, text = srcinfo.filename, srcinfo._start, srcinfo.text if len(text) > 500: text = text[:500] + '...' - - if srcinfo.too_much_text(maxtext): - msg = [ - 'too much text, try to increase MAX_SIZES[MAXTEXT] in cpython/_parser.py', - f'{filename} starting at line {lno_from} to {lno_to}', - f'has code with length {len(text)} greater than {maxtext}:', - text - ] - raise Exception('\n'.join(msg)) - - if srcinfo.too_much_lines(maxlines): - msg = [ - 'too much lines, try to increase MAX_SIZES[MAXLINES] in cpython/_parser.py', - f'{filename} starting at line {lno_from} to {lno_to}', - f'has code with number of lines {lno_to - lno_from} greater than {maxlines}:', - text - ] - raise Exception('\n'.join(msg)) - - raise Exception(f'unmatched text ({filename} starting at line {lno_from}):\n{text}') + raise Exception(f'unmatched text ({filename} starting at line {lno}):\n{text}') diff --git a/Tools/c-analyzer/c_parser/parser/_info.py b/Tools/c-analyzer/c_parser/parser/_info.py index fbfc1d74f9a27a..340223db933c90 100644 --- a/Tools/c-analyzer/c_parser/parser/_info.py +++ b/Tools/c-analyzer/c_parser/parser/_info.py @@ -123,16 +123,10 @@ def resolve(self, kind, data, name, parent=None): def done(self): self._set_ready() - def too_much_text(self, maxtext): - return maxtext and len(self.text) > maxtext - - def too_much_lines(self, maxlines): - return maxlines and self.end - self.start > maxlines - def too_much(self, maxtext, maxlines): - if self.too_much_text(maxtext): + if maxtext and len(self.text) > maxtext: pass - elif self.too_much_lines(maxlines): + elif maxlines and self.end - self.start > maxlines: pass else: return False From af8ce305dbce8b36457e46c6cf017bed6e192bf2 Mon Sep 17 00:00:00 2001 From: Sergey Miryanov Date: Thu, 1 May 2025 21:46:23 +0500 Subject: [PATCH 25/25] Apply suggestions from code review Co-authored-by: sobolevn --- .../2025-04-10-01-52-42.gh-issue-132042.fePwlj.rst | 2 +- Objects/typeobject.c | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) 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 index e853fbaebf7f69..b1e4c6a5d30bb6 100644 --- 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 @@ -1 +1 @@ -Improve class creation times by up to 40%. Patch by Sergey Miryanov. +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 368266201de467..b261a9397462ca 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -11296,7 +11296,7 @@ _PyType_InitSlotDefs(PyInterpreterState *interp) pytype_slotdef *p; Py_ssize_t idx = 0; for (p = slotdefs; p->name_strobj; p++, idx++) { - assert (idx < 255); + assert(idx < 255); if (PyDict_GetItemRef(cache, p->name_strobj, &bytearray) < 0) { goto error; @@ -11317,11 +11317,11 @@ _PyType_InitSlotDefs(PyInterpreterState *interp) } } - assert (PyByteArray_CheckExact(bytearray)); + assert(PyByteArray_CheckExact(bytearray)); uint8_t *data = (uint8_t *)PyByteArray_AS_STRING(bytearray); data[0] += 1; - assert (data[0] < MAX_EQUIV); + assert(data[0] < MAX_EQUIV); data[data[0]] = (uint8_t)idx; @@ -11330,9 +11330,9 @@ _PyType_InitSlotDefs(PyInterpreterState *interp) memset(slotdefs_name_counts, 0, sizeof(slotdefs_name_counts)); - Py_ssize_t pos=0; - PyObject *key=NULL; - PyObject *value=NULL; + 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];