mirror of https://github.com/python/cpython
bpo-31336: Speed up type creation. (#3279)
Speed up class creation by 10-20% by reducing the overhead in the necessary special method lookups.
This commit is contained in:
parent
d6bb65f378
commit
2102c78903
|
@ -0,0 +1,2 @@
|
||||||
|
Speed up class creation by 10-20% by reducing the overhead in the
|
||||||
|
necessary special method lookups. Patch by Stefan Behnel.
|
|
@ -2367,35 +2367,39 @@ type_new(PyTypeObject *metatype, PyObject *args, PyObject *kwds)
|
||||||
&bases, &PyDict_Type, &orig_dict))
|
&bases, &PyDict_Type, &orig_dict))
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
||||||
/* Determine the proper metatype to deal with this: */
|
|
||||||
winner = _PyType_CalculateMetaclass(metatype, bases);
|
|
||||||
if (winner == NULL) {
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (winner != metatype) {
|
|
||||||
if (winner->tp_new != type_new) /* Pass it to the winner */
|
|
||||||
return winner->tp_new(winner, args, kwds);
|
|
||||||
metatype = winner;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Adjust for empty tuple bases */
|
/* Adjust for empty tuple bases */
|
||||||
nbases = PyTuple_GET_SIZE(bases);
|
nbases = PyTuple_GET_SIZE(bases);
|
||||||
if (nbases == 0) {
|
if (nbases == 0) {
|
||||||
bases = PyTuple_Pack(1, &PyBaseObject_Type);
|
base = &PyBaseObject_Type;
|
||||||
|
bases = PyTuple_Pack(1, base);
|
||||||
if (bases == NULL)
|
if (bases == NULL)
|
||||||
goto error;
|
return NULL;
|
||||||
nbases = 1;
|
nbases = 1;
|
||||||
}
|
}
|
||||||
else
|
else {
|
||||||
Py_INCREF(bases);
|
/* Search the bases for the proper metatype to deal with this: */
|
||||||
|
winner = _PyType_CalculateMetaclass(metatype, bases);
|
||||||
|
if (winner == NULL) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
/* Calculate best base, and check that all bases are type objects */
|
if (winner != metatype) {
|
||||||
base = best_base(bases);
|
if (winner->tp_new != type_new) /* Pass it to the winner */
|
||||||
if (base == NULL) {
|
return winner->tp_new(winner, args, kwds);
|
||||||
goto error;
|
metatype = winner;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Calculate best base, and check that all bases are type objects */
|
||||||
|
base = best_base(bases);
|
||||||
|
if (base == NULL) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
Py_INCREF(bases);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Use "goto error" from this point on as we now own the reference to "bases". */
|
||||||
|
|
||||||
dict = PyDict_Copy(orig_dict);
|
dict = PyDict_Copy(orig_dict);
|
||||||
if (dict == NULL)
|
if (dict == NULL)
|
||||||
goto error;
|
goto error;
|
||||||
|
@ -2945,13 +2949,75 @@ PyType_GetSlot(PyTypeObject *type, int slot)
|
||||||
return *(void**)(((char*)type) + slotoffsets[slot]);
|
return *(void**)(((char*)type) + slotoffsets[slot]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Internal API to look for a name through the MRO, bypassing the method cache.
|
||||||
|
This returns a borrowed 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)
|
||||||
|
{
|
||||||
|
Py_ssize_t i, n;
|
||||||
|
PyObject *mro, *res, *base, *dict;
|
||||||
|
Py_hash_t hash;
|
||||||
|
|
||||||
|
if (!PyUnicode_CheckExact(name) ||
|
||||||
|
(hash = ((PyASCIIObject *) name)->hash) == -1)
|
||||||
|
{
|
||||||
|
hash = PyObject_Hash(name);
|
||||||
|
if (hash == -1) {
|
||||||
|
*error = -1;
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Look in tp_dict of types in MRO */
|
||||||
|
mro = type->tp_mro;
|
||||||
|
|
||||||
|
if (mro == NULL) {
|
||||||
|
if ((type->tp_flags & Py_TPFLAGS_READYING) == 0) {
|
||||||
|
if (PyType_Ready(type) < 0) {
|
||||||
|
*error = -1;
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
mro = type->tp_mro;
|
||||||
|
}
|
||||||
|
if (mro == NULL) {
|
||||||
|
*error = 1;
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
res = NULL;
|
||||||
|
/* Keep a strong reference to mro because type->tp_mro can be replaced
|
||||||
|
during dict lookup, e.g. when comparing to non-string keys. */
|
||||||
|
Py_INCREF(mro);
|
||||||
|
assert(PyTuple_Check(mro));
|
||||||
|
n = PyTuple_GET_SIZE(mro);
|
||||||
|
for (i = 0; i < n; i++) {
|
||||||
|
base = PyTuple_GET_ITEM(mro, i);
|
||||||
|
assert(PyType_Check(base));
|
||||||
|
dict = ((PyTypeObject *)base)->tp_dict;
|
||||||
|
assert(dict && PyDict_Check(dict));
|
||||||
|
res = _PyDict_GetItem_KnownHash(dict, name, hash);
|
||||||
|
if (res != NULL)
|
||||||
|
break;
|
||||||
|
if (PyErr_Occurred()) {
|
||||||
|
*error = -1;
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*error = 0;
|
||||||
|
done:
|
||||||
|
Py_DECREF(mro);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
/* Internal API to look for a name through the MRO.
|
/* Internal API to look for a name through the MRO.
|
||||||
This returns a borrowed reference, and doesn't set an exception! */
|
This returns a borrowed reference, and doesn't set an exception! */
|
||||||
PyObject *
|
PyObject *
|
||||||
_PyType_Lookup(PyTypeObject *type, PyObject *name)
|
_PyType_Lookup(PyTypeObject *type, PyObject *name)
|
||||||
{
|
{
|
||||||
Py_ssize_t i, n;
|
PyObject *res;
|
||||||
PyObject *mro, *res, *base, *dict;
|
int error;
|
||||||
unsigned int h;
|
unsigned int h;
|
||||||
|
|
||||||
if (MCACHE_CACHEABLE_NAME(name) &&
|
if (MCACHE_CACHEABLE_NAME(name) &&
|
||||||
|
@ -2967,46 +3033,26 @@ _PyType_Lookup(PyTypeObject *type, PyObject *name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Look in tp_dict of types in MRO */
|
/* We may end up clearing live exceptions below, so make sure it's ours. */
|
||||||
mro = type->tp_mro;
|
assert(!PyErr_Occurred());
|
||||||
|
|
||||||
if (mro == NULL) {
|
res = find_name_in_mro(type, name, &error);
|
||||||
if ((type->tp_flags & Py_TPFLAGS_READYING) == 0 &&
|
/* Only put NULL results into cache if there was no error. */
|
||||||
PyType_Ready(type) < 0) {
|
if (error) {
|
||||||
/* It's not ideal to clear the error condition,
|
/* It's not ideal to clear the error condition,
|
||||||
but this function is documented as not setting
|
but this function is documented as not setting
|
||||||
an exception, and I don't want to change that.
|
an exception, and I don't want to change that.
|
||||||
When PyType_Ready() can't proceed, it won't
|
E.g., when PyType_Ready() can't proceed, it won't
|
||||||
set the "ready" flag, so future attempts to ready
|
set the "ready" flag, so future attempts to ready
|
||||||
the same type will call it again -- hopefully
|
the same type will call it again -- hopefully
|
||||||
in a context that propagates the exception out.
|
in a context that propagates the exception out.
|
||||||
*/
|
*/
|
||||||
|
if (error == -1) {
|
||||||
PyErr_Clear();
|
PyErr_Clear();
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
mro = type->tp_mro;
|
|
||||||
if (mro == NULL) {
|
|
||||||
return NULL;
|
|
||||||
}
|
}
|
||||||
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
res = NULL;
|
|
||||||
/* keep a strong reference to mro because type->tp_mro can be replaced
|
|
||||||
during PyDict_GetItem(dict, name) */
|
|
||||||
Py_INCREF(mro);
|
|
||||||
assert(PyTuple_Check(mro));
|
|
||||||
n = PyTuple_GET_SIZE(mro);
|
|
||||||
for (i = 0; i < n; i++) {
|
|
||||||
base = PyTuple_GET_ITEM(mro, i);
|
|
||||||
assert(PyType_Check(base));
|
|
||||||
dict = ((PyTypeObject *)base)->tp_dict;
|
|
||||||
assert(dict && PyDict_Check(dict));
|
|
||||||
res = PyDict_GetItem(dict, name);
|
|
||||||
if (res != NULL)
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
Py_DECREF(mro);
|
|
||||||
|
|
||||||
if (MCACHE_CACHEABLE_NAME(name) && assign_version_tag(type)) {
|
if (MCACHE_CACHEABLE_NAME(name) && assign_version_tag(type)) {
|
||||||
h = MCACHE_HASH_METHOD(type, name);
|
h = MCACHE_HASH_METHOD(type, name);
|
||||||
method_cache[h].version = type->tp_version_tag;
|
method_cache[h].version = type->tp_version_tag;
|
||||||
|
@ -6965,6 +7011,7 @@ update_one_slot(PyTypeObject *type, slotdef *p)
|
||||||
void *generic = NULL, *specific = NULL;
|
void *generic = NULL, *specific = NULL;
|
||||||
int use_generic = 0;
|
int use_generic = 0;
|
||||||
int offset = p->offset;
|
int offset = p->offset;
|
||||||
|
int error;
|
||||||
void **ptr = slotptr(type, offset);
|
void **ptr = slotptr(type, offset);
|
||||||
|
|
||||||
if (ptr == NULL) {
|
if (ptr == NULL) {
|
||||||
|
@ -6973,9 +7020,18 @@ update_one_slot(PyTypeObject *type, slotdef *p)
|
||||||
} while (p->offset == offset);
|
} while (p->offset == offset);
|
||||||
return p;
|
return p;
|
||||||
}
|
}
|
||||||
|
/* We may end up clearing live exceptions below, so make sure it's ours. */
|
||||||
|
assert(!PyErr_Occurred());
|
||||||
do {
|
do {
|
||||||
descr = _PyType_Lookup(type, p->name_strobj);
|
/* 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 (descr == NULL) {
|
if (descr == NULL) {
|
||||||
|
if (error == -1) {
|
||||||
|
/* It is unlikely by not impossible that there has been an exception
|
||||||
|
during lookup. Since this function originally expected no errors,
|
||||||
|
we ignore them here in order to keep up the interface. */
|
||||||
|
PyErr_Clear();
|
||||||
|
}
|
||||||
if (ptr == (void**)&type->tp_iternext) {
|
if (ptr == (void**)&type->tp_iternext) {
|
||||||
specific = (void *)_PyObject_NextNotImplemented;
|
specific = (void *)_PyObject_NextNotImplemented;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue