A modest speedup of object deallocation. call_finalizer() did rather
a lot of work: it had to save and restore the current exception around
a call to lookup_maybe(), because that could fail in rare cases, and
most objects don't have a __del__ method, so the whole exercise was
usually a waste of time. Changed this to cache the __del__ method in
the type object just like all other special methods, in a new slot
tp_del. So now subtype_dealloc() can test whether tp_del is NULL and
skip the whole exercise if it is. The new slot doesn't need a new
flag bit: subtype_dealloc() is only called if the type was dynamically
allocated by type_new(), so it's guaranteed to have all current slots.
Types defined in C cannot fill in tp_del with a function of their own,
so there's no corresponding "wrapper". (That functionality is already
available through tp_dealloc.)
diff --git a/Objects/typeobject.c b/Objects/typeobject.c
index d494344..edf4e70 100644
--- a/Objects/typeobject.c
+++ b/Objects/typeobject.c
@@ -356,68 +356,6 @@
return 0;
}
-static PyObject *lookup_maybe(PyObject *, char *, PyObject **);
-
-static int
-call_finalizer(PyObject *self)
-{
- static PyObject *del_str = NULL;
- PyObject *del, *res;
- PyObject *error_type, *error_value, *error_traceback;
-
- /* Temporarily resurrect the object. */
- assert(self->ob_refcnt == 0);
- self->ob_refcnt = 1;
-
- /* Save the current exception, if any. */
- PyErr_Fetch(&error_type, &error_value, &error_traceback);
-
- /* Execute __del__ method, if any. */
- del = lookup_maybe(self, "__del__", &del_str);
- if (del != NULL) {
- res = PyEval_CallObject(del, NULL);
- if (res == NULL)
- PyErr_WriteUnraisable(del);
- else
- Py_DECREF(res);
- Py_DECREF(del);
- }
-
- /* Restore the saved exception. */
- PyErr_Restore(error_type, error_value, error_traceback);
-
- /* Undo the temporary resurrection; can't use DECREF here, it would
- * cause a recursive call.
- */
- assert(self->ob_refcnt > 0);
- if (--self->ob_refcnt == 0)
- return 0; /* this is the normal path out */
-
- /* __del__ resurrected it! Make it look like the original Py_DECREF
- * never happened.
- */
- {
- int refcnt = self->ob_refcnt;
- _Py_NewReference(self);
- self->ob_refcnt = refcnt;
- }
- assert(!PyType_IS_GC(self->ob_type) ||
- _Py_AS_GC(self)->gc.gc_refs != _PyGC_REFS_UNTRACKED);
- /* If Py_REF_DEBUG, the original decref dropped _Py_RefTotal, but
- * _Py_NewReference bumped it again, so that's a wash.
- * If Py_TRACE_REFS, _Py_NewReference re-added self to the object
- * chain, so no more to do there either.
- * If COUNT_ALLOCS, the original decref bumped tp_frees, and
- * _Py_NewReference bumped tp_allocs: both of those need to be
- * undone.
- */
-#ifdef COUNT_ALLOCS
- --self->ob_type->tp_frees;
- --self->ob_type->tp_allocs;
-#endif
- return -1; /* __del__ added a reference; don't delete now */
-}
-
static void
subtype_dealloc(PyObject *self)
{
@@ -438,8 +376,11 @@
clear_slots(), or DECREF the dict, or clear weakrefs. */
/* Maybe call finalizer; exit early if resurrected */
- if (call_finalizer(self) < 0)
- return;
+ if (type->tp_del) {
+ type->tp_del(self);
+ if (self->ob_refcnt > 0)
+ return;
+ }
/* Find the nearest base with a different tp_dealloc */
base = type;
@@ -468,8 +409,11 @@
_PyObject_GC_TRACK(self); /* We'll untrack for real later */
/* Maybe call finalizer; exit early if resurrected */
- if (call_finalizer(self) < 0)
- goto endlabel;
+ if (type->tp_del) {
+ type->tp_del(self);
+ if (self->ob_refcnt > 0)
+ goto endlabel;
+ }
/* Find the nearest base with a different tp_dealloc
and clear slots while we're at it */
@@ -3721,6 +3665,65 @@
return x;
}
+static void
+slot_tp_del(PyObject *self)
+{
+ static PyObject *del_str = NULL;
+ PyObject *del, *res;
+ PyObject *error_type, *error_value, *error_traceback;
+
+ /* Temporarily resurrect the object. */
+ assert(self->ob_refcnt == 0);
+ self->ob_refcnt = 1;
+
+ /* Save the current exception, if any. */
+ PyErr_Fetch(&error_type, &error_value, &error_traceback);
+
+ /* Execute __del__ method, if any. */
+ del = lookup_maybe(self, "__del__", &del_str);
+ if (del != NULL) {
+ res = PyEval_CallObject(del, NULL);
+ if (res == NULL)
+ PyErr_WriteUnraisable(del);
+ else
+ Py_DECREF(res);
+ Py_DECREF(del);
+ }
+
+ /* Restore the saved exception. */
+ PyErr_Restore(error_type, error_value, error_traceback);
+
+ /* Undo the temporary resurrection; can't use DECREF here, it would
+ * cause a recursive call.
+ */
+ assert(self->ob_refcnt > 0);
+ if (--self->ob_refcnt == 0)
+ return; /* this is the normal path out */
+
+ /* __del__ resurrected it! Make it look like the original Py_DECREF
+ * never happened.
+ */
+ {
+ int refcnt = self->ob_refcnt;
+ _Py_NewReference(self);
+ self->ob_refcnt = refcnt;
+ }
+ assert(!PyType_IS_GC(self->ob_type) ||
+ _Py_AS_GC(self)->gc.gc_refs != _PyGC_REFS_UNTRACKED);
+ /* If Py_REF_DEBUG, the original decref dropped _Py_RefTotal, but
+ * _Py_NewReference bumped it again, so that's a wash.
+ * If Py_TRACE_REFS, _Py_NewReference re-added self to the object
+ * chain, so no more to do there either.
+ * If COUNT_ALLOCS, the original decref bumped tp_frees, and
+ * _Py_NewReference bumped tp_allocs: both of those need to be
+ * undone.
+ */
+#ifdef COUNT_ALLOCS
+ --self->ob_type->tp_frees;
+ --self->ob_type->tp_allocs;
+#endif
+}
+
/* Table mapping __foo__ names to tp_foo offsets and slot_tp_foo wrapper
functions. The offsets here are relative to the 'etype' structure, which
@@ -3949,6 +3952,7 @@
"see x.__class__.__doc__ for signature",
PyWrapperFlag_KEYWORDS),
TPSLOT("__new__", tp_new, slot_tp_new, NULL, ""),
+ TPSLOT("__del__", tp_del, slot_tp_del, NULL, ""),
{NULL}
};