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:
scoder 2017-10-01 10:37:47 +02:00 committed by Serhiy Storchaka
parent d6bb65f378
commit 2102c78903
2 changed files with 116 additions and 58 deletions

View File

@ -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.

View File

@ -2367,35 +2367,39 @@ type_new(PyTypeObject *metatype, PyObject *args, PyObject *kwds)
&bases, &PyDict_Type, &orig_dict))
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 */
nbases = PyTuple_GET_SIZE(bases);
if (nbases == 0) {
bases = PyTuple_Pack(1, &PyBaseObject_Type);
base = &PyBaseObject_Type;
bases = PyTuple_Pack(1, base);
if (bases == NULL)
goto error;
return NULL;
nbases = 1;
}
else
Py_INCREF(bases);
else {
/* 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 */
base = best_base(bases);
if (base == NULL) {
goto error;
if (winner != metatype) {
if (winner->tp_new != type_new) /* Pass it to the winner */
return winner->tp_new(winner, args, kwds);
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);
if (dict == NULL)
goto error;
@ -2945,13 +2949,75 @@ PyType_GetSlot(PyTypeObject *type, int 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.
This returns a borrowed reference, and doesn't set an exception! */
PyObject *
_PyType_Lookup(PyTypeObject *type, PyObject *name)
{
Py_ssize_t i, n;
PyObject *mro, *res, *base, *dict;
PyObject *res;
int error;
unsigned int h;
if (MCACHE_CACHEABLE_NAME(name) &&
@ -2967,46 +3033,26 @@ _PyType_Lookup(PyTypeObject *type, PyObject *name)
}
}
/* Look in tp_dict of types in MRO */
mro = type->tp_mro;
/* We may end up clearing live exceptions below, so make sure it's ours. */
assert(!PyErr_Occurred());
if (mro == NULL) {
if ((type->tp_flags & Py_TPFLAGS_READYING) == 0 &&
PyType_Ready(type) < 0) {
/* It's not ideal to clear the error condition,
but this function is documented as not setting
an exception, and I don't want to change that.
When PyType_Ready() can't proceed, it won't
set the "ready" flag, so future attempts to ready
the same type will call it again -- hopefully
in a context that propagates the exception out.
*/
res = find_name_in_mro(type, name, &error);
/* Only put NULL results into cache if there was no error. */
if (error) {
/* It's not ideal to clear the error condition,
but this function is documented as not setting
an exception, and I don't want to change that.
E.g., when PyType_Ready() can't proceed, it won't
set the "ready" flag, so future attempts to ready
the same type will call it again -- hopefully
in a context that propagates the exception out.
*/
if (error == -1) {
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)) {
h = MCACHE_HASH_METHOD(type, name);
method_cache[h].version = type->tp_version_tag;
@ -6965,6 +7011,7 @@ update_one_slot(PyTypeObject *type, slotdef *p)
void *generic = NULL, *specific = NULL;
int use_generic = 0;
int offset = p->offset;
int error;
void **ptr = slotptr(type, offset);
if (ptr == NULL) {
@ -6973,9 +7020,18 @@ update_one_slot(PyTypeObject *type, slotdef *p)
} while (p->offset == offset);
return p;
}
/* We may end up clearing live exceptions below, so make sure it's ours. */
assert(!PyErr_Occurred());
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 (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) {
specific = (void *)_PyObject_NextNotImplemented;
}