diff --git a/a/gc.c b/b/gc.c index bcdc9af..b380842 100644 --- a/a/gc.c +++ b/b/gc.c @@ -23,8 +23,16 @@ #include #include +#ifdef _WIN32 +#include +#else +#include +#endif + #ifdef HAVE_SYS_TIME_H #include +#elif defined(_WIN32) +#include #endif #ifdef HAVE_SYS_RESOURCE_H @@ -211,11 +219,11 @@ getrusage_time(void) if (objspace->profile.run) {\ size_t count = objspace->profile.count;\ objspace->profile.record[count].heap_use_slots = heaps_used;\ - objspace->profile.record[count].heap_live_objects = live;\ + objspace->profile.record[count].heap_live_objects = live_objects;\ objspace->profile.record[count].heap_free_objects = freed;\ objspace->profile.record[count].heap_total_objects = heaps_used * HEAP_OBJ_LIMIT;\ objspace->profile.record[count].have_finalize = final_list ? Qtrue : Qfalse;\ - objspace->profile.record[count].heap_use_size = live * sizeof(RVALUE);\ + objspace->profile.record[count].heap_use_size = live_objects * sizeof(RVALUE);\ objspace->profile.record[count].heap_total_size = heaps_used * (HEAP_OBJ_LIMIT * sizeof(RVALUE));\ }\ } while(0) @@ -232,7 +240,7 @@ getrusage_time(void) if (objspace->profile.run) {\ size_t count = objspace->profile.count;\ objspace->profile.record[count].heap_total_objects = heaps_used * HEAP_OBJ_LIMIT;\ - objspace->profile.record[count].heap_use_size = live * sizeof(RVALUE);\ + objspace->profile.record[count].heap_use_size = live_objects * sizeof(RVALUE);\ objspace->profile.record[count].heap_total_size = heaps_used * HEAP_SIZE;\ }\ } while(0) @@ -372,7 +380,7 @@ rb_objspace_alloc(void) { rb_objspace_t *objspace = malloc(sizeof(rb_objspace_t)); memset(objspace, 0, sizeof(*objspace)); - malloc_limit = GC_MALLOC_LIMIT; + malloc_limit = initial_malloc_limit; ruby_gc_stress = ruby_initial_gc_stress; return objspace; @@ -405,6 +413,170 @@ int ruby_disable_gc_stress = 0; static void run_final(rb_objspace_t *objspace, VALUE obj); static int garbage_collect(rb_objspace_t *objspace); +static unsigned long live_objects = 0; +unsigned long rb_os_live_objects() +{ return live_objects; } + +#if defined(HAVE_LONG_LONG) +static unsigned long long allocated_objects = 0; +unsigned long long rb_os_allocated_objects() +{ return allocated_objects; } +#else +static unsigned long allocated_objects = 0; +unsigned long rb_os_allocated_objects() +{ return allocated_objects; } +#endif + +static int heap_min_slots = HEAP_MIN_SLOTS; +static int heap_free_min = 4096; +static double heap_slots_growth_factor = 1.8; + +static long initial_malloc_limit = GC_MALLOC_LIMIT; +static int verbose_gc_stats = Qfalse; +static FILE* gc_data_file = NULL; + +static void set_gc_parameters() +{ + char *gc_stats_ptr, *min_slots_ptr, *free_min_ptr, *heap_slots_incr_ptr, + *heap_incr_ptr, *malloc_limit_ptr, *gc_heap_file_ptr, *heap_slots_growth_factor_ptr; + + gc_data_file = stderr; + + gc_stats_ptr = getenv("RUBY_GC_STATS"); + if (gc_stats_ptr != NULL) { + int gc_stats_i = atoi(gc_stats_ptr); + if (gc_stats_i > 0) { + verbose_gc_stats = Qtrue; + } + } + + gc_heap_file_ptr = getenv("RUBY_GC_DATA_FILE"); + if (gc_heap_file_ptr != NULL) { + FILE* data_file = fopen(gc_heap_file_ptr, "w"); + if (data_file != NULL) { + gc_data_file = data_file; + } + else { + fprintf(stderr, + "can't open gc log file %s for writing, using default\n", gc_heap_file_ptr); + } + } + + min_slots_ptr = getenv("RUBY_HEAP_MIN_SLOTS"); + if (min_slots_ptr != NULL) { + int min_slots_i = atoi(min_slots_ptr); + if (verbose_gc_stats) { + fprintf(gc_data_file, "RUBY_HEAP_MIN_SLOTS=%s\n", min_slots_ptr); + } + if (min_slots_i > 0) { + heap_min_slots = min_slots_i; + } + } + + free_min_ptr = getenv("RUBY_HEAP_FREE_MIN"); + if (free_min_ptr != NULL) { + int free_min_i = atoi(free_min_ptr); + if (verbose_gc_stats) { + fprintf(gc_data_file, "RUBY_HEAP_FREE_MIN=%s\n", free_min_ptr); + } + if (free_min_i > 0) { + heap_free_min = free_min_i; + } + } + + heap_incr_ptr = getenv("RUBY_HEAP_INCREMENT"); + if (heap_incr_ptr != NULL) { + int heap_incr_i = atoi(heap_incr_ptr); + if (verbose_gc_stats) { + fprintf(gc_data_file, "RUBY_HEAP_INCREMENT=%s\n", heap_incr_ptr); + } + if (heap_incr_i > 0) { + (&rb_objspace)->malloc_params.increase = heap_incr_i; + } + } + + heap_slots_incr_ptr = getenv("RUBY_HEAP_SLOTS_INCREMENT"); + if (heap_slots_incr_ptr != NULL) { + int heap_slots_incr_i = atoi(heap_slots_incr_ptr); + if (verbose_gc_stats) { + fprintf(gc_data_file, "RUBY_HEAP_SLOTS_INCREMENT=%s\n", heap_slots_incr_ptr); + } + if (heap_slots_incr_i > 0) { + (&rb_objspace)->malloc_params.increase = heap_slots_incr_i; + } + } + + heap_slots_growth_factor_ptr = getenv("RUBY_HEAP_SLOTS_GROWTH_FACTOR"); + if (heap_slots_growth_factor_ptr != NULL) { + double heap_slots_growth_factor_d = atoi(heap_slots_growth_factor_ptr); + if (verbose_gc_stats) { + fprintf(gc_data_file, "RUBY_HEAP_SLOTS_GROWTH_FACTOR=%s\n", heap_slots_growth_factor_ptr); + } + if (heap_slots_growth_factor_d > 0) { + heap_slots_growth_factor = heap_slots_growth_factor_d; + } + } + + malloc_limit_ptr = getenv("RUBY_GC_MALLOC_LIMIT"); + if (malloc_limit_ptr != NULL) { + int malloc_limit_i = atol(malloc_limit_ptr); + if (verbose_gc_stats) { + fprintf(gc_data_file, "RUBY_GC_MALLOC_LIMIT=%s\n", malloc_limit_ptr); + } + if (malloc_limit_i > 0) { + initial_malloc_limit = malloc_limit_i; + } + } +} + +/* + * call-seq: + * GC.dump => nil + * + * dumps information about the current GC data structures to the GC log file + * + * GC.dump #=> nil + * + */ + +VALUE +rb_gc_dump() +{ + int i; + rb_objspace_t *objspace = &rb_objspace; + + for (i = 0; i < heaps_used; i++) { + int heap_size = heaps[i].limit; + fprintf(gc_data_file, "HEAP[%2d]: size=%7d\n", i, heap_size); + } + + return Qnil; +} + +/* + * call-seq: + * GC.log String => String + * + * Logs string to the GC data file and returns it. + * + * GC.log "manual GC call" #=> "manual GC call" + * + */ + +VALUE +rb_gc_log(VALUE self, VALUE original_str) +{ + if (original_str == Qnil) { + fprintf(gc_data_file, "\n"); + } + else { + VALUE str = StringValue(original_str); + char *p = RSTRING_PTR(str); + fprintf(gc_data_file, "%s\n", p); + } + return original_str; +} + void rb_global_variable(VALUE *var) { @@ -454,6 +626,10 @@ rb_memerror(void) rb_exc_raise(nomem_error); } +long gc_allocated_size = 0; +long gc_num_allocations = 0; +static int gc_statistics = 0; + /* * call-seq: * GC.stress => true or false @@ -699,6 +875,10 @@ vm_xfree(rb_objspace_t *objspace, void *ptr) void * ruby_xmalloc(size_t size) { + if (gc_statistics) { + gc_allocated_size += size; + gc_num_allocations += 1; + } return vm_xmalloc(&rb_objspace, size); } @@ -744,6 +924,13 @@ ruby_xfree(void *x) vm_xfree(&rb_objspace, x); } +#if HAVE_LONG_LONG +#define GC_TIME_TYPE LONG_LONG +#else +#define GC_TIME_TYPE long +#endif +static GC_TIME_TYPE gc_time = 0; +static int gc_collections = 0; /* * call-seq: @@ -775,7 +962,7 @@ rb_gc_enable(void) * Disables garbage collection, returning true if garbage * collection was already disabled. * - * GC.disable #=> false + * GC.disable #=> false or true * GC.disable #=> true * */ @@ -790,6 +977,139 @@ rb_gc_disable(void) return old; } +/* + * call-seq: + * GC.enable_stats => true or false + * + * Enables garbage collection statistics, returning true if garbage + * collection statistics was already enabled. + * + * GC.enable_stats #=> false or true + * GC.enable_stats #=> true + * + */ + +VALUE +rb_gc_enable_stats() +{ + int old = gc_statistics; + gc_statistics = Qtrue; + return old; +} + +/* + * call-seq: + * GC.disable_stats => true or false + * + * Disables garbage collection statistics, returning true if garbage + * collection statistics was already disabled. + * + * GC.disable_stats #=> false or true + * GC.disable_stats #=> true + * + */ + +VALUE +rb_gc_disable_stats() +{ + int old = gc_statistics; + gc_statistics = Qfalse; + gc_allocated_size = 0; + gc_num_allocations = 0; + return old; +} + +/* + * call-seq: + * GC.clear_stats => nil + * + * Clears garbage collection statistics, returning nil. This resets the number + * of collections (GC.collections) and the time used (GC.time) to 0. + * + * GC.clear_stats #=> nil + * + */ + +VALUE +rb_gc_clear_stats() +{ + gc_collections = 0; + gc_time = 0; + gc_allocated_size = 0; + gc_num_allocations = 0; + return Qnil; +} + +/* + * call-seq: + * GC.allocated_size => Integer + * + * Returns the size of memory (in bytes) allocated since GC statistics collection + * was enabled. + * + * GC.allocated_size #=> 35 + * + */ +VALUE +rb_gc_allocated_size() +{ + return INT2NUM(gc_allocated_size); +} + +/* + * call-seq: + * GC.num_allocations => Integer + * + * Returns the number of memory allocations since GC statistics collection + * was enabled. + * + * GC.num_allocations #=> 150 + * + */ +VALUE +rb_gc_num_allocations() +{ + return INT2NUM(gc_num_allocations); +} + +/* + * call-seq: + * GC.collections => Integer + * + * Returns the number of garbage collections performed while GC statistics collection + * was enabled. + * + * GC.collections #=> 35 + * + */ + +VALUE +rb_gc_collections() +{ + return INT2NUM(gc_collections); +} + +/* + * call-seq: + * GC.time => Integer + * + * Returns the time spent during garbage collection while GC statistics collection + * was enabled (in micro seconds). + * + * GC.time #=> 20000 + * + */ + +VALUE +rb_gc_time() +{ +#if HAVE_LONG_LONG + return LL2NUM(gc_time); +#else + return LONG2NUM(gc_time); +#endif +} + VALUE rb_mGC; void @@ -921,7 +1241,7 @@ init_heap(rb_objspace_t *objspace) { size_t add, i; - add = HEAP_MIN_SLOTS / HEAP_OBJ_LIMIT; + add = heap_min_slots / HEAP_OBJ_LIMIT; if ((heaps_used + add) > heaps_length) { allocate_heaps(objspace, heaps_used + add); @@ -934,12 +1254,12 @@ init_heap(rb_objspace_t *objspace) objspace->profile.invoke_time = getrusage_time(); } - static void set_heaps_increment(rb_objspace_t *objspace) { - size_t next_heaps_length = heaps_used * 1.8; + size_t next_heaps_length = heaps_used * heap_slots_growth_factor; heaps_inc = next_heaps_length - heaps_used; + if (next_heaps_length <= 0) next_heaps_length = heap_min_slots; if (next_heaps_length > heaps_length) { allocate_heaps(objspace, next_heaps_length); @@ -980,6 +1300,8 @@ rb_newobj_from_heap(rb_objspace_t *objspace) RANY(obj)->line = rb_sourceline(); #endif + live_objects++; + allocated_objects++; return obj; } @@ -1671,6 +1993,39 @@ finalize_list(rb_objspace_t *objspace, RVALUE *p) } } +static char* obj_type(int tp) +{ + switch (tp) { + case T_NIL : return "NIL"; + case T_OBJECT : return "OBJECT"; + case T_CLASS : return "CLASS"; + case T_ICLASS : return "ICLASS"; + case T_MODULE : return "MODULE"; + case T_FLOAT : return "FLOAT"; + case T_STRING : return "STRING"; + case T_REGEXP : return "REGEXP"; + case T_ARRAY : return "ARRAY"; + case T_FIXNUM : return "FIXNUM"; + case T_HASH : return "HASH"; + case T_STRUCT : return "STRUCT"; + case T_BIGNUM : return "BIGNUM"; + case T_FILE : return "FILE"; + + case T_TRUE : return "TRUE"; + case T_FALSE : return "FALSE"; + case T_DATA : return "DATA"; + case T_MATCH : return "MATCH"; + case T_SYMBOL : return "SYMBOL"; + + //case T_BLKTAG : return "BLKTAG"; + case T_UNDEF : return "UNDEF"; + case T_RATIONAL : return "RATIONAL"; + case T_COMPLEX : return "COMPLEX"; + case T_NODE : return "NODE"; + default: return "____"; + } +} + static void free_unused_heaps(rb_objspace_t *objspace) { @@ -1711,14 +2066,24 @@ gc_sweep(rb_objspace_t *objspace) RVALUE *p, *pend, *final_list; size_t freed = 0; size_t i; - size_t live = 0, free_min = 0, do_heap_free = 0; + size_t free_min = 0, do_heap_free = 0; + live_objects = 0; + + unsigned long really_freed = 0; + int free_counts[256]; + int live_counts[256]; + int do_gc_stats = gc_statistics & verbose_gc_stats; do_heap_free = (heaps_used * HEAP_OBJ_LIMIT) * 0.65; free_min = (heaps_used * HEAP_OBJ_LIMIT) * 0.2; - if (free_min < FREE_MIN) { + if (free_min < heap_free_min) { do_heap_free = heaps_used * HEAP_OBJ_LIMIT; - free_min = FREE_MIN; + free_min = heap_free_min; + } + + if (do_gc_stats) { + for (i = 0 ; i< 256; i++) { free_counts[i] = live_counts[i] = 0; } } freelist = 0; @@ -1739,6 +2104,9 @@ gc_sweep(rb_objspace_t *objspace) if (!deferred) { p->as.free.flags = T_ZOMBIE; RDATA(p)->dfree = 0; + if (do_gc_stats) { + really_freed++; + } } p->as.free.flags |= FL_MARK; p->as.free.next = final_list; @@ -1746,6 +2114,12 @@ gc_sweep(rb_objspace_t *objspace) final_num++; } else { + if (do_gc_stats) { + int obt = p->as.basic.flags & T_MASK; + if (obt) { + free_counts[obt]++; + } + } add_freelist(objspace, p); free_num++; } @@ -1756,7 +2130,10 @@ gc_sweep(rb_objspace_t *objspace) } else { RBASIC(p)->flags &= ~FL_MARK; - live++; + live_objects++; + if (do_gc_stats) { + live_counts[RANY((VALUE)p)->as.basic.flags & T_MASK]++; + } } p++; } @@ -1777,8 +2154,8 @@ gc_sweep(rb_objspace_t *objspace) } GC_PROF_SET_MALLOC_INFO; if (malloc_increase > malloc_limit) { - malloc_limit += (malloc_increase - malloc_limit) * (double)live / (live + freed); - if (malloc_limit < GC_MALLOC_LIMIT) malloc_limit = GC_MALLOC_LIMIT; + malloc_limit += (malloc_increase - malloc_limit) * (double)live_objects / (live_objects + freed); + if (malloc_limit < initial_malloc_limit) malloc_limit = initial_malloc_limit; } malloc_increase = 0; if (freed < free_min) { @@ -1786,6 +2163,20 @@ gc_sweep(rb_objspace_t *objspace) heaps_increment(objspace); } during_gc = 0; + + if (do_gc_stats) { + fprintf(gc_data_file, "objects processed: %.7d\n", live_objects+freed); + fprintf(gc_data_file, "live objects : %.7d\n", live_objects); + fprintf(gc_data_file, "freelist objects : %.7d\n", freed - really_freed); + fprintf(gc_data_file, "freed objects : %.7d\n", really_freed); + for(i=0; i<256; i++) { + if (free_counts[i]>0) { + fprintf(gc_data_file, + "kept %.7d / freed %.7d objects of type %s\n", + live_counts[i], free_counts[i], obj_type(i)); + } + } + } /* clear finalization list */ if (final_list) { @@ -1994,6 +2385,7 @@ garbage_collect(rb_objspace_t *objspace) { struct gc_list *list; rb_thread_t *th = GET_THREAD(); + struct timeval gctv1, gctv2; INIT_GC_PROF_PARAMS; if (GC_NOTIFY) printf("start garbage_collect()\n"); @@ -2013,6 +2405,14 @@ garbage_collect(rb_objspace_t *objspace) } during_gc++; objspace->count++; + + if (gc_statistics) { + gc_collections++; + gettimeofday(&gctv1, NULL); + if (verbose_gc_stats) { + fprintf(gc_data_file, "Garbage collection started\n"); + } + } GC_PROF_TIMER_START; GC_PROF_MARK_TIMER_START; @@ -2060,6 +2460,17 @@ garbage_collect(rb_objspace_t *objspace) GC_PROF_SWEEP_TIMER_START; gc_sweep(objspace); GC_PROF_SWEEP_TIMER_STOP; + + if (gc_statistics) { + GC_TIME_TYPE musecs_used; + gettimeofday(&gctv2, NULL); + musecs_used = ((GC_TIME_TYPE)(gctv2.tv_sec - gctv1.tv_sec) * 1000000) + (gctv2.tv_usec - gctv1.tv_usec); + gc_time += musecs_used; + + if (verbose_gc_stats) { + fprintf(gc_data_file, "GC time: %d msec\n", musecs_used / 1000); + } + } GC_PROF_TIMER_STOP; if (GC_NOTIFY) printf("end garbage_collect()\n"); @@ -2153,6 +2564,7 @@ Init_stack(VALUE *addr) void Init_heap(void) { + set_gc_parameters(); init_heap(&rb_objspace); } @@ -2868,6 +3280,34 @@ gc_profile_report(int argc, VALUE *argv, VALUE self) return Qnil; } +/* call-seq: + * ObjectSpace.live_objects => number + * + * Returns the count of objects currently allocated in the system. This goes + * down after the garbage collector runs. + */ +static +VALUE os_live_objects(VALUE self) +{ return ULONG2NUM(live_objects); } + +/* call-seq: + * ObjectSpace.allocated_objects => number + * + * Returns the count of objects allocated since the Ruby interpreter has + * started. This number can only increase. To know how many objects are + * currently allocated, use ObjectSpace::live_objects + */ +static +VALUE os_allocated_objects(VALUE self) +{ +#if defined(HAVE_LONG_LONG) + return ULL2NUM(allocated_objects); +#else + return ULONG2NUM(allocated_objects); +#endif +} + + /* * The GC module provides an interface to Ruby's mark and @@ -2897,10 +3337,23 @@ Init_GC(void) rb_define_singleton_method(rb_mProfiler, "clear", gc_profile_clear, 0); rb_define_singleton_method(rb_mProfiler, "result", gc_profile_result, 0); rb_define_singleton_method(rb_mProfiler, "report", gc_profile_report, -1); + + rb_define_singleton_method(rb_mGC, "enable_stats", rb_gc_enable_stats, 0); + rb_define_singleton_method(rb_mGC, "disable_stats", rb_gc_disable_stats, 0); + rb_define_singleton_method(rb_mGC, "clear_stats", rb_gc_clear_stats, 0); + rb_define_singleton_method(rb_mGC, "allocated_size", rb_gc_allocated_size, 0); + rb_define_singleton_method(rb_mGC, "num_allocations", rb_gc_num_allocations, 0); + rb_define_singleton_method(rb_mGC, "collections", rb_gc_collections, 0); + rb_define_singleton_method(rb_mGC, "time", rb_gc_time, 0); + rb_define_singleton_method(rb_mGC, "dump", rb_gc_dump, 0); + rb_define_singleton_method(rb_mGC, "log", rb_gc_log, 1); rb_mObSpace = rb_define_module("ObjectSpace"); rb_define_module_function(rb_mObSpace, "each_object", os_each_obj, -1); rb_define_module_function(rb_mObSpace, "garbage_collect", rb_gc_start, 0); + rb_define_module_function(rb_mObSpace, "live_objects", os_live_objects, 0); + rb_define_module_function(rb_mObSpace, "allocated_objects", os_allocated_objects, 0); + rb_define_module_function(rb_mObSpace, "define_finalizer", define_final, -1); rb_define_module_function(rb_mObSpace, "undefine_finalizer", undefine_final, 1);