Skip to content

Commit 2ba7c7f

Browse files
authored
Add some GC stats to Py_STATS (GH-107581)
1 parent fa45958 commit 2ba7c7f

File tree

5 files changed

+80
-1
lines changed

5 files changed

+80
-1
lines changed

Include/internal/pycore_code.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -274,6 +274,7 @@ extern int _PyStaticCode_Init(PyCodeObject *co);
274274
#define EVAL_CALL_STAT_INC(name) do { if (_py_stats) _py_stats->call_stats.eval_calls[name]++; } while (0)
275275
#define EVAL_CALL_STAT_INC_IF_FUNCTION(name, callable) \
276276
do { if (_py_stats && PyFunction_Check(callable)) _py_stats->call_stats.eval_calls[name]++; } while (0)
277+
#define GC_STAT_ADD(gen, name, n) do { if (_py_stats) _py_stats->gc_stats[(gen)].name += (n); } while (0)
277278

278279
// Export for '_opcode' shared extension
279280
PyAPI_FUNC(PyObject*) _Py_GetSpecializationStats(void);
@@ -287,6 +288,7 @@ PyAPI_FUNC(PyObject*) _Py_GetSpecializationStats(void);
287288
#define OBJECT_STAT_INC_COND(name, cond) ((void)0)
288289
#define EVAL_CALL_STAT_INC(name) ((void)0)
289290
#define EVAL_CALL_STAT_INC_IF_FUNCTION(name, callable) ((void)0)
291+
#define GC_STAT_ADD(gen, name, n) ((void)0)
290292
#endif // !Py_STATS
291293

292294
// Utility functions for reading/writing 32/64-bit values in the inline caches.

Include/pystats.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,12 +74,21 @@ typedef struct _object_stats {
7474
uint64_t optimization_traces_created;
7575
uint64_t optimization_traces_executed;
7676
uint64_t optimization_uops_executed;
77+
/* Temporary value used during GC */
78+
uint64_t object_visits;
7779
} ObjectStats;
7880

81+
typedef struct _gc_stats {
82+
uint64_t collections;
83+
uint64_t object_visits;
84+
uint64_t objects_collected;
85+
} GCStats;
86+
7987
typedef struct _stats {
8088
OpcodeStats opcode_stats[256];
8189
CallStats call_stats;
8290
ObjectStats object_stats;
91+
GCStats *gc_stats;
8392
} PyStats;
8493

8594

