Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions Doc/whatsnew/3.8.rst
Original file line number Diff line number Diff line change
Expand Up @@ -375,6 +375,11 @@ Optimizations
This makes the created list 12% smaller on average. (Contributed by
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,
Comment thread
scoder marked this conversation as resolved.
Neil Schemenauer, and Serhiy Storchaka in :issue:`36012`.)


Build and C API Changes
=======================
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
51 changes: 38 additions & 13 deletions Objects/typeobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -3164,6 +3164,24 @@ _PyType_LookupId(PyTypeObject *type, struct _Py_Identifier *name)
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(),
but uses _PyType_Lookup() instead of just looking in type->tp_dict. */
static PyObject *
Expand Down Expand Up @@ -3275,12 +3293,14 @@ type_setattro(PyTypeObject *type, PyObject *name, PyObject *value)
if (name == NULL)
return -1;
}
PyUnicode_InternInPlace(&name);
if (!PyUnicode_CHECK_INTERNED(name)) {
PyErr_SetString(PyExc_MemoryError,
"Out of memory interning an attribute name");
Py_DECREF(name);
return -1;
PyUnicode_InternInPlace(&name);
if (!PyUnicode_CHECK_INTERNED(name)) {
PyErr_SetString(PyExc_MemoryError,
"Out of memory interning an attribute name");
Py_DECREF(name);
return -1;
}
}
}
else {
Expand All @@ -3289,7 +3309,16 @@ type_setattro(PyTypeObject *type, PyObject *name, PyObject *value)
}
res = _PyObject_GenericSetAttrWithDict((PyObject *)type, name, value, NULL);
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));
}
Py_DECREF(name);
Expand Down Expand Up @@ -7233,13 +7262,6 @@ update_slot(PyTypeObject *type, PyObject *name)
assert(PyUnicode_CheckExact(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();
pp = ptrs;
for (p = slotdefs; p->name; p++) {
Expand Down Expand Up @@ -7278,6 +7300,9 @@ update_all_slots(PyTypeObject* type)
{
slotdef *p;

/* Clear the VALID_VERSION flag of 'type' and all its subclasses. */
PyType_Modified(type);
Comment thread
rhettinger marked this conversation as resolved.

init_slotdefs();
for (p = slotdefs; p->name; p++) {
/* update_slot returns int but can't actually fail */
Expand Down