GH-140638: Add a GC "candidates" stat (GH-141814)

This commit is contained in:
Brandt Bucher 2025-11-22 13:59:14 -08:00 committed by GitHub
parent 425fd85ca3
commit 227b9d326e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 50 additions and 20 deletions

View file

@ -483,11 +483,12 @@ validate_consistent_old_space(PyGC_Head *head)
/* Set all gc_refs = ob_refcnt. After this, gc_refs is > 0 and
* PREV_MASK_COLLECTING bit is set for all objects in containers.
*/
static void
static Py_ssize_t
update_refs(PyGC_Head *containers)
{
PyGC_Head *next;
PyGC_Head *gc = GC_NEXT(containers);
Py_ssize_t candidates = 0;
while (gc != containers) {
next = GC_NEXT(gc);
@ -519,7 +520,9 @@ update_refs(PyGC_Head *containers)
*/
_PyObject_ASSERT(op, gc_get_refs(gc) != 0);
gc = next;
candidates++;
}
return candidates;
}
/* A traversal callback for subtract_refs. */
@ -1240,7 +1243,7 @@ flag set but it does not clear it to skip unnecessary iteration. Before the
flag is cleared (for example, by using 'clear_unreachable_mask' function or
by a call to 'move_legacy_finalizers'), the 'unreachable' list is not a normal
list and we can not use most gc_list_* functions for it. */
static inline void
static inline Py_ssize_t
deduce_unreachable(PyGC_Head *base, PyGC_Head *unreachable) {
validate_list(base, collecting_clear_unreachable_clear);
/* Using ob_refcnt and gc_refs, calculate which objects in the
@ -1248,7 +1251,7 @@ deduce_unreachable(PyGC_Head *base, PyGC_Head *unreachable) {
* refcount greater than 0 when all the references within the
* set are taken into account).
*/
update_refs(base); // gc_prev is used for gc_refs
Py_ssize_t candidates = update_refs(base); // gc_prev is used for gc_refs
subtract_refs(base);
/* Leave everything reachable from outside base in base, and move
@ -1289,6 +1292,7 @@ deduce_unreachable(PyGC_Head *base, PyGC_Head *unreachable) {
move_unreachable(base, unreachable); // gc_prev is pointer again
validate_list(base, collecting_clear_unreachable_clear);
validate_list(unreachable, collecting_set_unreachable_set);
return candidates;
}
/* Handle objects that may have resurrected after a call to 'finalize_garbage', moving
@ -1366,6 +1370,7 @@ add_stats(GCState *gcstate, int gen, struct gc_collection_stats *stats)
gcstate->generation_stats[gen].duration += stats->duration;
gcstate->generation_stats[gen].collected += stats->collected;
gcstate->generation_stats[gen].uncollectable += stats->uncollectable;
gcstate->generation_stats[gen].candidates += stats->candidates;
gcstate->generation_stats[gen].collections += 1;
}
@ -1662,6 +1667,7 @@ gc_collect_increment(PyThreadState *tstate, struct gc_collection_stats *stats)
Py_ssize_t objects_marked = mark_at_start(tstate);
GC_STAT_ADD(1, objects_transitively_reachable, objects_marked);
gcstate->work_to_do -= objects_marked;
stats->candidates += objects_marked;
validate_spaces(gcstate);
return;
}
@ -1754,7 +1760,7 @@ gc_collect_region(PyThreadState *tstate,
assert(!_PyErr_Occurred(tstate));
gc_list_init(&unreachable);
deduce_unreachable(from, &unreachable);
stats->candidates = deduce_unreachable(from, &unreachable);
validate_consistent_old_space(from);
untrack_tuples(from);
@ -1844,10 +1850,11 @@ do_gc_callback(GCState *gcstate, const char *phase,
assert(PyList_CheckExact(gcstate->callbacks));
PyObject *info = NULL;
if (PyList_GET_SIZE(gcstate->callbacks) != 0) {
info = Py_BuildValue("{sisnsnsd}",
info = Py_BuildValue("{sisnsnsnsd}",
"generation", generation,
"collected", stats->collected,
"uncollectable", stats->uncollectable,
"candidates", stats->candidates,
"duration", stats->duration);
if (info == NULL) {
PyErr_FormatUnraisable("Exception ignored while invoking gc callbacks");