From 91fa05e41a29b872a1ee8b6903bcf939b2486fc8 Mon Sep 17 00:00:00 2001 From: Ken Jin Date: Sat, 18 Jun 2022 00:25:43 +0800 Subject: [PATCH 1/6] Use unbound methods for slot `__getattr__` --- Objects/typeobject.c | 27 +++++++++++---------------- 1 file changed, 11 insertions(+), 16 deletions(-) diff --git a/Objects/typeobject.c b/Objects/typeobject.c index db4682c69ed0e3..0f25600d6204ac 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -7805,38 +7805,33 @@ slot_tp_getattr_hook(PyObject *self, PyObject *name) { PyTypeObject *tp = Py_TYPE(self); PyObject *getattr, *getattribute, *res; + PyObject *args[] = { self, name }; + PyThreadState *tstate = _PyThreadState_GET(); - /* speed hack: we could use lookup_maybe, but that would resolve the - method fully for each attribute lookup for classes with - __getattr__, even when the attribute is present. So we use - _PyType_Lookup and create the method only when needed, with - call_attribute. */ - getattr = _PyType_Lookup(tp, &_Py_ID(__getattr__)); + int unbound_getattr; + getattr = lookup_maybe_method(self, &_Py_ID(__getattr__), &unbound_getattr); if (getattr == NULL) { /* No __getattr__ hook: use a simpler dispatcher */ tp->tp_getattro = slot_tp_getattro; return slot_tp_getattro(self, name); } - Py_INCREF(getattr); - /* speed hack: we could use lookup_maybe, but that would resolve the - method fully for each attribute lookup for classes with - __getattr__, even when self has the default __getattribute__ - method. So we use _PyType_Lookup and create the method only when - needed, with call_attribute. */ - getattribute = _PyType_Lookup(tp, &_Py_ID(__getattribute__)); + + int unbound_getattribute; + getattribute = lookup_maybe_method(self, &_Py_ID(__getattribute__), + &unbound_getattribute); if (getattribute == NULL || (Py_IS_TYPE(getattribute, &PyWrapperDescr_Type) && ((PyWrapperDescrObject *)getattribute)->d_wrapped == (void *)PyObject_GenericGetAttr)) res = PyObject_GenericGetAttr(self, name); else { - Py_INCREF(getattribute); - res = call_attribute(self, getattribute, name); + res = vectorcall_unbound(tstate, unbound_getattribute, getattribute, + args, 2); Py_DECREF(getattribute); } if (res == NULL && PyErr_ExceptionMatches(PyExc_AttributeError)) { PyErr_Clear(); - res = call_attribute(self, getattr, name); + res = vectorcall_unbound(tstate, unbound_getattr, getattr, args, 2); } Py_DECREF(getattr); return res; From 4c28ac63bc1002bd21459bbe0c8e38032a47cfae Mon Sep 17 00:00:00 2001 From: "blurb-it[bot]" <43283697+blurb-it[bot]@users.noreply.github.com> Date: Fri, 17 Jun 2022 16:30:25 +0000 Subject: [PATCH 2/6] =?UTF-8?q?=F0=9F=93=9C=F0=9F=A4=96=20Added=20by=20blu?= =?UTF-8?q?rb=5Fit.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../2022-06-17-16-30-24.gh-issue-93955.LmiAe9.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 Misc/NEWS.d/next/Core and Builtins/2022-06-17-16-30-24.gh-issue-93955.LmiAe9.rst diff --git a/Misc/NEWS.d/next/Core and Builtins/2022-06-17-16-30-24.gh-issue-93955.LmiAe9.rst b/Misc/NEWS.d/next/Core and Builtins/2022-06-17-16-30-24.gh-issue-93955.LmiAe9.rst new file mode 100644 index 00000000000000..3b2f0e8c32d745 --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2022-06-17-16-30-24.gh-issue-93955.LmiAe9.rst @@ -0,0 +1 @@ +Improve performance of attribute lookups on objects with custom ``__getattribute__`` and ``__getattr__``. Patch by Ken Jin. From c2b7441ef6a5e82335bfbe6a94a015927a663183 Mon Sep 17 00:00:00 2001 From: Ken Jin Date: Sat, 18 Jun 2022 00:34:13 +0800 Subject: [PATCH 3/6] fix refleak --- Objects/typeobject.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Objects/typeobject.c b/Objects/typeobject.c index 0f25600d6204ac..38a5a9f6760c23 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -7827,8 +7827,8 @@ slot_tp_getattr_hook(PyObject *self, PyObject *name) else { res = vectorcall_unbound(tstate, unbound_getattribute, getattribute, args, 2); - Py_DECREF(getattribute); } + Py_XDECREF(getattribute); if (res == NULL && PyErr_ExceptionMatches(PyExc_AttributeError)) { PyErr_Clear(); res = vectorcall_unbound(tstate, unbound_getattr, getattr, args, 2); From 83809babba2344d18f5a2ba78e3df2bc062be4b9 Mon Sep 17 00:00:00 2001 From: Ken Jin Date: Sat, 18 Jun 2022 00:38:46 +0800 Subject: [PATCH 4/6] Remove call_attribute --- Objects/typeobject.c | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/Objects/typeobject.c b/Objects/typeobject.c index 38a5a9f6760c23..fb284dc9324d54 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -7782,24 +7782,6 @@ slot_tp_getattro(PyObject *self, PyObject *name) return vectorcall_method(&_Py_ID(__getattribute__), stack, 2); } -static PyObject * -call_attribute(PyObject *self, PyObject *attr, PyObject *name) -{ - PyObject *res, *descr = NULL; - descrgetfunc f = Py_TYPE(attr)->tp_descr_get; - - if (f != NULL) { - descr = f(attr, self, (PyObject *)(Py_TYPE(self))); - if (descr == NULL) - return NULL; - else - attr = descr; - } - res = PyObject_CallOneArg(attr, name); - Py_XDECREF(descr); - return res; -} - static PyObject * slot_tp_getattr_hook(PyObject *self, PyObject *name) { From c83149da2d54157401a0e04133e3b45640096446 Mon Sep 17 00:00:00 2001 From: Ken Jin Date: Sat, 18 Jun 2022 02:37:56 +0800 Subject: [PATCH 5/6] re-add speed hacks --- Objects/typeobject.c | 54 ++++++++++++++++++++++++++++++++++---------- 1 file changed, 42 insertions(+), 12 deletions(-) diff --git a/Objects/typeobject.c b/Objects/typeobject.c index fb284dc9324d54..632b4450260b4e 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -7782,38 +7782,68 @@ slot_tp_getattro(PyObject *self, PyObject *name) return vectorcall_method(&_Py_ID(__getattribute__), stack, 2); } +static inline PyObject * +call_attribute(PyObject *self, PyObject *attr, PyObject *name) +{ + PyObject *res, *descr = NULL; + + if (_PyType_HasFeature(Py_TYPE(attr), Py_TPFLAGS_METHOD_DESCRIPTOR)) { + PyObject *args[] = { self, name }; + res = PyObject_Vectorcall(attr, args, 2, NULL); + return res; + } + + descrgetfunc f = Py_TYPE(attr)->tp_descr_get; + + if (f != NULL) { + descr = f(attr, self, (PyObject *)(Py_TYPE(self))); + if (descr == NULL) + return NULL; + else + attr = descr; + } + res = PyObject_CallOneArg(attr, name); + Py_XDECREF(descr); + return res; +} + static PyObject * slot_tp_getattr_hook(PyObject *self, PyObject *name) { PyTypeObject *tp = Py_TYPE(self); PyObject *getattr, *getattribute, *res; - PyObject *args[] = { self, name }; - PyThreadState *tstate = _PyThreadState_GET(); - int unbound_getattr; - getattr = lookup_maybe_method(self, &_Py_ID(__getattr__), &unbound_getattr); + /* speed hack: we could use lookup_maybe, but that would resolve the + method fully for each attribute lookup for classes with + __getattr__, even when the attribute is present. So we use + _PyType_Lookup and create the method only when needed, with + call_attribute. */ + getattr = _PyType_Lookup(tp, &_Py_ID(__getattr__)); if (getattr == NULL) { /* No __getattr__ hook: use a simpler dispatcher */ tp->tp_getattro = slot_tp_getattro; return slot_tp_getattro(self, name); } - - int unbound_getattribute; - getattribute = lookup_maybe_method(self, &_Py_ID(__getattribute__), - &unbound_getattribute); + Py_INCREF(getattr); + /* speed hack: we could use lookup_maybe, but that would resolve the + method fully for each attribute lookup for classes with + __getattr__, even when the attribute is present. So we use + _PyType_Lookup and create the method only when needed, with + call_attribute. */ + getattribute = _PyType_Lookup(tp, &_Py_ID(__getattribute__)); if (getattribute == NULL || (Py_IS_TYPE(getattribute, &PyWrapperDescr_Type) && ((PyWrapperDescrObject *)getattribute)->d_wrapped == (void *)PyObject_GenericGetAttr)) res = PyObject_GenericGetAttr(self, name); else { - res = vectorcall_unbound(tstate, unbound_getattribute, getattribute, - args, 2); + Py_INCREF(getattribute); + res = call_attribute(self, getattribute, name); + Py_DECREF(getattribute); } - Py_XDECREF(getattribute); if (res == NULL && PyErr_ExceptionMatches(PyExc_AttributeError)) { PyErr_Clear(); - res = vectorcall_unbound(tstate, unbound_getattr, getattr, args, 2); + res = call_attribute(self, getattr, name); } Py_DECREF(getattr); return res; From 0e14cc7032478fd704af4f4be42560862d95bd29 Mon Sep 17 00:00:00 2001 From: Ken Jin Date: Sat, 18 Jun 2022 02:39:42 +0800 Subject: [PATCH 6/6] fix comment --- Objects/typeobject.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Objects/typeobject.c b/Objects/typeobject.c index 632b4450260b4e..513055168062f1 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -7827,9 +7827,9 @@ slot_tp_getattr_hook(PyObject *self, PyObject *name) Py_INCREF(getattr); /* speed hack: we could use lookup_maybe, but that would resolve the method fully for each attribute lookup for classes with - __getattr__, even when the attribute is present. So we use - _PyType_Lookup and create the method only when needed, with - call_attribute. */ + __getattr__, even when self has the default __getattribute__ + method. So we use _PyType_Lookup and create the method only when + needed, with call_attribute. */ getattribute = _PyType_Lookup(tp, &_Py_ID(__getattribute__)); if (getattribute == NULL || (Py_IS_TYPE(getattribute, &PyWrapperDescr_Type) &&