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}
 };