Skip to content

Commit 2102c78

Browse files
scoderserhiy-storchaka
authored andcommitted
bpo-31336: Speed up type creation. (python#3279)
Speed up class creation by 10-20% by reducing the overhead in the necessary special method lookups.
1 parent d6bb65f commit 2102c78

File tree

2 files changed

+110
-52
lines changed

2 files changed

+110
-52
lines changed
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Speed up class creation by 10-20% by reducing the overhead in the
2+
necessary special method lookups. Patch by Stefan Behnel.

Objects/typeobject.c

Lines changed: 108 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -2367,35 +2367,39 @@ type_new(PyTypeObject *metatype, PyObject *args, PyObject *kwds)
23672367
&bases, &PyDict_Type, &orig_dict))
23682368
return NULL;
23692369

2370-
/* Determine the proper metatype to deal with this: */
2371-
winner = _PyType_CalculateMetaclass(metatype, bases);
2372-
if (winner == NULL) {
2373-
return NULL;
2374-
}
2375-
2376-
if (winner != metatype) {
2377-
if (winner->tp_new != type_new) /* Pass it to the winner */
2378-
return winner->tp_new(winner, args, kwds);
2379-
metatype = winner;
2380-
}
2381-
23822370
/* Adjust for empty tuple bases */
23832371
nbases = PyTuple_GET_SIZE(bases);
23842372
if (nbases == 0) {
2385-
bases = PyTuple_Pack(1, &PyBaseObject_Type);
2373+
base = &PyBaseObject_Type;
2374+
bases = PyTuple_Pack(1, base);
23862375
if (bases == NULL)
2387-
goto error;
2376+
return NULL;
23882377
nbases = 1;
23892378
}
2390-
else
2391-
Py_INCREF(bases);
2379+
else {
2380+
/* Search the bases for the proper metatype to deal with this: */
2381+
winner = _PyType_CalculateMetaclass(metatype, bases);
2382+
if (winner == NULL) {
2383+
return NULL;
2384+
}
23922385

2393-
/* Calculate best base, and check that all bases are type objects */
2394-
base = best_base(bases);
2395-
if (base == NULL) {
2396-
goto error;
2386+
if (winner != metatype) {
2387+
if (winner->tp_new != type_new) /* Pass it to the winner */
2388+
return winner->tp_new(winner, args, kwds);
2389+
metatype = winner;
2390+
}
2391+
2392+
/* Calculate best base, and check that all bases are type objects */
2393+
base = best_base(bases);
2394+
if (base == NULL) {
2395+
return NULL;
2396+
}
2397+
2398+
Py_INCREF(bases);
23972399
}
23982400

2401+
/* Use "goto error" from this point on as we now own the reference to "bases". */
2402+
23992403
dict = PyDict_Copy(orig_dict);
24002404
if (dict == NULL)
24012405
goto error;
@@ -2945,54 +2949,46 @@ PyType_GetSlot(PyTypeObject *type, int slot)
29452949
return *(void**)(((char*)type) + slotoffsets[slot]);
29462950
}
29472951

