Skip to content

Commit d34a745

Browse files
committed
gh-129210: Skip second reachability check if no finalizers run
To handle objects potentially resurrected in finalizers it is necessary to repeat the reachability check on objects to be collected. If there are no finalizers run this can be skipped: in that case it is not possible for an object to be resurrected. In my environment and testing this gives a ~25-30% performance improvement for the gh-129210 micro-benchmark.
1 parent ccad61e commit d34a745

File tree

1 file changed

+16
-6
lines changed

1 file changed

+16
-6
lines changed

Python/gc.c

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1074,11 +1074,12 @@ handle_legacy_finalizers(PyThreadState *tstate,
10741074
* Note that this may remove some (or even all) of the objects from the
10751075
* list, due to refcounts falling to 0.
10761076
*/
1077-
static void
1077+
static int
10781078
finalize_garbage(PyThreadState *tstate, PyGC_Head *collectable)
10791079
{
10801080
destructor finalize;
10811081
PyGC_Head seen;
1082+
int finalizer_run = 0;
10821083

10831084
/* While we're going through the loop, `finalize(op)` may cause op, or
10841085
* other objects, to be reclaimed via refcounts falling to zero. So
@@ -1097,6 +1098,7 @@ finalize_garbage(PyThreadState *tstate, PyGC_Head *collectable)
10971098
if (!_PyGC_FINALIZED(op) &&
10981099
(finalize = Py_TYPE(op)->tp_finalize) != NULL)
10991100
{
1101+
finalizer_run = 1;
11001102
_PyGC_SET_FINALIZED(op);
11011103
Py_INCREF(op);
11021104
finalize(op);
@@ -1105,6 +1107,7 @@ finalize_garbage(PyThreadState *tstate, PyGC_Head *collectable)
11051107
}
11061108
}
11071109
gc_list_merge(&seen, collectable);
1110+
return finalizer_run;
11081111
}
11091112

11101113
/* Break reference cycles by clearing the containers involved. This is
@@ -1736,13 +1739,20 @@ gc_collect_region(PyThreadState *tstate,
17361739
validate_list(&unreachable, collecting_set_unreachable_clear);
17371740

17381741
/* Call tp_finalize on objects which have one. */
1739-
finalize_garbage(tstate, &unreachable);
1740-
/* Handle any objects that may have resurrected after the call
1741-
* to 'finalize_garbage' and continue the collection with the
1742-
* objects that are still unreachable */
1742+
int check_resurrected = finalize_garbage(tstate, &unreachable);
1743+
1744+
/* If no finalizers have run, no objects can have been resurrected: in that
1745+
* case skip the resurrection check. Otherwise we need to check and handle
1746+
* any objects that may have resurrected after the call to
1747+
* 'finalize_garbage' and continue the collection with the objects that are
1748+
* still unreachable */
17431749
PyGC_Head final_unreachable;
17441750
gc_list_init(&final_unreachable);
1745-
handle_resurrected_objects(&unreachable, &final_unreachable, to);
1751+
if (check_resurrected) {
1752+
handle_resurrected_objects(&unreachable, &final_unreachable, to);
1753+
} else {
1754+
gc_list_merge(&unreachable, &final_unreachable);
1755+
}
17461756

17471757
/* Call tp_clear on objects in the final_unreachable set. This will cause
17481758
* the reference cycles to be broken. It may also cause some objects

0 commit comments

Comments
 (0)