Skip to content
Merged
Changes from 3 commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
56d13fc
Prebuild mro_dict for find_name_in_mro
sergey-miryanov Apr 6, 2025
1eed75d
Preget tp_dict
sergey-miryanov Apr 6, 2025
bba66d6
Slotdefs cache
sergey-miryanov Apr 7, 2025
bac95a5
Move slotdefs_cache to interp
sergey-miryanov Apr 8, 2025
8d1f5be
Use bytes for slotdefs_cache
sergey-miryanov Apr 8, 2025
8cf19e8
Use type_slots_ptrs cache
sergey-miryanov Apr 8, 2025
b0ad875
Move slotdefs_cache init to pycore_init_builtins
sergey-miryanov Apr 8, 2025
79a165d
Create slotdefs_cache only for main interpreter
sergey-miryanov Apr 9, 2025
a853294
Do not iterate slotdefs_cache just check dups count
sergey-miryanov Apr 9, 2025
75c17fb
Add name_count to pytype_slotdef and get rid of slotdefs_cache and re…
sergey-miryanov Apr 9, 2025
fc17a68
Rename _PyType_InitSlotDefsCache to _PyType_InitSlotDefsNameCounts
sergey-miryanov Apr 9, 2025
736bca4
Get rid of type_slots_ptrs and type_slots_pname from _Py_interp_cache…
sergey-miryanov Apr 9, 2025
1341ed9
Add news entry
sergey-miryanov Apr 9, 2025
3efb9ca
Rename _PyType_InitSlotDefsNameCounts
sergey-miryanov Apr 16, 2025
a792e9d
Use
sergey-miryanov Apr 16, 2025
b6fafa9
Revert "Preget tp_dict"
sergey-miryanov Apr 16, 2025
1459c16
Revert "Prebuild mro_dict for find_name_in_mro"
sergey-miryanov Apr 16, 2025
08740af
Update _PyType_InitSlotDefs and add comment for wrapperbase.name_count
sergey-miryanov Apr 16, 2025
0370d59
Fix error message if _PyType_InitSlotDefs fails
sergey-miryanov Apr 16, 2025
7a57f7c
Merge branch 'main' into gh-132042-optimize-class-creation
sergey-miryanov Apr 18, 2025
c2372f1
Merge branch 'main' into gh-132042-optimize-class-creation
sergey-miryanov Apr 20, 2025
9f48eb3
Merge branch 'main' into gh-132042-optimize-class-creation
sergey-miryanov Apr 22, 2025
17d0265
Use slotdefs_name_counts to check name duplicates
sergey-miryanov Apr 22, 2025
6d5589c
Add slotdefs_name_counts to ignored.tsv to make c-analyzer happy
sergey-miryanov Apr 22, 2025
a7af5bd
Adjust c-analyzer max_sizes
sergey-miryanov Apr 25, 2025
7f62d59
Extend error messages if too much code to c-analyzer
sergey-miryanov Apr 30, 2025
b09deaf
Revert "Extend error messages if too much code to c-analyzer"
sergey-miryanov Apr 30, 2025
04539cc
Merge branch 'main' into gh-132042-optimize-class-creation
sergey-miryanov Apr 30, 2025
af8ce30
Apply suggestions from code review
sergey-miryanov May 1, 2025
9db0885
Merge branch 'main' into gh-132042-optimize-class-creation
sergey-miryanov May 2, 2025
dbf869f
Merge branch 'main' into gh-132042-optimize-class-creation
sergey-miryanov May 2, 2025
9434773
Merge branch 'gh-132042-optimize-class-creation' of github.com:sergey…
sergey-miryanov May 3, 2025
0a4c369
Merge branch 'main' into gh-132042-optimize-class-creation
sergey-miryanov May 3, 2025
05df484
Merge branch 'main' into gh-132042-optimize-class-creation
sergey-miryanov May 8, 2025
7a61ed3
Merge branch 'main' into gh-132042-optimize-class-creation
sergey-miryanov Jun 24, 2025
211d043
Apply suggestions from code review
sergey-miryanov Oct 1, 2025
a4c4d8e
Merge branch 'gh-132042-optimize-class-creation' of github.com:sergey…
sergey-miryanov Oct 1, 2025
82466cc
Fix stray change
sergey-miryanov Oct 1, 2025
f0b40c7
Merge branch 'main' into gh-132042-optimize-class-creation
sergey-miryanov Oct 1, 2025
25acb4a
Fix news entry
sergey-miryanov Oct 1, 2025
5db0944
Apply suggestions from code review
sergey-miryanov Oct 2, 2025
b185436
Fix build
sergey-miryanov Oct 2, 2025
c1884f5
Update Objects/typeobject.c
sergey-miryanov Oct 3, 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
223 changes: 195 additions & 28 deletions Objects/typeobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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()) {
Expand Down Expand Up @@ -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()) {
Expand All @@ -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()) {
Expand Down Expand Up @@ -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()) {
Expand All @@ -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()) {
Expand Down Expand Up @@ -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;
}

Expand All @@ -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;
Expand Down Expand Up @@ -5636,6 +5632,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
Expand Down Expand Up @@ -10946,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
Expand Down Expand Up @@ -10994,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. */
Expand All @@ -11016,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)
Expand Down Expand Up @@ -11086,7 +11156,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 @@ -11117,7 +11187,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 @@ -11208,7 +11282,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 @@ -11261,11 +11335,104 @@ 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;
}
}
}

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);
p = update_one_slot(type, p, mro_dict);
}

Py_XDECREF(mro_dict);

END_TYPE_LOCK();
}

Expand Down
Loading