Skip to content

gh-132042: Prebuild mro_dict for find_name_in_mro to speedup class creation #132618

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 22 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
197615e
Prebuild mro_dict for find_name_in_mro
sergey-miryanov Apr 6, 2025
b7842e0
Simplify find_name_in_mro_new and add some comments
sergey-miryanov Apr 16, 2025
a7a978c
Fix comment for find_namd_in_mro becase it returns strong ref now
sergey-miryanov Apr 16, 2025
b870751
Merge branch 'main' into gh-132042-precalc-mro-dict
sergey-miryanov Apr 17, 2025
a64d3a0
Allow fixup_slot_dispatchers fails
sergey-miryanov Apr 18, 2025
7e067e3
Merge branch 'main' into gh-132042-precalc-mro-dict
sergey-miryanov Apr 18, 2025
88a19d5
Revert "Allow fixup_slot_dispatchers fails"
sergey-miryanov Apr 18, 2025
860fbe7
Update comment about prebuild MRO-dict
sergey-miryanov Apr 18, 2025
52617cb
Merge branch 'main' into gh-132042-precalc-mro-dict
sergey-miryanov Apr 19, 2025
d46f493
Allow fixup_slot_dispatchers fails
sergey-miryanov Apr 22, 2025
8f7750e
Fix test_type_lookup_mro_reference test because it raises exception w…
sergey-miryanov Apr 22, 2025
0d17713
Merge branch 'main' into gh-132042-precalc-mro-dict
sergey-miryanov Apr 22, 2025
7f4c9c7
Update comment about building MRO dict
sergey-miryanov Apr 22, 2025
0d5375e
Merge branch 'main' into gh-132042-precalc-mro-dict
sergey-miryanov Apr 29, 2025
90296f0
Merge branch 'gh-132042-precalc-mro-dict' of github.com:sergey-miryan…
sergey-miryanov Apr 29, 2025
26bafbf
Remove END_TYPE_LOCK from fixup_slot_dispatchers
sergey-miryanov Apr 29, 2025
23aaee0
Merge branch 'main' into gh-132042-precalc-mro-dict
sergey-miryanov Apr 29, 2025
74eabaf
Trying to fix build
sergey-miryanov Apr 29, 2025
63ab044
Fix build
sergey-miryanov Apr 29, 2025
da778df
Merge branch 'main' into gh-132042-precalc-mro-dict
sergey-miryanov Apr 29, 2025
b9a1208
Fix merge conflicts
sergey-miryanov Apr 29, 2025
39f987b
Fix merge conflicts
sergey-miryanov Apr 29, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion Lib/test/test_descr.py
Original file line number Diff line number Diff line change
Expand Up @@ -5236,7 +5236,10 @@ def __hash__(self):
return hash('mykey')

def __eq__(self, other):
X.__bases__ = (Base2,)
try:
X.__bases__ = (Base2,)
except NameError:
pass

class Base(object):
mykey = 'from Base'
Expand Down
80 changes: 71 additions & 9 deletions Objects/typeobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -3626,7 +3626,7 @@ static void object_dealloc(PyObject *);
static PyObject *object_new(PyTypeObject *, PyObject *, PyObject *);
static int object_init(PyObject *, PyObject *, PyObject *);
static int update_slot(PyTypeObject *, PyObject *);
static void fixup_slot_dispatchers(PyTypeObject *);
static int fixup_slot_dispatchers(PyTypeObject *);
static int type_new_set_names(PyTypeObject *);
static int type_new_init_subclass(PyTypeObject *, PyObject *);

Expand Down Expand Up @@ -4582,7 +4582,9 @@ type_new_impl(type_new_ctx *ctx)
}

// Put the proper slots in place
fixup_slot_dispatchers(type);
if (fixup_slot_dispatchers(type) < 0) {
goto error;
}

if (!_PyDict_HasOnlyStringKeys(type->tp_dict)) {
if (PyErr_WarnFormat(
Expand Down Expand Up @@ -5605,7 +5607,7 @@ PyObject_GetItemData(PyObject *obj)
}

/* Internal API to look for a name through the MRO, bypassing the method cache.
This returns a borrowed reference, and might set an exception.
This returns a strong reference, and might set an exception.
'error' is set to: -1: error with exception; 1: error without exception; 0: ok */
static PyObject *
find_name_in_mro(PyTypeObject *type, PyObject *name, int *error)
Expand Down Expand Up @@ -5657,6 +5659,23 @@ find_name_in_mro(PyTypeObject *type, PyObject *name, int *error)
return res;
}

/* Internal API to look for a name through the prebuilt MRO dict.
This returns a strong reference, and might set an exception.
'error' is set to: -1: error with exception; 0: ok */
static PyObject *
find_name_in_mro_new(PyObject *mro_dict, PyObject *name, int *error)
{
ASSERT_TYPE_LOCK_HELD();

PyObject *res = NULL;
if (PyDict_GetItemRef(mro_dict, name, &res) < 0) {
*error = -1;
} else {
*error = 0;
}
return res;
}

/* Check if the "readied" PyUnicode name
is a double-underscore special name. */
static int
Expand Down Expand Up @@ -11150,7 +11169,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();

Expand Down Expand Up @@ -11181,7 +11200,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
Expand Down Expand Up @@ -11279,7 +11302,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;
}
Expand Down Expand Up @@ -11323,21 +11346,60 @@ update_slot(PyTypeObject *type, PyObject *name)

/* Store the proper functions in the slot dispatches at class (type)
definition time, based upon which operations the class overrides in its
dict. */
static void
dict. Returns -1 and exception set on error or 0 otherwise.*/
static int
fixup_slot_dispatchers(PyTypeObject *type)
{
int res = 0;

// This lock isn't strictly necessary because the type has not been
// exposed to anyone else yet, but update_ont_slot calls find_name_in_mro
// where we'd like to assert that the type is locked.
BEGIN_TYPE_LOCK();

PyObject *mro = Py_NewRef(lookup_tp_mro(type));

// Build MRO dict. We build it in bottom-top manner,
// from bottom base to the top one, because the bottommost base
// has more items then other and copying it is preferable than
// merging.
// If we fails, then stop init type.
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) {
res = -1;
goto finish;
}
} else {
if (PyDict_Merge(mro_dict, dict, 1) < 0) {
res = -1;
goto finish;
}
}
}

assert(!res);
assert(mro_dict);
assert(!PyErr_Occurred());
for (pytype_slotdef *p = slotdefs; p->name; ) {
p = update_one_slot(type, p);
p = update_one_slot(type, p, mro_dict);
}

assert(!PyErr_Occurred());

finish:
Py_XDECREF(mro_dict);
Py_DECREF(mro);

END_TYPE_LOCK();
return res;
}

static void
Expand Down
Loading