Modules/gcmodule.c

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -460,6 +460,7 @@ update_refs(PyGC_Head *containers)
460460
static int
461461
visit_decref(PyObject *op, void *parent)
462462
{
463+
OBJECT_STAT_INC(object_visits);
463464
_PyObject_ASSERT(_PyObject_CAST(parent), !_PyObject_IsFreed(op));
464465

465466
if (_PyObject_IS_GC(op)) {
@@ -498,6 +499,7 @@ subtract_refs(PyGC_Head *containers)
498499
static int
499500
visit_reachable(PyObject *op, PyGC_Head *reachable)
500501
{
502+
OBJECT_STAT_INC(object_visits);
501503
if (!_PyObject_IS_GC(op)) {
502504
return 0;
503505
}
@@ -725,6 +727,7 @@ clear_unreachable_mask(PyGC_Head *unreachable)
725727
static int
726728
visit_move(PyObject *op, PyGC_Head *tolist)
727729
{
730+
OBJECT_STAT_INC(object_visits);
728731
if (_PyObject_IS_GC(op)) {
729732
PyGC_Head *gc = AS_GC(op);
730733
if (gc_is_collecting(gc)) {
@@ -1195,6 +1198,12 @@ gc_collect_main(PyThreadState *tstate, int generation,
11951198
Py_ssize_t *n_collected, Py_ssize_t *n_uncollectable,
11961199
int nofail)
11971200
{
1201+
GC_STAT_ADD(generation, collections, 1);
1202+
#ifdef Py_STATS
1203+
if (_py_stats) {
1204+
_py_stats->object_stats.object_visits = 0;
1205+
}
1206+
#endif
11981207
int i;
11991208
Py_ssize_t m = 0; /* # objects collected */
12001209
Py_ssize_t n = 0; /* # unreachable objects that couldn't be collected */
@@ -1351,6 +1360,15 @@ gc_collect_main(PyThreadState *tstate, int generation,
13511360
stats->collected += m;
13521361
stats->uncollectable += n;
13531362

1363+
GC_STAT_ADD(generation, objects_collected, m);
1364+
#ifdef Py_STATS
1365+
if (_py_stats) {
1366+
GC_STAT_ADD(generation, object_visits,
1367+
_py_stats->object_stats.object_visits);
1368+
_py_stats->object_stats.object_visits = 0;
1369+
}
1370+
#endif
1371+
13541372
if (PyDTrace_GC_DONE_ENABLED()) {
13551373
PyDTrace_GC_DONE(n + m);
13561374
}

Python/specialize.c

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@
1818
*/
1919

2020
#ifdef Py_STATS
21-
PyStats _py_stats_struct = { 0 };
21+
GCStats _py_gc_stats[NUM_GENERATIONS] = { 0 };
22+
PyStats _py_stats_struct = { .gc_stats = &_py_gc_stats[0] };
2223
PyStats *_py_stats = NULL;
2324

2425
#define ADD_STAT_TO_DICT(res, field) \
@@ -202,17 +203,32 @@ print_object_stats(FILE *out, ObjectStats *stats)
202203
fprintf(out, "Optimization uops executed: %" PRIu64 "\n", stats->optimization_uops_executed);
203204
}
204205

206+
static void
207+
print_gc_stats(FILE *out, GCStats *stats)
208+
{
209+
for (int i = 0; i < NUM_GENERATIONS; i++) {
210+
fprintf(out, "GC[%d] collections: %" PRIu64 "\n", i, stats[i].collections);
211+
fprintf(out, "GC[%d] object visits: %" PRIu64 "\n", i, stats[i].object_visits);
212+
fprintf(out, "GC[%d] objects collected: %" PRIu64 "\n", i, stats[i].objects_collected);
213+
}
214+
}
215+
205216
static void
206217
print_stats(FILE *out, PyStats *stats) {
207218
print_spec_stats(out, stats->opcode_stats);
208219
print_call_stats(out, &stats->call_stats);
209220
print_object_stats(out, &stats->object_stats);
221+
print_gc_stats(out, stats->gc_stats);
210222
}
211223

212224
void
213225
_Py_StatsClear(void)
214226
{
227+
for (int i = 0; i < NUM_GENERATIONS; i++) {
228+
_py_gc_stats[i] = (GCStats) { 0 };
229+
}
215230
_py_stats_struct = (PyStats) { 0 };
231+
_py_stats_struct.gc_stats = _py_gc_stats;
216232
}
217233

218234
void

Tools/scripts/summarize_stats.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -494,6 +494,22 @@ def calculate_object_stats(stats):
494494
rows.append((label, value, ratio))
495495
return rows
496496

497+
def calculate_gc_stats(stats):
498+
gc_stats = []
499+
for key, value in stats.items():
500+
if not key.startswith("GC"):
501+
continue
502+
n, _, rest = key[3:].partition("]")
503+
name = rest.strip()
504+
gen_n = int(n)
505+
while len(gc_stats) <= gen_n:
506+
gc_stats.append({})
507+
gc_stats[gen_n][name] = value
508+
return [
509+
(i, gen["collections"], gen["objects collected"], gen["object visits"])
510+
for (i, gen) in enumerate(gc_stats)
511+
]
512+
497513
def emit_object_stats(stats):
498514
with Section("Object stats", summary="allocations, frees and dict materializatons"):
499515
rows = calculate_object_stats(stats)
@@ -505,6 +521,22 @@ def emit_comparative_object_stats(base_stats, head_stats):
505521
head_rows = calculate_object_stats(head_stats)
506522
emit_table(("", "Base Count:", "Base Ratio:", "Head Count:", "Head Ratio:"), join_rows(base_rows, head_rows))
507523

524+
def emit_gc_stats(stats):
525+
with Section("GC stats", summary="GC collections and effectiveness"):
526+
rows = calculate_gc_stats(stats)
527+
emit_table(("Generation:", "Collections:", "Objects collected:", "Object visits:"), rows)
528+
529+
def emit_comparative_gc_stats(base_stats, head_stats):
530+
with Section("GC stats", summary="GC collections and effectiveness"):
531+
base_rows = calculate_gc_stats(base_stats)
532+
head_rows = calculate_gc_stats(head_stats)
533+
emit_table(
534+
("Generation:",
535+
"Base collections:", "Head collections:",
536+
"Base objects collected:", "Head objects collected:",
537+
"Base object visits:", "Head object visits:"),
538+
join_rows(base_rows, head_rows))
539+
508540
def get_total(opcode_stats):
509541
total = 0
510542
for opcode_stat in opcode_stats:
@@ -574,6 +606,7 @@ def output_single_stats(stats):
574606
emit_specialization_overview(opcode_stats, total)
575607
emit_call_stats(stats)
576608
emit_object_stats(stats)
609+
emit_gc_stats(stats)
577610
with Section("Meta stats", summary="Meta statistics"):
578611
emit_table(("", "Count:"), [('Number of data files', stats['__nfiles__'])])
579612

@@ -596,6 +629,7 @@ def output_comparative_stats(base_stats, head_stats):
596629
)
597630
emit_comparative_call_stats(base_stats, head_stats)
598631
emit_comparative_object_stats(base_stats, head_stats)
632+
emit_comparative_gc_stats(base_stats, head_stats)
599633

600634
def output_stats(inputs, json_output=None):
601635
if len(inputs) == 1:

0 commit comments

Comments
 (0)