1
0
Fork 0
mirror of https://github.com/ruby/ruby.git synced 2022-11-09 12:17:21 -05:00

GC measurement feature

* `GC.measure_total_time = true` enables total time measurement (default: true)
* `GC.measure_total_time` returns current flag.
* `GC.total_time` returns measured total time in nano seconds.
* `GC.stat(:time)` (and Hash) returns measured total time in milli seconds.
This commit is contained in:
Koichi Sasada 2021-11-10 16:52:22 +09:00
parent 349a179782
commit c347038d4e
Notes: git 2021-11-19 08:32:29 +09:00
3 changed files with 106 additions and 37 deletions

View file

@ -5852,6 +5852,7 @@ gc.$(OBJEXT): {$(VPATH)}encoding.h
gc.$(OBJEXT): {$(VPATH)}eval_intern.h
gc.$(OBJEXT): {$(VPATH)}gc.c
gc.$(OBJEXT): {$(VPATH)}gc.h
gc.$(OBJEXT): {$(VPATH)}gc.rb
gc.$(OBJEXT): {$(VPATH)}gc.rbinc
gc.$(OBJEXT): {$(VPATH)}id.h
gc.$(OBJEXT): {$(VPATH)}id_table.h

97
gc.c
View file

@ -732,6 +732,7 @@ typedef struct rb_objspace {
#if GC_ENABLE_INCREMENTAL_MARK
unsigned int during_incremental_marking : 1;
#endif
unsigned int measure_gc : 1;
} flags;
rb_event_flag_t hook_events;
@ -807,7 +808,7 @@ typedef struct rb_objspace {
size_t total_freed_objects;
size_t total_allocated_pages;
size_t total_freed_pages;
size_t total_time_ns;
uint64_t total_time_ns;
struct timespec start_time;
} profile;
struct gc_list *global_list;
@ -1718,6 +1719,7 @@ rb_objspace_t *
rb_objspace_alloc(void)
{
rb_objspace_t *objspace = calloc1(sizeof(rb_objspace_t));
objspace->flags.measure_gc = 1;
malloc_limit = gc_params.malloc_limit_min;
for (int i = 0; i < SIZE_POOL_COUNT; i++) {
@ -9165,10 +9167,12 @@ gc_enter_count(enum gc_enter_event event)
}
}
#define MEASURE_GC 1
#ifndef MEASURE_GC
#define MEASURE_GC (objspace->flags.measure_gc)
#endif
static bool
gc_enter_event_measure_p(enum gc_enter_event event)
gc_enter_event_measure_p(rb_objspace_t *objspace, enum gc_enter_event event)
{
if (!MEASURE_GC) return false;
@ -9178,33 +9182,47 @@ gc_enter_event_measure_p(enum gc_enter_event event)
case gc_enter_event_sweep_continue:
case gc_enter_event_rest:
return true;
case gc_enter_event_finalizer:
case gc_enter_event_rb_memerror:
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)
{
if (gc_enter_event_measure_p(event)) {
clock_gettime(CLOCK_MONOTONIC, &objspace->profile.start_time);
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;
}
}
}
static void
gc_exit_clock(rb_objspace_t *objspace, enum gc_enter_event event)
{
if (gc_enter_event_measure_p(event)) {
if (gc_enter_event_measure_p(objspace, event)) {
struct timespec end_time;
clock_gettime(CLOCK_MONOTONIC, &end_time);
if (end_time.tv_sec < objspace->profile.start_time.tv_sec) {
return; // ignore
}
size_t ns = (end_time.tv_sec - objspace->profile.start_time.tv_sec) * 1000 * 1000 * 1000 + end_time.tv_nsec - objspace->profile.start_time.tv_nsec;
if ((objspace->profile.start_time.tv_sec > 0 ||
objspace->profile.start_time.tv_nsec > 0) &&
current_process_time(&end_time)) {
objspace->profile.total_time_ns += ns;
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;
}
}
}
}
@ -12411,16 +12429,14 @@ wmap_size(VALUE self)
#define GC_PROFILE_RECORD_DEFAULT_SIZE 100
/* return sec in user time */
static double
getrusage_time(void)
static bool
current_process_time(struct timespec *ts)
{
#if defined(HAVE_CLOCK_GETTIME) && defined(CLOCK_PROCESS_CPUTIME_ID)
{
static int try_clock_gettime = 1;
struct timespec ts;
if (try_clock_gettime && clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &ts) == 0) {
return ts.tv_sec + ts.tv_nsec * 1e-9;
if (try_clock_gettime && clock_gettime(CLOCK_PROCESS_CPUTIME_ID, ts) == 0) {
return true;
}
else {
try_clock_gettime = 0;
@ -12434,7 +12450,9 @@ getrusage_time(void)
struct timeval time;
if (getrusage(RUSAGE_SELF, &usage) == 0) {
time = usage.ru_utime;
return time.tv_sec + time.tv_usec * 1e-6;
ts->tv_sec = time.tv_sec;
ts->tv_nsec = time.tv_usec * 1000;
return true;
}
}
#endif
@ -12442,30 +12460,35 @@ getrusage_time(void)
#ifdef _WIN32
{
FILETIME creation_time, exit_time, kernel_time, user_time;
ULARGE_INTEGER ui;
LONG_LONG q;
double t;
ULARGE_INTEGER ui;
if (GetProcessTimes(GetCurrentProcess(),
if (GetProcessTimes(GetCurrentProcess(),
&creation_time, &exit_time, &kernel_time, &user_time) != 0) {
memcpy(&ui, &user_time, sizeof(FILETIME));
q = ui.QuadPart / 10L;
t = (DWORD)(q % 1000000L) * 1e-6;
q /= 1000000L;
#ifdef __GNUC__
t += q;
#else
t += (double)(DWORD)(q >> 16) * (1 << 16);
t += (DWORD)q & ~(~0 << 16);
#endif
return t;
memcpy(&ui, &user_time, sizeof(FILETIME));
#define PER100NSEC (uint64_t)(1000 * 1000 * 10)
ts->tv_nsec = (long)(ui.QuadPart % PER100NSEC);
ts->tv_sec = (time_t)(ui.QuadPart / PER100NSEC);
return true;
}
}
#endif
return 0.0;
return false;
}
static double
getrusage_time(void)
{
struct timespec ts;
if (current_process_time(&ts)) {
return ts.tv_sec + ts.tv_nsec * 1e-9;
}
else {
return 0.0;
}
}
static inline void
gc_prof_setup_new_record(rb_objspace_t *objspace, unsigned int reason)
{

45
gc.rb
View file

@ -266,6 +266,51 @@ module GC
def self.using_rvargc?
GC::INTERNAL_CONSTANTS[:SIZE_POOL_COUNT] > 1
end
# call-seq:
# GC.measure_total_time = true/false
#
# Enable to measure GC time.
# You can get the result with `GC.stat(:time)`.
# Note that the GC time measurement can introduce the performance regression.
def self.measure_total_time=(flag)
Primitive.cstmt! %{
rb_objspace.flags.measure_gc = RTEST(flag) ? TRUE : FALSE;
return flag;
}
end
# call-seq:
# GC.measure_total_time -> true/false
#
# Return measure_total_time flag (default: true).
# Note that measurement can affect the application performance.
def self.measure_total_time
Primitive.cexpr! %{
RBOOL(rb_objspace.flags.measure_gc)
}
end
Primitive.cinit! %{
#if SIZEOF_LONG == 8
#define UINT64_2NUM RB_ULONG2NUM
#elif SIZEOF_LONG_LONG == 8
#define UINT64_2NUM RB_ULL2NUM
#else
#error Can not make UINT64_2NUM
#endif
}
# call-seq:
# GC.total_time -> int
#
# Return measured GC total time in nano seconds.
def self.total_time
Primitive.cexpr! %{
UINT64_2NUM(rb_objspace.profile.total_time_ns)
}
end
end
module ObjectSpace