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

Raymond Hettinger webhook-mailer at python.org
Wed Feb 20 12:29:27 EST 2019


https://github.com/python/cpython/commit/d8b9e1fc2e45d2bc3f4a9737c375f2adb8a8c7de
commit: d8b9e1fc2e45d2bc3f4a9737c375f2adb8a8c7de
branch: master
author: Stefan Behnel <stefan_ml at behnel.de>
committer: Raymond Hettinger <rhettinger at users.noreply.github.com>
date: 2019-02-20T09:29:24-08:00
summary:

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

files:
A Misc/NEWS.d/next/Core and Builtins/2019-02-19-10-47-51.bpo-36012.xq7C9E.rst
M Doc/whatsnew/3.8.rst
M Objects/typeobject.c

diff --git a/Doc/whatsnew/3.8.rst b/Doc/whatsnew/3.8.rst
index 6bc131419ca8..66e1c13cccce 100644
--- a/Doc/whatsnew/3.8.rst
+++ b/Doc/whatsnew/3.8.rst
@@ -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,
+  Neil Schemenauer, and Serhiy Storchaka in :issue:`36012`.)
+
 
 Build and C API Changes
 =======================
diff --git a/Misc/NEWS.d/next/Core and Builtins/2019-02-19-10-47-51.bpo-36012.xq7C9E.rst b/Misc/NEWS.d/next/Core and Builtins/2019-02-19-10-47-51.bpo-36012.xq7C9E.rst
new file mode 100644
index 000000000000..ff3fdbf79c6e
--- /dev/null
+++ b/Misc/NEWS.d/next/Core and Builtins/2019-02-19-10-47-51.bpo-36012.xq7C9E.rst	
@@ -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.
diff --git a/Objects/typeobject.c b/Objects/typeobject.c
index e6cf4fbf30ae..423472663e21 100644
--- a/Objects/typeobject.c
+++ b/Objects/typeobject.c
@@ -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 *
@@ -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 {
@@ -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);
@@ -7236,13 +7265,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++) {
@@ -7281,6 +7303,9 @@ update_all_slots(PyTypeObject* type)
 {
     slotdef *p;
 
+    /* Clear the VALID_VERSION flag of 'type' and all its subclasses. */
+    PyType_Modified(type);
+
     init_slotdefs();
     for (p = slotdefs; p->name; p++) {
         /* update_slot returns int but can't actually fail */



More information about the Python-checkins mailing list