8000 bpo-44897: WIP: Integrate trashcan into _Py_Dealloc by nascheme · Pull Request #27738 · python/cpython · GitHub
[go: up one dir, main page]

Skip to content

bpo-44897: WIP: Integrate trashcan into _Py_Dealloc #27738

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 5 commits into from
Closed
Changes from 1 commit
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
Prev Previous commit
Next Next commit
Small optimization for _Py_Dealloc().
  • Loading branch information
nascheme committed Aug 16, 2021
commit 6ce59ff98caf045b76751a1c65262ef222924497
91 changes: 49 additions & 42 deletions Objects/object.c
8000 544C
Original file line number Diff line number Diff line change
Expand Up @@ -249,7 +249,7 @@ PyObject_CallFinalizerFromDealloc(PyObject *self)
_Py_NewReference(self);
Py_SET_REFCNT(self, refcnt);

if (_PyType_IS_GC(Py_TYPE(self))) {
if (_PyObject_IS_GC(self)) {
/* If we get here, it must have originally been tracked when
* _Py_Dealloc was called. Make it tracked again so it can't become
* part of a reference cycle and leak.
Expand Down Expand Up @@ -2102,16 +2102,16 @@ chain of deallocations, as each Py_DECREF in turn drops the refcount on "the
next" object in the chain to 0. This can easily lead to stack overflows,
especially in threads (which typically have less stack space to work with).

We avoid this by checking the nesting level of _PyTrash_dealloc calls.
We avoid this by checking the nesting level of dealloc_gc() calls.

How it works: Entering _PyTrash_dealloc increments a call-depth counter. So
How it works: Entering dealloc_gc() increments a call-depth counter. So
long as this counter is small, the dealloc method of the type is run directly
without further ado. But if the counter gets large, it instead adds the object
to a list of objects to be deallocated later and skips calling the deallocator.
In that case, _PyTrash_dealloc returns without deallocating anything (and so
In that case, dealloc_gc() returns without deallocating anything (and so
unbounded call-stack depth is avoided).

When the call stack finishes unwinding again, the _PyTrash_dealloc function
When the call stack finishes unwinding again, the dealloc_gc() function
notices this, and calls another routine to deallocate all the objects that
may have been added to the list of deferred deallocations. In effect, a
chain of N deallocations is broken into (N-1)/(_PyTrash_UNWIND_LEVEL-1) pieces,
Expand All @@ -2130,7 +2130,7 @@ _PyTrash_thread_deposit_object(PyObject *op)
{
PyThreadState *tstate = _PyThreadState_GET();
_PyObject_ASSERT(op, _PyObject_IS_GC(op));
/* untrack is done by _PyTrash_dealloc() so the following assert must be
/* untrack is done by dealloc_gc()() so the following assert must be
* true. Previously Py_TRASHCAN_BEGIN() did not untrack itself and it was
* the responsibility of the users of the trashcan mechanism to ensure that
* untrack was called first.
Expand Down Expand Up @@ -2200,30 +2200,6 @@ _PyTrash_cond(PyObject *op, destructor dealloc)
return 0;
}

static void
_PyTrash_dealloc(PyObject *op)
{
if (_PyObject_GC_IS_TRACKED(op)) {
// The trash list re-uses the GC prev pointer and so we must untrack
// the object first, if it is tracked.
_PyObject_GC_UNTRACK(op);
}
PyThreadState *tstate = PyThreadState_Get();
if (tstate->trash_delete_nesting >= _PyTrash_UNWIND_LEVEL) {
/* Store the object (to be deallocated later) */
_PyTrash_thread_deposit_object(op);
}
else {
++tstate->trash_delete_nesting;
destructor dealloc = Py_TYPE(op)->tp_dealloc;
(*dealloc)(op);
--tstate->trash_delete_nesting;
if (tstate->trash_delete_later && tstate->trash_delete_nesting <= 0) {
_PyTrash_thread_destroy_chain();
}
}
}

void _Py_NO_RETURN
_PyObject_AssertFailed(PyObject *obj, const char *expr, const char *msg,
const char *file, int line, const char *function)
Expand Down Expand Up @@ -2279,30 +2255,61 @@ _PyObject_AssertFailed(PyObject *obj, const char *expr, const char *msg,
Py_FatalError("_PyObject_AssertFailed");
}

static void
dealloc_gc(PyObject *op)
{
if (_PyObject_GC_IS_TRACKED(op)) {
// The trash list re-uses the GC prev pointer and so we must untrack
// the object first, if it is tracked.
_PyObject_GC_UNTRACK(op);
}
PyThreadState *tstate = PyThreadState_Get();
if (tstate->trash_delete_nesting >= _PyTrash_UNWIND_LEVEL) {
/* Store the object (to be deallocated later) */
_PyTrash_thread_deposit_object(op);
}
else {
++tstate->trash_delete_nesting;
destructor dealloc = Py_TYPE(op)->tp_dealloc;
(*dealloc)(op);
--tstate->trash_delete_nesting;
if (tstate->trash_delete_later && tstate->trash_delete_nesting <= 0) {
_PyTrash_thread_destroy_chain();
}
}
}

static void
dealloc_simple(PyObject *op)
{
PyTypeObject *type = Py_TYPE(op);
destructor dealloc = type->tp_dealloc;
(*dealloc)(op);
}

void
_Py_Dealloc(PyObject *op)
{
#ifdef Py_TRACE_REFS
_Py_ForgetReference(op);
#endif
// Note: it might be safe to use _PyType_IS_GC() here, for a small
// optimization. The question is if a type that implements tp_is_gc() that
// returns false can ever be deallocated. For PyType_Type, the answer
// seems to be no since those objects are statically allocated. However,
// it seems possible that some 3rd party type could have a heap allocated
// instance where tp_is_gc() returns false.
if (_PyObject_IS_GC(op)) {
_PyTrash_dealloc(op);
// Note: using _PyType_IS_GC() here is probably not safe. A 3rd party
// extension type might have a tp_is_gc() that returns false for heap
// allocated instances. It doesn't seem likely but it's possible.
PyTypeObject *type = Py_TYPE(op);
if (!_PyType_IS_GC(type)) {
dealloc_simple(op);
}
else {
PyTypeObject *type = Py_TYPE(op);
destructor dealloc = type->tp_dealloc;
(*dealloc)(op);
if (type->tp_is_gc == NULL || type->tp_is_gc(op)) {
dealloc_gc(op);
}
else {
dealloc_simple(op);
}
}
}


PyObject **
PyObject_GET_WEAKREFS_LISTPTR(PyObject *op)
{
Expand Down
0