From 2102c789035ccacbac4362589402ac68baa2cd29 Mon Sep 17 00:00:00 2001 From: scoder Date: Sun, 1 Oct 2017 10:37:47 +0200 Subject: bpo-31336: Speed up type creation. (#3279) Speed up class creation by 10-20% by reducing the overhead in the necessary special method lookups. --- .../2017-09-13-12-04-23.bpo-31336.gi2ahY.rst | 2 + Objects/typeobject.c | 160 ++++++++++++++------- 2 files changed, 110 insertions(+), 52 deletions(-) create mode 100644 Misc/NEWS.d/next/Core and Builtins/2017-09-13-12-04-23.bpo-31336.gi2ahY.rst diff --git a/Misc/NEWS.d/next/Core and Builtins/2017-09-13-12-04-23.bpo-31336.gi2ahY.rst b/Misc/NEWS.d/next/Core and Builtins/2017-09-13-12-04-23.bpo-31336.gi2ahY.rst new file mode 100644 index 0000000..e62b065 --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2017-09-13-12-04-23.bpo-31336.gi2ahY.rst @@ -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. diff --git a/Objects/typeobject.c b/Objects/typeobject.c index 5e0d81f..e72460b 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -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,25 +2949,23 @@ 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; } } @@ -2971,28 +2973,22 @@ _PyType_Lookup(PyTypeObject *type, PyObject *name) 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); @@ -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); @@ -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; } -- cgit v0.12