bpo-36012: Avoid linear slot search for non-dunder methods (GH-11907)

This commit is contained in:
Stefan Behnel 2019-02-20 18:29:24 +01:00 committed by Raymond Hettinger
parent b5409dacc4
commit d8b9e1fc2e
3 changed files with 45 additions and 13 deletions

View File

@ -375,6 +375,11 @@ Optimizations
This makes the created list 12% smaller on average. (Contributed by This makes the created list 12% smaller on average. (Contributed by
Raymond Hettinger and Pablo Galindo in :issue:`33234`.) Raymond Hettinger and Pablo Galindo in :issue:`33234`.)
* Doubled the speed of class variable writes. When a non-dunder attribute
was updated, there was an unnecessary call to update slots.
(Contributed by Stefan Behnel, Pablo Galindo Salgado, Raymond Hettinger,
Neil Schemenauer, and Serhiy Storchaka in :issue:`36012`.)
Build and C API Changes Build and C API Changes
======================= =======================

View File

@ -0,0 +1,2 @@
Doubled the speed of class variable writes. When a non-dunder attribute was
updated, there was an unnecessary call to update slots.

View File

@ -3164,6 +3164,24 @@ _PyType_LookupId(PyTypeObject *type, struct _Py_Identifier *name)
return _PyType_Lookup(type, oname); return _PyType_Lookup(type, oname);
} }
/* Check if the "readied" PyUnicode name
is a double-underscore special name. */
static int
is_dunder_name(PyObject *name)
{
Py_ssize_t length = PyUnicode_GET_LENGTH(name);
int kind = PyUnicode_KIND(name);
/* Special names contain at least "__x__" and are always ASCII. */
if (length > 4 && kind == PyUnicode_1BYTE_KIND) {
Py_UCS1 *characters = PyUnicode_1BYTE_DATA(name);
return (
((characters[length-2] == '_') && (characters[length-1] == '_')) &&
((characters[0] == '_') && (characters[1] == '_'))
);
}
return 0;
}
/* This is similar to PyObject_GenericGetAttr(), /* This is similar to PyObject_GenericGetAttr(),
but uses _PyType_Lookup() instead of just looking in type->tp_dict. */ but uses _PyType_Lookup() instead of just looking in type->tp_dict. */
static PyObject * static PyObject *
@ -3275,12 +3293,14 @@ type_setattro(PyTypeObject *type, PyObject *name, PyObject *value)
if (name == NULL) if (name == NULL)
return -1; return -1;
} }
PyUnicode_InternInPlace(&name);
if (!PyUnicode_CHECK_INTERNED(name)) { if (!PyUnicode_CHECK_INTERNED(name)) {
PyErr_SetString(PyExc_MemoryError, PyUnicode_InternInPlace(&name);
"Out of memory interning an attribute name"); if (!PyUnicode_CHECK_INTERNED(name)) {
Py_DECREF(name); PyErr_SetString(PyExc_MemoryError,
return -1; "Out of memory interning an attribute name");
Py_DECREF(name);
return -1;
}
} }
} }
else { else {
@ -3289,7 +3309,16 @@ type_setattro(PyTypeObject *type, PyObject *name, PyObject *value)
} }
res = _PyObject_GenericSetAttrWithDict((PyObject *)type, name, value, NULL); res = _PyObject_GenericSetAttrWithDict((PyObject *)type, name, value, NULL);
if (res == 0) { if (res == 0) {
res = update_slot(type, name); /* Clear the VALID_VERSION flag of 'type' and all its
subclasses. This could possibly be unified with the
update_subclasses() recursion in update_slot(), but carefully:
they each have their own conditions on which to stop
recursing into subclasses. */
PyType_Modified(type);
if (is_dunder_name(name)) {
res = update_slot(type, name);
}
assert(_PyType_CheckConsistency(type)); assert(_PyType_CheckConsistency(type));
} }
Py_DECREF(name); Py_DECREF(name);
@ -7236,13 +7265,6 @@ update_slot(PyTypeObject *type, PyObject *name)
assert(PyUnicode_CheckExact(name)); assert(PyUnicode_CheckExact(name));
assert(PyUnicode_CHECK_INTERNED(name)); assert(PyUnicode_CHECK_INTERNED(name));
/* Clear the VALID_VERSION flag of 'type' and all its
subclasses. This could possibly be unified with the
update_subclasses() recursion below, but carefully:
they each have their own conditions on which to stop
recursing into subclasses. */
PyType_Modified(type);
init_slotdefs(); init_slotdefs();
pp = ptrs; pp = ptrs;
for (p = slotdefs; p->name; p++) { for (p = slotdefs; p->name; p++) {
@ -7281,6 +7303,9 @@ update_all_slots(PyTypeObject* type)
{ {
slotdef *p; slotdef *p;
/* Clear the VALID_VERSION flag of 'type' and all its subclasses. */
PyType_Modified(type);
init_slotdefs(); init_slotdefs();
for (p = slotdefs; p->name; p++) { for (p = slotdefs; p->name; p++) {
/* update_slot returns int but can't actually fail */ /* update_slot returns int but can't actually fail */