From febd61dc02f0edbe9aa746fabde8ce97fbbeabdb Mon Sep 17 00:00:00 2001 From: Guido van Rossum Date: Thu, 8 Aug 2002 20:55:20 +0000 Subject: 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.) --- Include/object.h | 1 + Objects/typeobject.c | 136 ++++++++++++++++++++++++++------------------------- 2 files changed, 71 insertions(+), 66 deletions(-) diff --git a/Include/object.h b/Include/object.h index 19460fe..d045be1 100644 --- a/Include/object.h +++ b/Include/object.h @@ -315,6 +315,7 @@ typedef struct _typeobject { PyObject *tp_cache; PyObject *tp_subclasses; PyObject *tp_weaklist; + destructor tp_del; #ifdef COUNT_ALLOCS /* these must be last and never explicitly initialized */ 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 @@ subtype_clear(PyObject *self) 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 @@ subtype_dealloc(PyObject *self) 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 @@ subtype_dealloc(PyObject *self) _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 @@ slot_tp_new(PyTypeObject *type, PyObject *args, PyObject *kwds) 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 @@ static slotdef slotdefs[] = { "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} }; -- cgit v0.12