Add marking and sweeping time to GC.stat

There is a `time` key in GC.stat that gives us the total time spent in
GC. However, we don't know what proportion of the time is spent between
marking and sweeping. This makes it difficult to tune the GC as we're
not sure where to focus our efforts on.

This PR adds keys `marking_time` and `sweeping_time` to GC.stat for the
time spent marking and sweeping, in milliseconds.

[Feature #19437]
This commit is contained in:
Peter Zhu 2023-02-14 15:55:25 -05:00
Родитель d7c1ca48bf
Коммит 93ac7405b8
3 изменённых файлов: 94 добавлений и 50 удалений

140
gc.c
Просмотреть файл

@ -826,8 +826,10 @@ typedef struct rb_objspace {
/* basic statistics */
size_t count;
size_t total_freed_objects;
uint64_t total_time_ns;
struct timespec start_time;
uint64_t marking_time_ns;
struct timespec marking_start_time;
uint64_t sweeping_time_ns;
struct timespec sweeping_start_time;
} profile;
struct gc_list *global_list;
@ -1242,6 +1244,10 @@ enum gc_enter_event {
static inline void gc_enter(rb_objspace_t *objspace, enum gc_enter_event event, unsigned int *lock_lev);
static inline void gc_exit(rb_objspace_t *objspace, enum gc_enter_event event, unsigned int *lock_lev);
static void gc_marking_enter(rb_objspace_t *objspace);
static void gc_marking_exit(rb_objspace_t *objspace);
static void gc_sweeping_enter(rb_objspace_t *objspace);
static void gc_sweeping_exit(rb_objspace_t *objspace);
static void gc_marks_start(rb_objspace_t *objspace, int full);
static void gc_marks_finish(rb_objspace_t *objspace);
@ -4572,7 +4578,7 @@ rb_objspace_call_finalizer(rb_objspace_t *objspace)
}
}
gc_exit(objspace, false, &lock_lev);
gc_exit(objspace, gc_enter_event_finalizer, &lock_lev);
finalize_deferred_heap_pages(objspace);
@ -6058,6 +6064,8 @@ gc_sweep_continue(rb_objspace_t *objspace, rb_size_pool_t *sweep_size_pool, rb_h
GC_ASSERT(dont_gc_val() == FALSE);
if (!GC_ENABLE_LAZY_SWEEP) return;
gc_sweeping_enter(objspace);
for (int i = 0; i < SIZE_POOL_COUNT; i++) {
rb_size_pool_t *size_pool = &size_pools[i];
if (!gc_sweep_step(objspace, size_pool, SIZE_POOL_EDEN_HEAP(size_pool))) {
@ -6076,6 +6084,8 @@ gc_sweep_continue(rb_objspace_t *objspace, rb_size_pool_t *sweep_size_pool, rb_h
#endif
}
}
gc_sweeping_exit(objspace);
}
static void
@ -6186,6 +6196,8 @@ static void gc_sweep_compact(rb_objspace_t *objspace);
static void
gc_sweep(rb_objspace_t *objspace)
{
gc_sweeping_enter(objspace);
const unsigned int immediate_sweep = objspace->flags.immediate_sweep;
gc_report(1, objspace, "gc_sweep: immediate: %d\n", immediate_sweep);
@ -6217,6 +6229,8 @@ gc_sweep(rb_objspace_t *objspace)
rb_size_pool_t *size_pool = &size_pools[0];
gc_heap_prepare_minimum_pages(objspace, size_pool, SIZE_POOL_EDEN_HEAP(size_pool));
#endif
gc_sweeping_exit(objspace);
}
/* Marking - Marking stack */
@ -8709,6 +8723,8 @@ gc_marks_continue(rb_objspace_t *objspace, rb_size_pool_t *size_pool, rb_heap_t
GC_ASSERT(dont_gc_val() == FALSE);
bool marking_finished = true;
gc_marking_enter(objspace);
#if GC_ENABLE_INCREMENTAL_MARK
if (heap->free_pages) {
gc_report(2, objspace, "gc_marks_continue: has pooled pages");
@ -8722,6 +8738,8 @@ gc_marks_continue(rb_objspace_t *objspace, rb_size_pool_t *size_pool, rb_heap_t
}
#endif
gc_marking_exit(objspace);
return marking_finished;
}
@ -8729,6 +8747,8 @@ static bool
gc_marks(rb_objspace_t *objspace, int full_mark)
{
gc_prof_mark_timer_start(objspace);
gc_marking_enter(objspace);
bool marking_finished = false;
/* setup marking */
@ -8745,6 +8765,8 @@ gc_marks(rb_objspace_t *objspace, int full_mark)
record->old_objects = objspace->rgengc.old_objects;
}
#endif
gc_marking_exit(objspace);
gc_prof_mark_timer_stop(objspace);
return marking_finished;
@ -9549,12 +9571,19 @@ gc_rest(rb_objspace_t *objspace)
if (RGENGC_CHECK_MODE >= 2) gc_verify_internal_consistency(objspace);
if (is_incremental_marking(objspace)) {
gc_marking_enter(objspace);
gc_marks_rest(objspace);
gc_marking_exit(objspace);
gc_sweep(objspace);
}
if (is_lazy_sweeping(objspace)) {
gc_sweeping_enter(objspace);
gc_sweep_rest(objspace);
gc_sweeping_exit(objspace);
}
gc_exit(objspace, gc_enter_event_rest, &lock_lev);
}
}
@ -9669,58 +9698,30 @@ gc_enter_count(enum gc_enter_event event)
#define MEASURE_GC (objspace->flags.measure_gc)
#endif
static bool
gc_enter_event_measure_p(rb_objspace_t *objspace, enum gc_enter_event event)
{
if (!MEASURE_GC) return false;
switch (event) {
case gc_enter_event_start:
case gc_enter_event_continue:
case gc_enter_event_rest:
return true;
default:
// case gc_enter_event_finalizer:
// case gc_enter_event_rb_memerror:
return false;
}
}
static bool current_process_time(struct timespec *ts);
static void
gc_enter_clock(rb_objspace_t *objspace, enum gc_enter_event event)
gc_clock_start(struct timespec *ts)
{
if (gc_enter_event_measure_p(objspace, event)) {
if (!current_process_time(&objspace->profile.start_time)) {
objspace->profile.start_time.tv_sec = 0;
objspace->profile.start_time.tv_nsec = 0;
}
if (!current_process_time(ts)) {
ts->tv_sec = 0;
ts->tv_nsec = 0;
}
}
static void
gc_exit_clock(rb_objspace_t *objspace, enum gc_enter_event event)
static uint64_t
gc_clock_end(struct timespec *ts)
{
if (gc_enter_event_measure_p(objspace, event)) {
struct timespec end_time;
struct timespec end_time;
if ((objspace->profile.start_time.tv_sec > 0 ||
objspace->profile.start_time.tv_nsec > 0) &&
current_process_time(&end_time)) {
if (end_time.tv_sec < objspace->profile.start_time.tv_sec) {
return; // ignore
}
else {
uint64_t ns =
(uint64_t)(end_time.tv_sec - objspace->profile.start_time.tv_sec) * (1000 * 1000 * 1000) +
(end_time.tv_nsec - objspace->profile.start_time.tv_nsec);
objspace->profile.total_time_ns += ns;
}
}
if ((ts->tv_sec > 0 || ts->tv_nsec > 0) &&
current_process_time(&end_time) &&
end_time.tv_sec >= ts->tv_sec) {
return (uint64_t)(end_time.tv_sec - ts->tv_sec) * (1000 * 1000 * 1000) +
(end_time.tv_nsec - ts->tv_nsec);
}
return 0;
}
static inline void
@ -9728,8 +9729,6 @@ gc_enter(rb_objspace_t *objspace, enum gc_enter_event event, unsigned int *lock_
{
RB_VM_LOCK_ENTER_LEV(lock_lev);
gc_enter_clock(objspace, event);
switch (event) {
case gc_enter_event_rest:
if (!is_marking(objspace)) break;
@ -9765,7 +9764,6 @@ gc_exit(rb_objspace_t *objspace, enum gc_enter_event event, unsigned int *lock_l
gc_report(1, objspace, "gc_exit: %s [%s]\n", gc_enter_event_cstr(event), gc_current_status(objspace));
during_gc = FALSE;
gc_exit_clock(objspace, event);
RB_VM_LOCK_LEAVE_LEV(lock_lev);
#if RGENGC_CHECK_MODE >= 2
@ -9777,6 +9775,38 @@ gc_exit(rb_objspace_t *objspace, enum gc_enter_event event, unsigned int *lock_l
#endif
}
static void
gc_marking_enter(rb_objspace_t *objspace)
{
GC_ASSERT(during_gc != 0);
gc_clock_start(&objspace->profile.marking_start_time);
}
static void
gc_marking_exit(rb_objspace_t *objspace)
{
GC_ASSERT(during_gc != 0);
objspace->profile.marking_time_ns += gc_clock_end(&objspace->profile.marking_start_time);
}
static void
gc_sweeping_enter(rb_objspace_t *objspace)
{
GC_ASSERT(during_gc != 0);
gc_clock_start(&objspace->profile.sweeping_start_time);
}
static void
gc_sweeping_exit(rb_objspace_t *objspace)
{
GC_ASSERT(during_gc != 0);
objspace->profile.sweeping_time_ns += gc_clock_end(&objspace->profile.sweeping_start_time);
}
static void *
gc_with_gvl(void *ptr)
{
@ -11152,6 +11182,8 @@ gc_latest_gc_info(rb_execution_context_t *ec, VALUE self, VALUE arg)
enum gc_stat_sym {
gc_stat_sym_count,
gc_stat_sym_time,
gc_stat_sym_marking_time,
gc_stat_sym_sweeping_time,
gc_stat_sym_heap_allocated_pages,
gc_stat_sym_heap_sorted_length,
gc_stat_sym_heap_allocatable_pages,
@ -11201,6 +11233,8 @@ setup_gc_stat_symbols(void)
#define S(s) gc_stat_symbols[gc_stat_sym_##s] = ID2SYM(rb_intern_const(#s))
S(count);
S(time);
S(marking_time),
S(sweeping_time),
S(heap_allocated_pages);
S(heap_sorted_length);
S(heap_allocatable_pages);
@ -11242,6 +11276,12 @@ setup_gc_stat_symbols(void)
}
}
static uint64_t
ns_to_ms(uint64_t ns)
{
return ns / (1000 * 1000);
}
static size_t
gc_stat_internal(VALUE hash_or_sym)
{
@ -11267,7 +11307,9 @@ gc_stat_internal(VALUE hash_or_sym)
rb_hash_aset(hash, gc_stat_symbols[gc_stat_sym_##name], SIZET2NUM(attr));
SET(count, objspace->profile.count);
SET(time, (size_t) (objspace->profile.total_time_ns / (1000 * 1000) /* ns -> ms */)); // TODO: UINT64T2NUM
SET(time, (size_t)ns_to_ms(objspace->profile.marking_time_ns + objspace->profile.sweeping_time_ns)); // TODO: UINT64T2NUM
SET(marking_time, (size_t)ns_to_ms(objspace->profile.marking_time_ns));
SET(sweeping_time, (size_t)ns_to_ms(objspace->profile.sweeping_time_ns));
/* implementation dependent counters */
SET(heap_allocated_pages, heap_allocated_pages);

2
gc.rb
Просмотреть файл

@ -282,7 +282,7 @@ module GC
# Return measured \GC total time in nano seconds.
def self.total_time
Primitive.cexpr! %{
ULL2NUM(rb_objspace.profile.total_time_ns)
ULL2NUM(rb_objspace.profile.marking_time_ns + rb_objspace.profile.sweeping_time_ns)
}
end
end

Просмотреть файл

@ -128,6 +128,8 @@ class TestGc < Test::Unit::TestCase
omit 'stress' if GC.stress
stat = GC.stat
# marking_time + sweeping_time could differ from time by 1 because they're stored in nanoseconds
assert_in_delta stat[:time], stat[:marking_time] + stat[:sweeping_time], 1
assert_equal stat[:total_allocated_pages], stat[:heap_allocated_pages] + stat[:total_freed_pages]
assert_operator stat[:heap_sorted_length], :>=, stat[:heap_eden_pages] + stat[:heap_allocatable_pages], "stat is: " + stat.inspect
assert_equal stat[:heap_available_slots], stat[:heap_live_slots] + stat[:heap_free_slots] + stat[:heap_final_slots]