@@ -766,7 +766,19 @@ typedef struct rb_objspace {
766766/* The one VM-global GC structure; for now it only holds the page pool. Page bodies are
767767 * carved out of large mmap arenas and reused via a process-wide freelist (per-page
768768 * mmap/munmap would serialize on the kernel's mmap_lock). Leaf lock: no alloc, no GC. */
769- typedef struct rb_global_objspace {
769+ typedef struct rb_global_objspace rb_global_objspace_t ;
770+
771+ /* Why a global (stop-the-world, cross-objspace) GC was started. Counted per
772+ * trigger in global_gc.trigger_counts and exposed by GC.stat. */
773+ enum global_gc_trigger {
774+ global_gc_trigger_shareable , /* per-objspace shareable_objects crossed its limit */
775+ global_gc_trigger_zombie , /* zombie objspace pages crossed the reclamation threshold */
776+ global_gc_trigger_explicit , /* GC.start full mark on a multi-objspace process */
777+ global_gc_trigger_compact , /* GC.compact or autocompact-driven compaction */
778+ global_gc_trigger_count
779+ };
780+
781+ struct rb_global_objspace {
770782 struct {
771783 rb_nativethread_lock_t lock ;
772784 struct heap_page_body * freelist ; /* bodies to reuse; the next pointer lives in the body */
@@ -807,6 +819,9 @@ typedef struct rb_global_objspace {
807819 bool compacting ;
808820 struct rb_objspace * * objspaces ;
809821 size_t n_objspaces , objspaces_capa ;
822+ size_t count ; /* total global GCs started */
823+ unsigned long long time_ns ; /* accumulated wall time of global GCs */
824+ size_t trigger_counts [global_gc_trigger_count ];
810825 } global_gc ;
811826
812827 /* Index of every objspace's heap pages, ordered by body address. Writers (page
@@ -817,7 +832,7 @@ typedef struct rb_global_objspace {
817832 size_t n_pages , capa ;
818833 uintptr_t lomem , himem ;
819834 } page_index ;
820- } rb_global_objspace_t ;
835+ };
821836
822837static rb_global_objspace_t rb_global_objspace_instance ;
823838static rb_global_objspace_t * global_objspace = NULL ;
@@ -7859,23 +7874,29 @@ gc_reset_malloc_info(rb_objspace_t *objspace, bool full_mark)
78597874#endif
78607875}
78617876
7862- static void gc_start_global (rb_objspace_t * driver , bool compact );
7877+ static void gc_start_global (rb_objspace_t * driver , bool compact , enum global_gc_trigger trigger );
78637878
78647879/* Decide whether this collection has to be global. A local GC can reclaim neither
78657880 * shareable objects nor zombie objspaces, so once those grow past their limits only a
78667881 * global GC makes progress. All inputs belong to this objspace. */
78677882static bool
7868- gc_need_global_p (rb_objspace_t * objspace )
7883+ gc_need_global_p (rb_objspace_t * objspace , enum global_gc_trigger * trigger )
78697884{
78707885 if (rb_gc_single_objspace_p ()) return false;
7871- if (objspace -> shareable_objects > objspace -> shareable_objects_limit ) return true;
7886+ if (objspace -> shareable_objects > objspace -> shareable_objects_limit ) {
7887+ * trigger = global_gc_trigger_shareable ;
7888+ return true;
7889+ }
78727890 /* A zombie's garbage only a global cycle reclaims, but what survived the last one
78737891 * is live data, so retrigger only once TRIGGER more pages accumulate on top of it.
78747892 * Otherwise one live-heavy unjoined zombie turns every GC stop-the-world forever. */
78757893 {
78767894 size_t zp = rb_gc_vm_zombie_total_pages ();
78777895 size_t base = global_objspace -> zombie_pages_survivors < zp ? global_objspace -> zombie_pages_survivors : zp ;
7878- if (zp - base >= ZOMBIE_PAGES_TRIGGER ) return true;
7896+ if (zp - base >= ZOMBIE_PAGES_TRIGGER ) {
7897+ * trigger = global_gc_trigger_zombie ;
7898+ return true;
7899+ }
78797900 }
78807901 return false;
78817902}
@@ -7912,9 +7933,12 @@ gc_start_body(rb_objspace_t *objspace, unsigned int reason, bool allow_global)
79127933 * allocation slow path, or an allocation-driven workload slips past every threshold
79137934 * (only a global cycle reclaims dead shareable objects and zombie pages). The
79147935 * exception is the retire GC, which never promotes: a Ractor's death must not STW. */
7915- if (allow_global && gc_need_global_p (objspace )) {
7916- gc_start_global (objspace , false);
7917- return TRUE;
7936+ if (allow_global ) {
7937+ enum global_gc_trigger trigger ;
7938+ if (gc_need_global_p (objspace , & trigger )) {
7939+ gc_start_global (objspace , false, trigger );
7940+ return TRUE;
7941+ }
79187942 }
79197943
79207944 rb_gc_initialize_vm_context (& objspace -> vm_context );
@@ -8629,11 +8653,12 @@ gc_global_mark_generic_fields(rb_objspace_t *driver)
86298653/* Two Ractors choosing a global GC at once are serialized by the barrier in gc_enter and
86308654 * simply run two cycles back to back. The second is wasted work, not an error. */
86318655static void
8632- gc_start_global (rb_objspace_t * driver , bool compact )
8656+ gc_start_global (rb_objspace_t * driver , bool compact , enum global_gc_trigger trigger )
86338657{
86348658 unsigned int lock_lev ;
86358659 gc_enter (driver , gc_enter_event_global , & lock_lev );
86368660
8661+ rb_hrtime_t global_gc_start = rb_hrtime_now ();
86378662 GC_ASSERT (is_mark_stack_empty (& driver -> mark_stack ));
86388663
86398664 gc_global_snapshot_objspaces ();
@@ -8832,6 +8857,10 @@ gc_start_global(rb_objspace_t *driver, bool compact)
88328857 }
88338858 driver -> profile .count ++ ;
88348859
8860+ global_objspace -> global_gc .count ++ ;
8861+ global_objspace -> global_gc .trigger_counts [trigger ]++ ;
8862+ global_objspace -> global_gc .time_ns += elapsed_hrtime_from (global_gc_start );
8863+
88358864 /* step 10 */
88368865 for (size_t i = 0 ; i < global_objspace -> global_gc .n_objspaces ; i ++ ) {
88378866 rb_objspace_t * objspace = global_objspace -> global_gc .objspaces [i ];
@@ -9121,7 +9150,9 @@ rb_gc_impl_start(void *objspace_ptr, bool full_mark, bool immediate_mark, bool i
91219150 * collector that reclaims shareable and cross-objspace garbage. It stops the world,
91229151 * so auto_compact is honoured here too (mirroring full mark x autocompact locally). */
91239152 if (!rb_gc_single_objspace_p () && (reason & GPR_FLAG_FULL_MARK )) {
9124- gc_start_global (objspace , compact || ruby_enable_autocompact );
9153+ bool do_compact = compact || ruby_enable_autocompact ;
9154+ gc_start_global (objspace , do_compact ,
9155+ do_compact ? global_gc_trigger_compact : global_gc_trigger_explicit );
91259156 }
91269157 else {
91279158 garbage_collect (objspace , reason );
@@ -9796,6 +9827,12 @@ enum gc_stat_sym {
97969827 gc_stat_sym_minor_gc_count ,
97979828 gc_stat_sym_major_gc_count ,
97989829 gc_stat_sym_compact_count ,
9830+ gc_stat_sym_global_gc_count ,
9831+ gc_stat_sym_global_gc_time_taken ,
9832+ gc_stat_sym_global_gc_trigger_shareable_count ,
9833+ gc_stat_sym_global_gc_trigger_zombie_count ,
9834+ gc_stat_sym_global_gc_trigger_explicit_count ,
9835+ gc_stat_sym_global_gc_trigger_compact_count ,
97999836 gc_stat_sym_read_barrier_faults ,
98009837 gc_stat_sym_total_moved_objects ,
98019838 gc_stat_sym_remembered_wb_unprotected_objects ,
@@ -9847,6 +9884,12 @@ setup_gc_stat_symbols(void)
98479884 S (malloc_increase_bytes_limit );
98489885 S (minor_gc_count );
98499886 S (major_gc_count );
9887+ S (global_gc_count );
9888+ S (global_gc_time_taken );
9889+ S (global_gc_trigger_shareable_count );
9890+ S (global_gc_trigger_zombie_count );
9891+ S (global_gc_trigger_explicit_count );
9892+ S (global_gc_trigger_compact_count );
98509893 S (compact_count );
98519894 S (read_barrier_faults );
98529895 S (total_moved_objects );
@@ -9933,6 +9976,12 @@ rb_gc_impl_stat(void *objspace_ptr, VALUE hash_or_sym)
99339976 SET (minor_gc_count , objspace -> profile .minor_gc_count );
99349977 SET (major_gc_count , objspace -> profile .major_gc_count );
99359978 SET (compact_count , objspace -> profile .compact_count );
9979+ SET (global_gc_count , global_objspace -> global_gc .count );
9980+ SET64 (global_gc_time_taken , ns_to_ms (global_objspace -> global_gc .time_ns ));
9981+ SET (global_gc_trigger_shareable_count , global_objspace -> global_gc .trigger_counts [global_gc_trigger_shareable ]);
9982+ SET (global_gc_trigger_zombie_count , global_objspace -> global_gc .trigger_counts [global_gc_trigger_zombie ]);
9983+ SET (global_gc_trigger_explicit_count , global_objspace -> global_gc .trigger_counts [global_gc_trigger_explicit ]);
9984+ SET (global_gc_trigger_compact_count , global_objspace -> global_gc .trigger_counts [global_gc_trigger_compact ]);
99369985 SET (read_barrier_faults , objspace -> profile .read_barrier_faults );
99379986 SET (total_moved_objects , objspace -> rcompactor .total_moved );
99389987 SET (remembered_wb_unprotected_objects , objspace -> rgengc .uncollectible_wb_unprotected_objects );
0 commit comments