Skip to content

Commit bb3fc94

Browse files
committed
Add a "visited" stat.
1 parent 3149d64 commit bb3fc94

File tree

4 files changed

+29
-13
lines changed

4 files changed

+29
-13
lines changed

Include/internal/pycore_interp_structs.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,8 @@ struct gc_collection_stats {
179179
Py_ssize_t collected;
180180
/* total number of uncollectable objects (put into gc.garbage) */
181181
Py_ssize_t uncollectable;
182+
// Total number of objects visited:
183+
Py_ssize_t visited;
182184
// Duration of the collection in seconds:
183185
double duration;
184186
};
@@ -191,6 +193,8 @@ struct gc_generation_stats {
191193
Py_ssize_t collected;
192194
/* total number of uncollectable objects (put into gc.garbage) */
193195
Py_ssize_t uncollectable;
196+
// Total number of objects visited:
197+
Py_ssize_t visited;
194198
// Duration of the collection in seconds:
195199
double duration;
196200
};

Modules/gcmodule.c

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -358,10 +358,11 @@ gc_get_stats_impl(PyObject *module)
358358
for (i = 0; i < NUM_GENERATIONS; i++) {
359359
PyObject *dict;
360360
st = &stats[i];
361-
dict = Py_BuildValue("{snsnsnsd}",
361+
dict = Py_BuildValue("{snsnsnsnsd}",
362362
"collections", st->collections,
363363
"collected", st->collected,
364364
"uncollectable", st->uncollectable,
365+
"visited", st->visited,
365366
"duration", st->duration
366367
);
367368
if (dict == NULL)

Python/gc.c

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -663,12 +663,13 @@ visit_reachable(PyObject *op, void *arg)
663663
* But _gc_next in unreachable list has NEXT_MASK_UNREACHABLE flag.
664664
* So we can not gc_list_* functions for unreachable until we remove the flag.
665665
*/
666-
static void
666+
static Py_ssize_t
667667
move_unreachable(PyGC_Head *young, PyGC_Head *unreachable)
668668
{
669669
// previous elem in the young list, used for restore gc_prev.
670670
PyGC_Head *prev = young;
671671
PyGC_Head *gc = GC_NEXT(young);
672+
Py_ssize_t visited = 0;
672673

673674
/* Invariants: all objects "to the left" of us in young are reachable
674675
* (directly or indirectly) from outside the young list as it was at entry.
@@ -683,6 +684,7 @@ move_unreachable(PyGC_Head *young, PyGC_Head *unreachable)
683684
/* Record which old space we are in, and set NEXT_MASK_UNREACHABLE bit for convenience */
684685
uintptr_t flags = NEXT_MASK_UNREACHABLE | (gc->_gc_next & _PyGC_NEXT_MASK_OLD_SPACE_1);
685686
while (gc != young) {
687+
visited++;
686688
if (gc_get_refs(gc)) {
687689
/* gc is definitely reachable from outside the
688690
* original 'young'. Mark it as such, and traverse
@@ -739,6 +741,7 @@ move_unreachable(PyGC_Head *young, PyGC_Head *unreachable)
739741
young->_gc_next &= _PyGC_PREV_MASK;
740742
// don't let the pollution of the list head's next pointer leak
741743
unreachable->_gc_next &= _PyGC_PREV_MASK;
744+
return visited;
742745
}
743746

744747
/* In theory, all tuples should be younger than the
@@ -1240,7 +1243,7 @@ flag set but it does not clear it to skip unnecessary iteration. Before the
12401243
flag is cleared (for example, by using 'clear_unreachable_mask' function or
12411244
by a call to 'move_legacy_finalizers'), the 'unreachable' list is not a normal
12421245
list and we can not use most gc_list_* functions for it. */
1243-
static inline void
1246+
static inline Py_ssize_t
12441247
deduce_unreachable(PyGC_Head *base, PyGC_Head *unreachable) {
12451248
validate_list(base, collecting_clear_unreachable_clear);
12461249
/* Using ob_refcnt and gc_refs, calculate which objects in the
@@ -1286,9 +1289,10 @@ deduce_unreachable(PyGC_Head *base, PyGC_Head *unreachable) {
12861289
* the reachable objects instead. But this is a one-time cost, probably not
12871290
* worth complicating the code to speed just a little.
12881291
*/
1289-
move_unreachable(base, unreachable); // gc_prev is pointer again
1292+
Py_ssize_t visited = move_unreachable(base, unreachable); // gc_prev is pointer again
12901293
validate_list(base, collecting_clear_unreachable_clear);
12911294
validate_list(unreachable, collecting_set_unreachable_set);
1295+
return visited;
12921296
}
12931297

12941298
/* Handle objects that may have resurrected after a call to 'finalize_garbage', moving
@@ -1316,7 +1320,7 @@ handle_resurrected_objects(PyGC_Head *unreachable, PyGC_Head* still_unreachable,
13161320
// have the PREV_MARK_COLLECTING set, but the objects are going to be
13171321
// removed so we can skip the expense of clearing the flag.
13181322
PyGC_Head* resurrected = unreachable;
1319-
deduce_unreachable(resurrected, still_unreachable);
1323+
(void)deduce_unreachable(resurrected, still_unreachable);
13201324
clear_unreachable_mask(still_unreachable);
13211325

13221326
// Move the resurrected objects to the old generation for future collection.
@@ -1364,6 +1368,7 @@ static void
13641368
add_stats(GCState *gcstate, int gen, struct gc_collection_stats *stats)
13651369
{
13661370
gcstate->generation_stats[gen].duration += stats->duration;
1371+
gcstate->generation_stats[gen].visited += stats->visited;
13671372
gcstate->generation_stats[gen].collected += stats->collected;
13681373
gcstate->generation_stats[gen].uncollectable += stats->uncollectable;
13691374
gcstate->generation_stats[gen].collections += 1;
@@ -1754,7 +1759,7 @@ gc_collect_region(PyThreadState *tstate,
17541759
assert(!_PyErr_Occurred(tstate));
17551760

17561761
gc_list_init(&unreachable);
1757-
deduce_unreachable(from, &unreachable);
1762+
stats->visited += deduce_unreachable(from, &unreachable);
17581763
validate_consistent_old_space(from);
17591764
untrack_tuples(from);
17601765

@@ -1844,10 +1849,11 @@ do_gc_callback(GCState *gcstate, const char *phase,
18441849
assert(PyList_CheckExact(gcstate->callbacks));
18451850
PyObject *info = NULL;
18461851
if (PyList_GET_SIZE(gcstate->callbacks) != 0) {
1847-
info = Py_BuildValue("{sisnsnsd}",
1852+
info = Py_BuildValue("{sisnsnsnsd}",
18481853
"generation", generation,
18491854
"collected", stats->collected,
18501855
"uncollectable", stats->uncollectable,
1856+
"visited", stats->visited,
18511857
"duration", stats->duration);
18521858
if (info == NULL) {
18531859
PyErr_FormatUnraisable("Exception ignored while invoking gc callbacks");

Python/gc_free_threading.c

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@ struct collection_state {
9898
// we can't collect objects with deferred references because we may not
9999
// see all references.
100100
int skip_deferred_objects;
101+
Py_ssize_t visited;
101102
Py_ssize_t collected;
102103
Py_ssize_t uncollectable;
103104
Py_ssize_t long_lived_total;
@@ -975,7 +976,9 @@ static bool
975976
update_refs(const mi_heap_t *heap, const mi_heap_area_t *area,
976977
void *block, size_t block_size, void *args)
977978
{
978-
PyObject *op = op_from_block(block, args, false);
979+
struct collection_state *state = (struct collection_state *)args;
980+
state->visited++;
981+
PyObject *op = op_from_block(block, &state->base, false);
979982
if (op == NULL) {
980983
return true;
981984
}
@@ -1434,7 +1437,7 @@ deduce_unreachable_heap(PyInterpreterState *interp,
14341437
// Identify objects that are directly reachable from outside the GC heap
14351438
// by computing the difference between the refcount and the number of
14361439
// incoming references.
1437-
gc_visit_heaps(interp, &update_refs, &state->base);
1440+
gc_visit_heaps(interp, &update_refs, &state);
14381441

14391442
#ifdef GC_DEBUG
14401443
// Check that all objects are marked as unreachable and that the computed
@@ -1911,7 +1914,7 @@ handle_resurrected_objects(struct collection_state *state)
19111914
static void
19121915
invoke_gc_callback(PyThreadState *tstate, const char *phase,
19131916
int generation, Py_ssize_t collected,
1914-
Py_ssize_t uncollectable, double duration)
1917+
Py_ssize_t uncollectable, Py_ssize_t visited, double duration)
19151918
{
19161919
assert(!_PyErr_Occurred(tstate));
19171920

@@ -1925,10 +1928,11 @@ invoke_gc_callback(PyThreadState *tstate, const char *phase,
19251928
assert(PyList_CheckExact(gcstate->callbacks));
19261929
PyObject *info = NULL;
19271930
if (PyList_GET_SIZE(gcstate->callbacks) != 0) {
1928-
info = Py_BuildValue("{sisnsnsd}",
1931+
info = Py_BuildValue("{sisnsnsnsd}",
19291932
"generation", generation,
19301933
"collected", collected,
19311934
"uncollectable", uncollectable,
1935+
"visited", visited,
19321936
"duration", duration);
19331937
if (info == NULL) {
19341938
PyErr_FormatUnraisable("Exception ignored while "
@@ -2372,7 +2376,7 @@ gc_collect_main(PyThreadState *tstate, int generation, _PyGC_Reason reason)
23722376
GC_STAT_ADD(generation, collections, 1);
23732377

23742378
if (reason != _Py_GC_REASON_SHUTDOWN) {
2375-
invoke_gc_callback(tstate, "start", generation, 0, 0, 0);
2379+
invoke_gc_callback(tstate, "start", generation, 0, 0, 0, 0);
23762380
}
23772381

23782382
if (gcstate->debug & _PyGC_DEBUG_STATS) {
@@ -2427,6 +2431,7 @@ gc_collect_main(PyThreadState *tstate, int generation, _PyGC_Reason reason)
24272431
stats->collected += m;
24282432
stats->uncollectable += n;
24292433
stats->duration += duration;
2434+
stats->visited += state.visited;
24302435

24312436
GC_STAT_ADD(generation, objects_collected, m);
24322437
#ifdef Py_STATS
@@ -2445,7 +2450,7 @@ gc_collect_main(PyThreadState *tstate, int generation, _PyGC_Reason reason)
24452450
}
24462451

24472452
if (reason != _Py_GC_REASON_SHUTDOWN) {
2448-
invoke_gc_callback(tstate, "stop", generation, m, n, duration);
2453+
invoke_gc_callback(tstate, "stop", generation, m, n, state->visited, duration);
24492454
}
24502455

24512456
assert(!_PyErr_Occurred(tstate));

0 commit comments

Comments
 (0)