2948-
/* Internal API to look for a name through the MRO.
2949-
This returns a borrowed reference, and doesn't set an exception! */
2950-
PyObject *
2951-
_PyType_Lookup(PyTypeObject *type, PyObject *name)
2952+
/* Internal API to look for a name through the MRO, bypassing the method cache.
2953+
This returns a borrowed reference, and might set an exception.
2954+
'error' is set to: -1: error with exception; 1: error without exception; 0: ok */
2955+
static PyObject *
2956+
find_name_in_mro(PyTypeObject *type, PyObject *name, int *error)
29522957
{
29532958
Py_ssize_t i, n;
29542959
PyObject *mro, *res, *base, *dict;
2955-
unsigned int h;
2960+
Py_hash_t hash;
29562961

2957-
if (MCACHE_CACHEABLE_NAME(name) &&
2958-
PyType_HasFeature(type, Py_TPFLAGS_VALID_VERSION_TAG)) {
2959-
/* fast path */
2960-
h = MCACHE_HASH_METHOD(type, name);
2961-
if (method_cache[h].version == type->tp_version_tag &&
2962-
method_cache[h].name == name) {
2963-
#if MCACHE_STATS
2964-
method_cache_hits++;
2965-
#endif
2966-
return method_cache[h].value;
2962+
if (!PyUnicode_CheckExact(name) ||
2963+
(hash = ((PyASCIIObject *) name)->hash) == -1)
2964+
{
2965+
hash = PyObject_Hash(name);
2966+
if (hash == -1) {
2967+
*error = -1;
2968+
return NULL;
29672969
}
29682970
}
29692971

29702972
/* Look in tp_dict of types in MRO */
29712973
mro = type->tp_mro;
29722974

29732975
if (mro == NULL) {
2974-
if ((type->tp_flags & Py_TPFLAGS_READYING) == 0 &&
2975-
PyType_Ready(type) < 0) {
2976-
/* It's not ideal to clear the error condition,
2977-
but this function is documented as not setting
2978-
an exception, and I don't want to change that.
2979-
When PyType_Ready() can't proceed, it won't
2980-
set the "ready" flag, so future attempts to ready
2981-
the same type will call it again -- hopefully
2982-
in a context that propagates the exception out.
2983-
*/
2984-
PyErr_Clear();
2985-
return NULL;
2976+
if ((type->tp_flags & Py_TPFLAGS_READYING) == 0) {
2977+
if (PyType_Ready(type) < 0) {
2978+
*error = -1;
2979+
return NULL;
2980+
}
2981+
mro = type->tp_mro;
29862982
}
2987-
mro = type->tp_mro;
29882983
if (mro == NULL) {
2984+
*error = 1;
29892985
return NULL;
29902986
}
29912987
}
29922988

29932989
res = NULL;
2994-
/* keep a strong reference to mro because type->tp_mro can be replaced
2995-
during PyDict_GetItem(dict, name) */
2990+
/* Keep a strong reference to mro because type->tp_mro can be replaced
2991+
during dict lookup, e.g. when comparing to non-string keys. */
29962992
Py_INCREF(mro);
29972993
assert(PyTuple_Check(mro));
29982994
n = PyTuple_GET_SIZE(mro);
@@ -3001,11 +2997,61 @@ _PyType_Lookup(PyTypeObject *type, PyObject *name)
30012997
assert(PyType_Check(base));
30022998
dict = ((PyTypeObject *)base)->tp_dict;
30032999
assert(dict && PyDict_Check(dict));
3004-
res = PyDict_GetItem(dict, name);
3000+
res = _PyDict_GetItem_KnownHash(dict, name, hash);
30053001
if (res != NULL)
30063002
break;
3003+
if (PyErr_Occurred()) {
3004+
*error = -1;
3005+
goto done;
3006+
}
30073007
}
3008+
*error = 0;
3009+
done:
30083010
Py_DECREF(mro);
3011+
return res;
3012+
}
3013+
3014+
/* Internal API to look for a name through the MRO.
3015+
This returns a borrowed reference, and doesn't set an exception! */
3016+
PyObject *
3017+
_PyType_Lookup(PyTypeObject *type, PyObject *name)
3018+
{
3019+
PyObject *res;
3020+
int error;
3021+
unsigned int h;
3022+
3023+
if (MCACHE_CACHEABLE_NAME(name) &&
3024+
PyType_HasFeature(type, Py_TPFLAGS_VALID_VERSION_TAG)) {
3025+
/* fast path */
3026+
h = MCACHE_HASH_METHOD(type, name);
3027+
if (method_cache[h].version == type->tp_version_tag &&
3028+
method_cache[h].name == name) {
3029+
#if MCACHE_STATS
3030+
method_cache_hits++;
3031+
#endif
3032+
return method_cache[h].value;
3033+
}
3034+
}
3035+
3036+
/* We may end up clearing live exceptions below, so make sure it's ours. */
3037+
assert(!PyErr_Occurred());
3038+
3039+
res = find_name_in_mro(type, name, &error);
3040+
/* Only put NULL results into cache if there was no error. */
3041+
if (error) {
3042+
/* It's not ideal to clear the error condition,
3043+
but this function is documented as not setting
3044+
an exception, and I don't want to change that.
3045+
E.g., when PyType_Ready() can't proceed, it won't
3046+
set the "ready" flag, so future attempts to ready
3047+
the same type will call it again -- hopefully
3048+
in a context that propagates the exception out.
3049+
*/
3050+
if (error == -1) {
3051+
PyErr_Clear();
3052+
}
3053+
return NULL;
3054+
}
30093055

30103056
if (MCACHE_CACHEABLE_NAME(name) && assign_version_tag(type)) {
30113057
h = MCACHE_HASH_METHOD(type, name);
@@ -6965,6 +7011,7 @@ update_one_slot(PyTypeObject *type, slotdef *p)
69657011
void *generic = NULL, *specific = NULL;
69667012
int use_generic = 0;
69677013
int offset = p->offset;
7014+
int error;
69687015
void **ptr = slotptr(type, offset);
69697016

69707017
if (ptr == NULL) {
@@ -6973,9 +7020,18 @@ update_one_slot(PyTypeObject *type, slotdef *p)
69737020
} while (p->offset == offset);
69747021
return p;
69757022
}
7023+
/* We may end up clearing live exceptions below, so make sure it's ours. */
7024+
assert(!PyErr_Occurred());
69767025
do {
6977-
descr = _PyType_Lookup(type, p->name_strobj);
7026+
/* Use faster uncached lookup as we won't get any cache hits during type setup. */
7027+
descr = find_name_in_mro(type, p->name_strobj, &error);
69787028
if (descr == NULL) {
7029+
if (error == -1) {
7030+
/* It is unlikely by not impossible that there has been an exception
7031+
during lookup. Since this function originally expected no errors,
7032+
we ignore them here in order to keep up the interface. */
7033+
PyErr_Clear();
7034+
}
69797035
if (ptr == (void**)&type->tp_iternext) {
69807036
specific = (void *)_PyObject_NextNotImplemented;
69817037
}

0 commit comments

Comments
 (0)