I think I finally found the reason. _PyObject_GenericGetAttrWithDict is the key function. The normal test result is : spend time: 0.6592750549316406.
If I delete this code:
set_attribute_error_context(obj, name);
The result will change to: spend time: 0.47563862800598145.
And if I delete raising exception:
// PyErr_Format(PyExc_AttributeError,
// "'%.50s' object has no attribute '%U'",
// tp->tp_name, name);
it will become much faster: spend time: 0.19989752769470215.
If Python3 fails finding an attribute in normal ways, it will return NULL and raise an exception. Bug raising an exception has performance cost. Python3.11.1 add set_attribute_error_context to support ine Grained Error Locations in Tracebacks. It makes things worser.
Back to this question, when we define __getattr__, failed to find an attribute is what we expected. If we can get this result and then call __getattr__ without exception handling, it will be faster.
I tried to modify Python3.11.1 like this:
- add a new function in
object.c:
PyObject *
PyObject_GenericTryGetAttr(PyObject *obj, PyObject *name)
{
return _PyObject_GenericGetAttrWithDict(obj, name, NULL, 1);
}
- change
typeobject.c :
if (getattribute == NULL ||
(Py_IS_TYPE(getattribute, &PyWrapperDescr_Type) &&
((PyWrapperDescrObject *)getattribute)->d_wrapped ==
(void *)PyObject_GenericGetAttr))
// res = PyObject_GenericGetAttr(self, name);
res = PyObject_GenericTryGetAttr(self, name);
else {
Py_INCREF(getattribute);
res = call_attribute(self, getattribute, name);
Py_DECREF(getattribute);
}
if (res == NULL) {
if (PyErr_ExceptionMatches(PyExc_AttributeError))
PyErr_Clear();
res = call_attribute(self, getattr, name);
}
Py_DECREF(getattr);
return res;
Rebuild python, it really become faster: spend time: 0.13772845268249512.