Skip to content

Commit 4521f15

Browse files
sergey-miryanovvstinnersobolevnkumaraditya303
authored andcommitted
pythongh-132042: Remove resolve_slotdups() to speedup class creation (python#132156)
Co-authored-by: Victor Stinner <[email protected]> Co-authored-by: sobolevn <[email protected]> Co-authored-by: Kumar Aditya <[email protected]>
1 parent 84ab67a commit 4521f15

File tree

6 files changed

+95
-61
lines changed

6 files changed

+95
-61
lines changed

Include/internal/pycore_interp_structs.h

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -672,11 +672,6 @@ struct _Py_interp_cached_objects {
672672

673673
/* object.__reduce__ */
674674
PyObject *objreduce;
675-
#ifndef Py_GIL_DISABLED
676-
/* resolve_slotdups() */
677-
PyObject *type_slots_pname;
678-
pytype_slotdef *type_slots_ptrs[MAX_EQUIV];
679-
#endif
680675

681676
/* TypeVar and related types */
682677
PyTypeObject *generic_type;

Include/internal/pycore_typeobject.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,9 @@ typedef int (*_py_validate_type)(PyTypeObject *);
152152
extern int _PyType_Validate(PyTypeObject *ty, _py_validate_type validate, unsigned int *tp_version);
153153
extern int _PyType_CacheGetItemForSpecialization(PyHeapTypeObject *ht, PyObject *descriptor, uint32_t tp_version);
154154

155+
// Precalculates count of non-unique slots and fills wrapperbase.name_count.
156+
extern int _PyType_InitSlotDefs(PyInterpreterState *interp);
157+
155158
#ifdef __cplusplus
156159
}
157160
#endif
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Improve class creation times by up to 12% by pre-computing type slots
2+
just once. Patch by Sergey Miryanov.

Objects/typeobject.c

Lines changed: 84 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -11422,6 +11422,11 @@ static pytype_slotdef slotdefs[] = {
1142211422
{NULL}
1142311423
};
1142411424

11425+
/* Stores the number of times where slotdefs has elements with same name.
11426+
This counter precalculated by _PyType_InitSlotDefs() when the main
11427+
interpreter starts. */
11428+
static uint8_t slotdefs_name_counts[Py_ARRAY_LENGTH(slotdefs)];
11429+
1142511430
/* Given a type pointer and an offset gotten from a slotdef entry, return a
1142611431
pointer to the actual slot. This is not quite the same as simply adding
1142711432
the offset to the type pointer, since it takes care to indirect through the
@@ -11464,61 +11469,6 @@ slotptr(PyTypeObject *type, int ioffset)
1146411469
return (void **)ptr;
1146511470
}
1146611471

11467-
/* Return a slot pointer for a given name, but ONLY if the attribute has
11468-
exactly one slot function. The name must be an interned string. */
11469-
static void **
11470-
resolve_slotdups(PyTypeObject *type, PyObject *name)
11471-
{
11472-
/* XXX Maybe this could be optimized more -- but is it worth it? */
11473-
11474-
#ifdef Py_GIL_DISABLED
11475-
pytype_slotdef *ptrs[MAX_EQUIV];
11476-
pytype_slotdef **pp = ptrs;
11477-
/* Collect all slotdefs that match name into ptrs. */
11478-
for (pytype_slotdef *p = slotdefs; p->name_strobj; p++) {
11479-
if (p->name_strobj == name)
11480-
*pp++ = p;
11481-
}
11482-
*pp = NULL;
11483-
#else
11484-
/* pname and ptrs act as a little cache */
11485-
PyInterpreterState *interp = _PyInterpreterState_GET();
11486-
#define pname _Py_INTERP_CACHED_OBJECT(interp, type_slots_pname)
11487-
#define ptrs _Py_INTERP_CACHED_OBJECT(interp, type_slots_ptrs)
11488-
pytype_slotdef *p, **pp;
11489-
11490-
if (pname != name) {
11491-
/* Collect all slotdefs that match name into ptrs. */
11492-
pname = name;
11493-
pp = ptrs;
11494-
for (p = slotdefs; p->name_strobj; p++) {
11495-
if (p->name_strobj == name)
11496-
*pp++ = p;
11497-
}
11498-
*pp = NULL;
11499-
}
11500-
#endif
11501-
11502-
/* Look in all slots of the type matching the name. If exactly one of these
11503-
has a filled-in slot, return a pointer to that slot.
11504-
Otherwise, return NULL. */
11505-
void **res, **ptr;
11506-
res = NULL;
11507-
for (pp = ptrs; *pp; pp++) {
11508-
ptr = slotptr(type, (*pp)->offset);
11509-
if (ptr == NULL || *ptr == NULL)
11510-
continue;
11511-
if (res != NULL)
11512-
return NULL;
11513-
res = ptr;
11514-
}
11515-
#ifndef Py_GIL_DISABLED
11516-
#undef pname
11517-
#undef ptrs
11518-
#endif
11519-
return res;
11520-
}
11521-
1152211472
// Return true if "name" corresponds to at least one slot definition. This is
1152311473
// a more accurate but more expensive test compared to is_dunder_name().
1152411474
static bool
@@ -11645,7 +11595,15 @@ update_one_slot(PyTypeObject *type, pytype_slotdef *p, pytype_slotdef **next_p,
1164511595
}
1164611596
if (Py_IS_TYPE(descr, &PyWrapperDescr_Type) &&
1164711597
((PyWrapperDescrObject *)descr)->d_base->name_strobj == p->name_strobj) {
11648-
void **tptr = resolve_slotdups(type, p->name_strobj);
11598+
void **tptr;
11599+
size_t index = (p - slotdefs) / sizeof(slotdefs[0]);
11600+
if (slotdefs_name_counts[index] == 1) {
11601+
tptr = slotptr(type, p->offset);
11602+
}
11603+
else {
11604+
tptr = NULL;
11605+
}
11606+
1164911607
if (tptr == NULL || tptr == ptr)
1165011608
generic = p->function;
1165111609
d = (PyWrapperDescrObject *)descr;
@@ -11858,6 +11816,76 @@ update_all_slots(PyTypeObject* type)
1185811816

1185911817
#endif
1186011818

11819+
int
11820+
_PyType_InitSlotDefs(PyInterpreterState *interp)
11821+
{
11822+
if (!_Py_IsMainInterpreter(interp)) {
11823+
return 0;
11824+
}
11825+
PyObject *bytearray = NULL;
11826+
PyObject *cache = PyDict_New();
11827+
if (!cache) {
11828+
return -1;
11829+
}
11830+
11831+
pytype_slotdef *p;
11832+
Py_ssize_t idx = 0;
11833+
for (p = slotdefs; p->name_strobj; p++, idx++) {
11834+
assert(idx < 255);
11835+
11836+
if (PyDict_GetItemRef(cache, p->name_strobj, &bytearray) < 0) {
11837+
goto error;
11838+
}
11839+
11840+
if (!bytearray) {
11841+
Py_ssize_t size = sizeof(uint8_t) * (1 + MAX_EQUIV);
11842+
bytearray = PyByteArray_FromStringAndSize(NULL, size);
11843+
if (!bytearray) {
11844+
goto error;
11845+
}
11846+
11847+
uint8_t *data = (uint8_t *)PyByteArray_AS_STRING(bytearray);
11848+
data[0] = 0;
11849+
11850+
if (PyDict_SetItem(cache, p->name_strobj, bytearray) < 0) {
11851+
goto error;
11852+
}
11853+
}
11854+
11855+
assert(PyByteArray_CheckExact(bytearray));
11856+
uint8_t *data = (uint8_t *)PyByteArray_AS_STRING(bytearray);
11857+
11858+
data[0] += 1;
11859+
assert(data[0] < MAX_EQUIV);
11860+
11861+
data[data[0]] = (uint8_t)idx;
11862+
11863+
Py_CLEAR(bytearray);
11864+
}
11865+
11866+
memset(slotdefs_name_counts, 0, sizeof(slotdefs_name_counts));
11867+
11868+
Py_ssize_t pos = 0;
11869+
PyObject *key = NULL;
11870+
PyObject *value = NULL;
11871+
while (PyDict_Next(cache, &pos, &key, &value)) {
11872+
uint8_t *data = (uint8_t *)PyByteArray_AS_STRING(value);
11873+
uint8_t n = data[0];
11874+
for (uint8_t i = 0; i < n; i++) {
11875+
uint8_t idx = data[i + 1];
11876+
slotdefs_name_counts[idx] = n;
11877+
}
11878+
}
11879+
11880+
Py_DECREF(cache);
11881+
return 0;
11882+
11883+
error:
11884+
Py_XDECREF(bytearray);
11885+
Py_DECREF(cache);
11886+
return -1;
11887+
}
11888+
1186111889

1186211890
PyObject *
1186311891
_PyType_GetSlotWrapperNames(void)

Python/pylifecycle.c

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -836,6 +836,10 @@ pycore_init_builtins(PyThreadState *tstate)
836836
}
837837
interp->callable_cache.object__getattribute__ = object__getattribute__;
838838

839+
if (_PyType_InitSlotDefs(interp) < 0) {
840+
return _PyStatus_ERR("failed to init slotdefs");
841+
}
842+
839843
if (_PyBuiltins_AddExceptions(bimod) < 0) {
840844
return _PyStatus_ERR("failed to add exceptions to builtins");
841845
}

Tools/c-analyzer/cpython/ignored.tsv

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -344,6 +344,8 @@ Objects/obmalloc.c - obmalloc_state_main -
344344
Objects/obmalloc.c - obmalloc_state_initialized -
345345
Objects/typeobject.c - name_op -
346346
Objects/typeobject.c - slotdefs -
347+
# It initialized only once when main interpeter starts
348+
Objects/typeobject.c - slotdefs_name_counts -
347349
Objects/unicodeobject.c - stripfuncnames -
348350
Objects/unicodeobject.c - utf7_category -
349351
Objects/unicodeobject.c unicode_decode_call_errorhandler_wchar argparse -

0 commit comments

Comments
 (0)