Skip to content

Commit

Permalink
bpo-31336: Speed up type creation. (#3279)
Browse files Browse the repository at this point in the history
Speed up class creation by 10-20% by reducing the overhead in the
necessary special method lookups.
  • Loading branch information
scoder authored and serhiy-storchaka committed Oct 1, 2017
1 parent d6bb65f commit 2102c78
Show file tree
Hide file tree
Showing 2 changed files with 110 additions and 52 deletions.
@@ -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.
160 changes: 108 additions & 52 deletions Objects/typeobject.c
Expand Up @@ -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;
Expand Down Expand Up @@ -2945,54 +2949,46 @@ PyType_GetSlot(PyTypeObject *type, int slot)
return *(void**)(((char*)type) + slotoffsets[slot]);
}

/* 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)
/* 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;
unsigned int h;
Py_hash_t hash;

if (MCACHE_CACHEABLE_NAME(name) &&
PyType_HasFeature(type, Py_TPFLAGS_VALID_VERSION_TAG)) {
/* fast path */
h = MCACHE_HASH_METHOD(type, name);
if (method_cache[h].version == type->tp_version_tag &&
method_cache[h].name == name) {
#if MCACHE_STATS
method_cache_hits++;
#endif
return method_cache[h].value;
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 &&
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.
*/
PyErr_Clear();
return NULL;
if ((type->tp_flags & Py_TPFLAGS_READYING) == 0) {
if (PyType_Ready(type) < 0) {
*error = -1;
return NULL;
}
mro = type->tp_mro;
}
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 PyDict_GetItem(dict, name) */
/* 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);
Expand All @@ -3001,11 +2997,61 @@ _PyType_Lookup(PyTypeObject *type, PyObject *name)
assert(PyType_Check(base));
dict = ((PyTypeObject *)base)->tp_dict;
assert(dict && PyDict_Check(dict));
res = PyDict_GetItem(dict, name);
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)
{
PyObject *res;
int error;
unsigned int h;

if (MCACHE_CACHEABLE_NAME(name) &&
PyType_HasFeature(type, Py_TPFLAGS_VALID_VERSION_TAG)) {
/* fast path */
h = MCACHE_HASH_METHOD(type, name);
if (method_cache[h].version == type->tp_version_tag &&
method_cache[h].name == name) {
#if MCACHE_STATS
method_cache_hits++;
#endif
return method_cache[h].value;
}
}

/* We may end up clearing live exceptions below, so make sure it's ours. */
assert(!PyErr_Occurred());

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;
}

if (MCACHE_CACHEABLE_NAME(name) && assign_version_tag(type)) {
h = MCACHE_HASH_METHOD(type, name);
Expand Down Expand Up @@ -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) {
Expand All @@ -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;
}
Expand Down

0 comments on commit 2102c78

Please sign in to comment.