@@ -70,6 +70,18 @@ gc_clear_collecting(PyGC_Head *g)
7070 g -> _gc_prev &= ~PREV_MASK_COLLECTING ;
7171}
7272
73+ static inline int
74+ gc_is_visited (PyGC_Head * g )
75+ {
76+ return (int )g -> _visited ;
77+ }
78+
79+ static inline void
80+ gc_set_is_visited (PyGC_Head * g )
81+ {
82+ g -> _visited = 1 ;
83+ }
84+
7385static inline Py_ssize_t
7486gc_get_refs (PyGC_Head * g )
7587{
@@ -769,6 +781,25 @@ untrack_tuples(PyGC_Head *head)
769781 return untracked ;
770782}
771783
784+ static Py_ssize_t
785+ count_tuples (PyGC_Head * head )
786+ {
787+ Py_ssize_t tuples = 0 ;
788+ PyGC_Head * gc = GC_NEXT (head );
789+ while (gc != head ) {
790+ PyObject * op = FROM_GC (gc );
791+ PyGC_Head * next = GC_NEXT (gc );
792+ if (!gc_is_visited (gc )) {
793+ if (PyTuple_CheckExact (op )) {
794+ tuples += 1 ;
795+ }
796+ gc_set_is_visited (gc );
797+ }
798+ gc = next ;
799+ }
800+ return tuples ;
801+ }
802+
772803/* Return true if object has a pre-PEP 442 finalization method. */
773804static int
774805has_legacy_finalizer (PyObject * op )
@@ -1378,6 +1409,7 @@ gc_collect_young(PyThreadState *tstate,
13781409 validate_spaces (gcstate );
13791410 PyGC_Head * young = & gcstate -> young .head ;
13801411 PyGC_Head * visited = & gcstate -> old [gcstate -> visited_space ].head ;
1412+ stats -> tracked_tuples += count_tuples (young );
13811413 stats -> untracked_tuples += untrack_tuples (young );
13821414 GC_STAT_ADD (0 , collections , 1 );
13831415
@@ -1656,6 +1688,7 @@ gc_collect_increment(PyThreadState *tstate, struct gc_collection_stats *stats)
16561688 GC_STAT_ADD (1 , collections , 1 );
16571689 GCState * gcstate = & tstate -> interp -> gc ;
16581690 gcstate -> work_to_do += assess_work_to_do (gcstate );
1691+ stats -> tracked_tuples += count_tuples (& gcstate -> young .head );
16591692 stats -> untracked_tuples += untrack_tuples (& gcstate -> young .head );
16601693 if (gcstate -> phase == GC_PHASE_MARK ) {
16611694 Py_ssize_t objects_marked = mark_at_start (tstate );
@@ -1718,6 +1751,7 @@ gc_collect_full(PyThreadState *tstate,
17181751 PyGC_Head * young = & gcstate -> young .head ;
17191752 PyGC_Head * pending = & gcstate -> old [gcstate -> visited_space ^1 ].head ;
17201753 PyGC_Head * visited = & gcstate -> old [gcstate -> visited_space ].head ;
1754+ stats -> tracked_tuples += count_tuples (young );
17211755 stats -> untracked_tuples += untrack_tuples (young );
17221756 /* merge all generations into visited */
17231757 gc_list_merge (young , pending );
@@ -1758,6 +1792,7 @@ gc_collect_region(PyThreadState *tstate,
17581792 gc_list_init (& unreachable );
17591793 deduce_unreachable (from , & unreachable );
17601794 validate_consistent_old_space (from );
1795+ stats -> tracked_tuples += count_tuples (from );
17611796 stats -> untracked_tuples += untrack_tuples (from );
17621797
17631798 /* Move reachable objects to next generation. */
@@ -2100,28 +2135,31 @@ _PyGC_Collect(PyThreadState *tstate, int generation, _PyGC_Reason reason)
21002135 default :
21012136 Py_UNREACHABLE ();
21022137 }
2103- gcstate -> generation_stats [generation ]. untracked_tuples += stats .untracked_tuples ;
2138+ gcstate -> generation_stats [0 ]. total_tracked_tuples += stats .tracked_tuples ;
21042139 gcstate -> generation_stats [0 ].total_untracked_tuples += stats .untracked_tuples ;
2140+ gcstate -> generation_stats [generation ].tracked_tuples += stats .tracked_tuples ;
2141+ gcstate -> generation_stats [generation ].untracked_tuples += stats .untracked_tuples ;
21052142 if (PyDTrace_GC_DONE_ENABLED ()) {
21062143 PyDTrace_GC_DONE (stats .uncollectable + stats .collected );
21072144 }
21082145 if (reason != _Py_GC_REASON_SHUTDOWN ) {
21092146 invoke_gc_callback (gcstate , "stop" , generation , & stats );
21102147 }
21112148 else {
2112- FILE * out = stderr ;
2113-
2114- fprintf (out , "GC[%d] total tuples : %zd\n" , 0 , gcstate -> generation_stats [0 ].total_tuples );
2115- fprintf (out , "GC[%d] total untracked_tuples : %zd\n" , 0 , gcstate -> generation_stats [0 ].total_untracked_tuples );
2116- for (int i = 0 ; i < 33 ; i ++ ) {
2117- fprintf (out , "GC[%d] by size %d : %zd\n" , 0 , i , gcstate -> generation_stats [0 ].tuples_by_size [i ]);
2118- }
2149+ if (true) {
2150+ FILE * out = stderr ;
2151+
2152+ fprintf (out , "GC[%d] total tuples : %zd\n" , 0 , gcstate -> generation_stats [0 ].total_tuples );
2153+ fprintf (out , "GC[%d] total tracked_tuples : %zd\n" , 0 , gcstate -> generation_stats [0 ].total_tracked_tuples );
2154+ fprintf (out , "GC[%d] total untracked_tuples : %zd\n" , 0 , gcstate -> generation_stats [0 ].total_untracked_tuples );
2155+ for (int i = 0 ; i < 33 ; i ++ ) {
2156+ fprintf (out , "GC[%d] by size %d : %zd\n" , 0 , i , gcstate -> generation_stats [0 ].tuples_by_size [i ]);
2157+ }
21192158
2120- for (int i = 0 ; i < NUM_GENERATIONS ; i ++ ) {
2121- fprintf (out , "GC[%d] collections : %zd\n" , i , gcstate -> generation_stats [i ].collections );
2122- fprintf (out , "GC[%d] collected : %zd\n" , i , gcstate -> generation_stats [i ].collected );
2123- fprintf (out , "GC[%d] uncollectable : %zd\n" , i , gcstate -> generation_stats [i ].uncollectable );
2124- fprintf (out , "GC[%d] untracked_tuples: %zd\n" , i , gcstate -> generation_stats [i ].untracked_tuples );
2159+ for (int i = 0 ; i < NUM_GENERATIONS ; i ++ ) {
2160+ fprintf (out , "GC[%d] tracked_tuples : %zd\n" , i , gcstate -> generation_stats [i ].tracked_tuples );
2161+ fprintf (out , "GC[%d] untracked_tuples: %zd\n" , i , gcstate -> generation_stats [i ].untracked_tuples );
2162+ }
21252163 }
21262164 }
21272165 _PyErr_SetRaisedException (tstate , exc );
@@ -2316,6 +2354,7 @@ _PyObject_GC_Link(PyObject *op)
23162354 GCState * gcstate = & tstate -> interp -> gc ;
23172355 gc -> _gc_next = 0 ;
23182356 gc -> _gc_prev = 0 ;
2357+ gc -> _visited = 0 ;
23192358 gcstate -> young .count ++ ; /* number of allocated GC objects */
23202359 gcstate -> heap_size ++ ;
23212360 if (gcstate -> young .count > gcstate -> young .threshold &&
0 commit comments