From 0a865c9309c6548fe6380500f02240d1995d3b16 Mon Sep 17 00:00:00 2001 From: Stefan Behnel Date: Mon, 4 Sep 2017 13:27:09 +0200 Subject: [PATCH 01/20] Speed up type creation, which is highly dominated by slow dict lookups. --- Objects/typeobject.c | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/Objects/typeobject.c b/Objects/typeobject.c index d1a12a7efac2c8..8069ebaae9ddb0 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -2945,6 +2945,7 @@ _PyType_Lookup(PyTypeObject *type, PyObject *name) { Py_ssize_t i, n; PyObject *mro, *res, *base, *dict; + Py_hash_t hash; unsigned int h; if (MCACHE_CACHEABLE_NAME(name) && @@ -2983,6 +2984,21 @@ _PyType_Lookup(PyTypeObject *type, PyObject *name) } } + if (!PyUnicode_CheckExact(name) || + (hash = ((PyASCIIObject *) name)->hash) == -1) + { + hash = PyObject_Hash(name); + if (hash == -1) { + /* Same comment as above applies: this should not ignore the + error. But it's highly unlikely that we even get here since + 'name' is bound to be a PyUnicode object and almost certainly + not a subtype. + */ + PyErr_Clear(); + return NULL; + } + } + res = NULL; /* keep a strong reference to mro because type->tp_mro can be replaced during PyDict_GetItem(dict, name) */ @@ -2994,7 +3010,7 @@ _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; } From c4779e3f7c06d1e53386f18e6bad3b045741369d Mon Sep 17 00:00:00 2001 From: Stefan Behnel Date: Mon, 4 Sep 2017 15:14:19 +0200 Subject: [PATCH 02/20] Update comment after changing the function call that it refers to. --- Objects/typeobject.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Objects/typeobject.c b/Objects/typeobject.c index 8069ebaae9ddb0..af5cba121f4552 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -3000,8 +3000,8 @@ _PyType_Lookup(PyTypeObject *type, PyObject *name) } 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); From ff2ea631ebc3b64f8785cfdbaa922cd3546bd1b1 Mon Sep 17 00:00:00 2001 From: Stefan Behnel Date: Mon, 4 Sep 2017 15:33:33 +0200 Subject: [PATCH 03/20] Ignore any errors (however unlikely) that may happen during the mro chained name lookups in _PyType_Lookup(). --- Objects/typeobject.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Objects/typeobject.c b/Objects/typeobject.c index af5cba121f4552..99eefbf3ab1024 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -3013,6 +3013,9 @@ _PyType_Lookup(PyTypeObject *type, PyObject *name) res = _PyDict_GetItem_KnownHash(dict, name, hash); if (res != NULL) break; + /* Ignore any errors during lookup - unlikely to happen, + but not impossible. */ + PyErr_Clear(); } Py_DECREF(mro); From 76b1986e093e9b236106df3abb7dfcd6f60e43ad Mon Sep 17 00:00:00 2001 From: Stefan Behnel Date: Tue, 5 Sep 2017 19:07:31 +0200 Subject: [PATCH 04/20] Extract non-caching code from _PyType_Lookup() to use it directly from update_one_slot(). --- Objects/typeobject.c | 95 ++++++++++++++++++++++++++------------------ 1 file changed, 56 insertions(+), 39 deletions(-) diff --git a/Objects/typeobject.c b/Objects/typeobject.c index 99eefbf3ab1024..84ed24adfb5d55 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -2938,44 +2938,21 @@ 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! */ +static PyObject * +_PyType_LookupUncached(PyTypeObject *type, PyObject *name, int *error) { Py_ssize_t i, n; PyObject *mro, *res, *base, *dict; Py_hash_t hash; - 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; - } - } - + *error = 1; /* 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(); + PyType_Ready(type) < 0) { return NULL; } mro = type->tp_mro; @@ -2989,12 +2966,6 @@ _PyType_Lookup(PyTypeObject *type, PyObject *name) { hash = PyObject_Hash(name); if (hash == -1) { - /* Same comment as above applies: this should not ignore the - error. But it's highly unlikely that we even get here since - 'name' is bound to be a PyUnicode object and almost certainly - not a subtype. - */ - PyErr_Clear(); return NULL; } } @@ -3013,11 +2984,50 @@ _PyType_Lookup(PyTypeObject *type, PyObject *name) res = _PyDict_GetItem_KnownHash(dict, name, hash); if (res != NULL) break; - /* Ignore any errors during lookup - unlikely to happen, - but not impossible. */ - PyErr_Clear(); + if (PyErr_Occurred()) + return NULL; } Py_DECREF(mro); + *error = 0; + 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; + } + } + + res = _PyType_LookupUncached(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. + */ + PyErr_Clear(); + return NULL; + } if (MCACHE_CACHEABLE_NAME(name) && assign_version_tag(type)) { h = MCACHE_HASH_METHOD(type, name); @@ -6968,6 +6978,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) { @@ -6977,8 +6988,14 @@ update_one_slot(PyTypeObject *type, slotdef *p) return p; } do { - descr = _PyType_Lookup(type, p->name_strobj); + descr = _PyType_LookupUncached(type, p->name_strobj, &error); if (descr == NULL) { + if (error) { + /* 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; } From 6f9848520ca858ae72d14ee99d0d86796b664981 Mon Sep 17 00:00:00 2001 From: Stefan Behnel Date: Tue, 5 Sep 2017 19:39:49 +0200 Subject: [PATCH 05/20] Avoid some useless overhead for the no-basetype case. If "object" ever ceases to be a valid base type, there'll probably be larger code sections to change than this one. --- Objects/typeobject.c | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/Objects/typeobject.c b/Objects/typeobject.c index 84ed24adfb5d55..7563523e18b64b 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -2375,18 +2375,20 @@ type_new(PyTypeObject *metatype, PyObject *args, PyObject *kwds) /* 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; nbases = 1; } - else + else { Py_INCREF(bases); - /* Calculate best base, and check that all bases are type objects */ - base = best_base(bases); - if (base == NULL) { - goto error; + /* Calculate best base, and check that all bases are type objects */ + base = best_base(bases); + if (base == NULL) { + goto error; + } } dict = PyDict_Copy(orig_dict); From d9b726e6ddaae31b01b41c33572e4f6f3fa1a63b Mon Sep 17 00:00:00 2001 From: Stefan Behnel Date: Wed, 6 Sep 2017 07:19:09 +0200 Subject: [PATCH 06/20] Add comment. --- Objects/typeobject.c | 1 + 1 file changed, 1 insertion(+) diff --git a/Objects/typeobject.c b/Objects/typeobject.c index 7563523e18b64b..045b595e30a0aa 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -6990,6 +6990,7 @@ update_one_slot(PyTypeObject *type, slotdef *p) return p; } do { + /* Use faster uncached lookup as we won't get any cache hits during type setup. */ descr = _PyType_LookupUncached(type, p->name_strobj, &error); if (descr == NULL) { if (error) { From be15255ff763ce1018a7318590dc37a4c1e04a00 Mon Sep 17 00:00:00 2001 From: Stefan Behnel Date: Wed, 6 Sep 2017 10:18:04 +0200 Subject: [PATCH 07/20] Avoid uselessly searching empty bases for a metaclass. This is quite common in Python 3 now. Also make the step from "return NULL" error handling to "goto error" reference cleanup explicit. --- Objects/typeobject.c | 32 +++++++++++++++++--------------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/Objects/typeobject.c b/Objects/typeobject.c index 045b595e30a0aa..a5f0eee945a1a0 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -2360,37 +2360,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) { base = &PyBaseObject_Type; bases = PyTuple_Pack(1, base); if (bases == NULL) - goto error; + return NULL; nbases = 1; } 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; + } + + 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) { - goto error; + 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; From 0736226d67b43c70013a397be7157a7317b42057 Mon Sep 17 00:00:00 2001 From: Stefan Behnel Date: Sun, 10 Sep 2017 21:57:20 +0200 Subject: [PATCH 08/20] Avoid unsafe handling of borrowed "mro" reference during hash() call. Distinguish between "error" and "error with exception" cases in _PyType_LookupUncached(). Fix a reference leak of "mro" on lookup errors. Resolves issues found by Serhiy Storchaka. --- Objects/typeobject.c | 40 ++++++++++++++++++++++++---------------- 1 file changed, 24 insertions(+), 16 deletions(-) diff --git a/Objects/typeobject.c b/Objects/typeobject.c index a5f0eee945a1a0..0ef076828b53a7 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -2943,33 +2943,37 @@ PyType_GetSlot(PyTypeObject *type, int 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! */ + 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 * _PyType_LookupUncached(PyTypeObject *type, PyObject *name, int *error) { Py_ssize_t i, n; PyObject *mro, *res, *base, *dict; Py_hash_t hash; - *error = 1; + + 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) { + *error = -1; return NULL; } mro = type->tp_mro; if (mro == NULL) { - return NULL; - } - } - - if (!PyUnicode_CheckExact(name) || - (hash = ((PyASCIIObject *) name)->hash) == -1) - { - hash = PyObject_Hash(name); - if (hash == -1) { + *error = 1; return NULL; } } @@ -2988,11 +2992,14 @@ _PyType_LookupUncached(PyTypeObject *type, PyObject *name, int *error) res = _PyDict_GetItem_KnownHash(dict, name, hash); if (res != NULL) break; - if (PyErr_Occurred()) - return NULL; + if (PyErr_Occurred()) { + *error = -1; + goto done; + } } - Py_DECREF(mro); *error = 0; +done: + Py_DECREF(mro); return res; } @@ -3029,7 +3036,8 @@ _PyType_Lookup(PyTypeObject *type, PyObject *name) the same type will call it again -- hopefully in a context that propagates the exception out. */ - PyErr_Clear(); + if (error == -1) + PyErr_Clear(); return NULL; } @@ -6995,7 +7003,7 @@ update_one_slot(PyTypeObject *type, slotdef *p) /* Use faster uncached lookup as we won't get any cache hits during type setup. */ descr = _PyType_LookupUncached(type, p->name_strobj, &error); if (descr == NULL) { - if (error) { + 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. */ From 8bc783f07e60c39afa464029b8d58443a0140137 Mon Sep 17 00:00:00 2001 From: Stefan Behnel Date: Sun, 10 Sep 2017 22:44:09 +0200 Subject: [PATCH 09/20] Clean up code and formatting a little. --- Objects/typeobject.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/Objects/typeobject.c b/Objects/typeobject.c index 0ef076828b53a7..4335bd90660ac7 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -2966,12 +2966,13 @@ _PyType_LookupUncached(PyTypeObject *type, PyObject *name, int *error) mro = type->tp_mro; if (mro == NULL) { - if ((type->tp_flags & Py_TPFLAGS_READYING) == 0 && - PyType_Ready(type) < 0) { - *error = -1; - 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; From 66648dd568cfefdf1f003bce8fc2aced4ec6972b Mon Sep 17 00:00:00 2001 From: Stefan Behnel Date: Sun, 10 Sep 2017 22:44:57 +0200 Subject: [PATCH 10/20] Add braces for code style reasons. --- Objects/typeobject.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Objects/typeobject.c b/Objects/typeobject.c index 4335bd90660ac7..8e64d6082bcb60 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -3037,8 +3037,9 @@ _PyType_Lookup(PyTypeObject *type, PyObject *name) the same type will call it again -- hopefully in a context that propagates the exception out. */ - if (error == -1) + if (error == -1) { PyErr_Clear(); + } return NULL; } From 85fb3aedd4a878cbd18145c92b32a03c7fdcbfeb Mon Sep 17 00:00:00 2001 From: Stefan Behnel Date: Sun, 10 Sep 2017 22:53:22 +0200 Subject: [PATCH 11/20] Give internal helper function a local name that does not resemble (existing) API names. Suggested by Serhiy Storchaka. --- Objects/typeobject.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Objects/typeobject.c b/Objects/typeobject.c index 8e64d6082bcb60..17713a0750d7d4 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -2946,7 +2946,7 @@ PyType_GetSlot(PyTypeObject *type, int slot) 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 * -_PyType_LookupUncached(PyTypeObject *type, PyObject *name, int *error) +find_name_in_mro(PyTypeObject *type, PyObject *name, int *error) { Py_ssize_t i, n; PyObject *mro, *res, *base, *dict; @@ -3026,7 +3026,7 @@ _PyType_Lookup(PyTypeObject *type, PyObject *name) } } - res = _PyType_LookupUncached(type, name, &error); + 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, @@ -7003,7 +7003,7 @@ update_one_slot(PyTypeObject *type, slotdef *p) } do { /* Use faster uncached lookup as we won't get any cache hits during type setup. */ - descr = _PyType_LookupUncached(type, p->name_strobj, &error); + 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 From 09e716a2e8eda777400da779c5a4f1b5e90375f6 Mon Sep 17 00:00:00 2001 From: Stefan Behnel Date: Mon, 11 Sep 2017 17:51:46 +0200 Subject: [PATCH 12/20] Allow non-dict types for the class dict when looking up names and call PyObject_GetItem() for them. --- Objects/typeobject.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/Objects/typeobject.c b/Objects/typeobject.c index 17713a0750d7d4..87c48785a3acc7 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -2989,8 +2989,12 @@ find_name_in_mro(PyTypeObject *type, PyObject *name, int *error) 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); + assert(dict); + if (PyDict_CheckExact(dict)) { + res = _PyDict_GetItem_KnownHash(dict, name, hash); + } else { + res = PyObject_GetItem(dict, name); + } if (res != NULL) break; if (PyErr_Occurred()) { From 1d52082bb544de02901f922242942253497cba58 Mon Sep 17 00:00:00 2001 From: Stefan Behnel Date: Mon, 11 Sep 2017 18:12:27 +0200 Subject: [PATCH 13/20] Lazily calculate name hash in find_name_in_mro() to avoid potential re-calculation if the mapping is not exactly a dict. --- Objects/typeobject.c | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/Objects/typeobject.c b/Objects/typeobject.c index 87c48785a3acc7..374b2b2baf75e8 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -2950,17 +2950,7 @@ 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; - } - } + Py_hash_t hash = -1; /* Look in tp_dict of types in MRO */ mro = type->tp_mro; @@ -2990,9 +2980,22 @@ find_name_in_mro(PyTypeObject *type, PyObject *name, int *error) assert(PyType_Check(base)); dict = ((PyTypeObject *)base)->tp_dict; assert(dict); + /* Optimise for the extremely common case: dict for lookup, unicode name. */ if (PyDict_CheckExact(dict)) { + if (hash == -1) { + if (!PyUnicode_CheckExact(name) || + (hash = ((PyASCIIObject *) name)->hash) == -1) + { + hash = PyObject_Hash(name); + if (hash == -1) { + *error = -1; + goto done; + } + } + } res = _PyDict_GetItem_KnownHash(dict, name, hash); } else { + /* Every other combination is much less safe, so be conservative. */ res = PyObject_GetItem(dict, name); } if (res != NULL) From f09bfd3fe764ae5cfb18c2a8b334964c76566c91 Mon Sep 17 00:00:00 2001 From: Stefan Behnel Date: Mon, 11 Sep 2017 20:44:57 +0200 Subject: [PATCH 14/20] Revert "Lazily calculate name hash in find_name_in_mro() to avoid potential re-calculation if the mapping is not exactly a dict." This reverts commit 1d52082bb544de02901f922242942253497cba58. --- Objects/typeobject.c | 25 +++++++++++-------------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/Objects/typeobject.c b/Objects/typeobject.c index 374b2b2baf75e8..87c48785a3acc7 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -2950,7 +2950,17 @@ find_name_in_mro(PyTypeObject *type, PyObject *name, int *error) { Py_ssize_t i, n; PyObject *mro, *res, *base, *dict; - Py_hash_t hash = -1; + 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; @@ -2980,22 +2990,9 @@ find_name_in_mro(PyTypeObject *type, PyObject *name, int *error) assert(PyType_Check(base)); dict = ((PyTypeObject *)base)->tp_dict; assert(dict); - /* Optimise for the extremely common case: dict for lookup, unicode name. */ if (PyDict_CheckExact(dict)) { - if (hash == -1) { - if (!PyUnicode_CheckExact(name) || - (hash = ((PyASCIIObject *) name)->hash) == -1) - { - hash = PyObject_Hash(name); - if (hash == -1) { - *error = -1; - goto done; - } - } - } res = _PyDict_GetItem_KnownHash(dict, name, hash); } else { - /* Every other combination is much less safe, so be conservative. */ res = PyObject_GetItem(dict, name); } if (res != NULL) From 824d7cb25cd25368e9d04c5be5f75c8c790d8447 Mon Sep 17 00:00:00 2001 From: Stefan Behnel Date: Mon, 11 Sep 2017 20:45:05 +0200 Subject: [PATCH 15/20] Revert "Allow non-dict types for the class dict when looking up names and call PyObject_GetItem() for them." This reverts commit 09e716a2e8eda777400da779c5a4f1b5e90375f6. --- Objects/typeobject.c | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/Objects/typeobject.c b/Objects/typeobject.c index 87c48785a3acc7..17713a0750d7d4 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -2989,12 +2989,8 @@ find_name_in_mro(PyTypeObject *type, PyObject *name, int *error) base = PyTuple_GET_ITEM(mro, i); assert(PyType_Check(base)); dict = ((PyTypeObject *)base)->tp_dict; - assert(dict); - if (PyDict_CheckExact(dict)) { - res = _PyDict_GetItem_KnownHash(dict, name, hash); - } else { - res = PyObject_GetItem(dict, name); - } + assert(dict && PyDict_Check(dict)); + res = _PyDict_GetItem_KnownHash(dict, name, hash); if (res != NULL) break; if (PyErr_Occurred()) { From 51841915d6f5f87c7f3559c7c83d0b2ce8ffebff Mon Sep 17 00:00:00 2001 From: Stefan Behnel Date: Wed, 13 Sep 2017 12:42:14 +0200 Subject: [PATCH 16/20] add news entry for faster class creation --- .../Core and Builtins/2017-09-13-12-04-23.bpo-31336.gi2ahY.rst | 2 ++ 1 file changed, 2 insertions(+) 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 00000000000000..cd2f2b6cfb51b2 --- /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 reducing overhead in the necessary special method +lookups. Patch by Stefan Behnel. From 4efde8ea72195e987a7946985b22725b1d8f4e3a Mon Sep 17 00:00:00 2001 From: Stefan Behnel Date: Wed, 13 Sep 2017 15:04:43 +0200 Subject: [PATCH 17/20] Change nice interface of "find_name_in_mro()" to evil interface eating lookup exceptions, just like "_PyType_Lookup()" did previously. --- Objects/typeobject.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/Objects/typeobject.c b/Objects/typeobject.c index 17713a0750d7d4..83128173fce604 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -2993,10 +2993,9 @@ find_name_in_mro(PyTypeObject *type, PyObject *name, int *error) res = _PyDict_GetItem_KnownHash(dict, name, hash); if (res != NULL) break; - if (PyErr_Occurred()) { - *error = -1; - goto done; - } + /* _PyType_Lookup() ignored and cleared lookup errors and we keep this + bad behaviour, instead of returning NULL and setting error = -1. */ + PyErr_Clear(); } *error = 0; done: From f5bce2a5494ee5856677d6345a55254e6cff2607 Mon Sep 17 00:00:00 2001 From: Stefan Behnel Date: Wed, 13 Sep 2017 16:33:00 +0200 Subject: [PATCH 18/20] Mention amount of speedup in News entry. --- .../2017-09-13-12-04-23.bpo-31336.gi2ahY.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 index cd2f2b6cfb51b2..e62b065af17b5a 100644 --- 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 @@ -1,2 +1,2 @@ -Speed up class creation by reducing overhead in the necessary special method -lookups. Patch by Stefan Behnel. +Speed up class creation by 10-20% by reducing the overhead in the +necessary special method lookups. Patch by Stefan Behnel. From 02bfef0f8dbe7b51e6f3c231c0d3d7a2554687ca Mon Sep 17 00:00:00 2001 From: Stefan Behnel Date: Thu, 14 Sep 2017 10:12:06 +0200 Subject: [PATCH 19/20] Revert "Change nice interface of "find_name_in_mro()" to evil interface eating lookup exceptions, just like "_PyType_Lookup()" did previously." This reverts commit 4efde8ea72195e987a7946985b22725b1d8f4e3a. --- Objects/typeobject.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Objects/typeobject.c b/Objects/typeobject.c index 83128173fce604..17713a0750d7d4 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -2993,9 +2993,10 @@ find_name_in_mro(PyTypeObject *type, PyObject *name, int *error) res = _PyDict_GetItem_KnownHash(dict, name, hash); if (res != NULL) break; - /* _PyType_Lookup() ignored and cleared lookup errors and we keep this - bad behaviour, instead of returning NULL and setting error = -1. */ - PyErr_Clear(); + if (PyErr_Occurred()) { + *error = -1; + goto done; + } } *error = 0; done: From 24978587ccaebb90ac858c6285b6d793a47ed969 Mon Sep 17 00:00:00 2001 From: Stefan Behnel Date: Thu, 14 Sep 2017 10:16:52 +0200 Subject: [PATCH 20/20] Guard against external live exceptions when calling find_name_in_mro() from functions that swallow live exceptions. --- Objects/typeobject.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Objects/typeobject.c b/Objects/typeobject.c index 17713a0750d7d4..16cd9b8c08a5f3 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -3026,6 +3026,9 @@ _PyType_Lookup(PyTypeObject *type, PyObject *name) } } + /* 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) { @@ -7001,6 +7004,8 @@ 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 { /* 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);