File 01-railsbench-gc-patch.patch of Package ruby-railsexpress
diff --git a/gc.c b/gc.c
index 564d260..27d9177 100644
--- gc.c.orig
+++ gc.c
@@ -78,6 +78,17 @@ void *alloca ();
#define GC_MALLOC_LIMIT 8000000
#endif
+#ifndef HAVE_LONG_LONG
+#define LONG_LONG long
+#endif
+
+static size_t heap_free_min = 4096;
+static int heap_min_slots = 10000;
+static int heap_slots_increment = 10000;
+static int initial_heap_slots_increment = 10000;
+static double heap_slots_growth_factor = 1.8;
+static size_t initial_malloc_limit = GC_MALLOC_LIMIT;
+
#define nomem_error GET_VM()->special_exceptions[ruby_error_nomemory]
#define MARK_STACK_MAX 1024
@@ -267,7 +278,7 @@ typedef struct RVALUE {
struct RComplex complex;
} as;
#ifdef GC_DEBUG
- const char *file;
+ VALUE file;
int line;
#endif
} RVALUE;
@@ -314,6 +325,8 @@ typedef struct rb_objspace {
struct {
int dont_gc;
int during_gc;
+ int gc_statistics;
+ int verbose_gc_stats;
} flags;
struct {
st_table *table;
@@ -334,6 +347,14 @@ typedef struct rb_objspace {
struct gc_list *global_list;
unsigned int count;
int gc_stress;
+ long heap_size;
+ unsigned LONG_LONG gc_time_accumulator;
+ FILE* gc_data_file;
+ long gc_collections;
+ unsigned LONG_LONG gc_allocated_size;
+ unsigned LONG_LONG gc_num_allocations;
+ unsigned long live_objects;
+ unsigned LONG_LONG allocated_objects;
} rb_objspace_t;
#if defined(ENABLE_VM_OBJSPACE) && ENABLE_VM_OBJSPACE
@@ -357,6 +378,16 @@ int *ruby_initial_gc_stress_ptr = &rb_objspace.gc_stress;
#define heaps_freed objspace->heap.freed
#define dont_gc objspace->flags.dont_gc
#define during_gc objspace->flags.during_gc
+#define gc_statistics objspace->flags.gc_statistics
+#define verbose_gc_stats objspace->flags.verbose_gc_stats
+#define heap_size objspace->heap_size
+#define gc_time_accumulator objspace->gc_time_accumulator
+#define gc_data_file objspace->gc_data_file
+#define gc_collections objspace->gc_collections
+#define gc_allocated_size objspace->gc_allocated_size
+#define gc_num_allocations objspace->gc_num_allocations
+#define live_objects objspace->live_objects
+#define allocated_objects objspace->allocated_objects
#define finalizer_table objspace->final.table
#define deferred_final_list objspace->final.deferred
#define mark_stack objspace->markstack.buffer
@@ -375,7 +406,8 @@ 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;
@@ -409,23 +441,8 @@ rb_objspace_free(rb_objspace_t *objspace)
}
#endif
-/* tiny heap size */
-/* 32KB */
-/*#define HEAP_SIZE 0x8000 */
-/* 128KB */
-/*#define HEAP_SIZE 0x20000 */
-/* 64KB */
-/*#define HEAP_SIZE 0x10000 */
-/* 16KB */
-#define HEAP_SIZE 0x4000
-/* 8KB */
-/*#define HEAP_SIZE 0x2000 */
-/* 4KB */
-/*#define HEAP_SIZE 0x1000 */
-/* 2KB */
-/*#define HEAP_SIZE 0x800 */
-
-#define HEAP_OBJ_LIMIT (HEAP_SIZE / sizeof(struct RVALUE))
+#define HEAP_OBJ_LIMIT 8000
+#define HEAP_SIZE (HEAP_OBJ_LIMIT * sizeof(struct RVALUE))
extern VALUE rb_cMutex;
extern st_table *rb_class_tbl;
@@ -673,6 +690,11 @@ vm_xmalloc(rb_objspace_t *objspace, size_t size)
mem = (size_t *)mem + 1;
#endif
+ if (gc_statistics) {
+ gc_allocated_size += size;
+ gc_num_allocations += 1;
+ }
+
return mem;
}
@@ -780,6 +802,92 @@ ruby_xfree(void *x)
vm_xfree(&rb_objspace, x);
}
+static void set_gc_parameters(rb_objspace_t *objspace)
+{
+ char *envp;
+
+ gc_data_file = stderr;
+
+ envp = getenv("RUBY_GC_STATS");
+ if (envp != NULL) {
+ int i = atoi(envp);
+ if (i > 0) {
+ verbose_gc_stats = 1;
+ }
+ /* child processes should not inherit RUBY_GC_STATS */
+ ruby_unsetenv("RUBY_GC_STATS");
+ }
+
+ envp = getenv("RUBY_GC_DATA_FILE");
+ if (envp != NULL) {
+ FILE* data_file = fopen(envp, "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", envp);
+ }
+ /* child processes should not inherit RUBY_GC_DATA_FILE to avoid clobbering */
+ ruby_unsetenv("RUBY_GC_DATA_FILE");
+ }
+
+ envp = getenv("RUBY_HEAP_MIN_SLOTS");
+ if (envp != NULL) {
+ int i = atoi(envp);
+ if (verbose_gc_stats) {
+ fprintf(gc_data_file, "RUBY_HEAP_MIN_SLOTS=%s\n", envp);
+ }
+ if (i > 0) {
+ heap_min_slots = i;
+ }
+ }
+
+ envp = getenv("RUBY_HEAP_FREE_MIN");
+ if (envp != NULL) {
+ int i = atoi(envp);
+ if (verbose_gc_stats) {
+ fprintf(gc_data_file, "RUBY_HEAP_FREE_MIN=%s\n", envp);
+ }
+ if (i > 0) {
+ heap_free_min = i;
+ }
+ }
+
+ envp = getenv("RUBY_HEAP_SLOTS_INCREMENT");
+ if (envp != NULL) {
+ int i = atoi(envp);
+ if (verbose_gc_stats) {
+ fprintf(gc_data_file, "RUBY_HEAP_SLOTS_INCREMENT=%s\n", envp);
+ }
+ heap_slots_increment = i;
+ initial_heap_slots_increment = heap_slots_increment;
+ }
+
+ envp = getenv("RUBY_HEAP_SLOTS_GROWTH_FACTOR");
+ if (envp != NULL) {
+ double d = atof(envp);
+ if (verbose_gc_stats) {
+ fprintf(gc_data_file, "RUBY_HEAP_SLOTS_GROWTH_FACTOR=%s\n", envp);
+ }
+ if (d > 0) {
+ heap_slots_growth_factor = d;
+ }
+ }
+
+ envp = getenv("RUBY_GC_MALLOC_LIMIT");
+ if (envp != NULL) {
+ long l = atol(envp);
+ if (verbose_gc_stats) {
+ fprintf(gc_data_file, "RUBY_GC_MALLOC_LIMIT=%s\n", envp);
+ }
+ if (l > 0) {
+ initial_malloc_limit = l;
+ malloc_limit = initial_malloc_limit;
+ }
+ }
+
+ fflush(gc_data_file);
+}
/*
* call-seq:
@@ -826,6 +934,454 @@ rb_gc_disable(void)
return old ? Qtrue : Qfalse;
}
+/*
+ * call-seq:
+ * GC.enable_stats => true or false
+ *
+ * Enables garbage collection statistics, returning <code>true</code> if garbage
+ * collection statistics was already enabled.
+ *
+ * GC.enable_stats #=> false or true
+ * GC.enable_stats #=> true
+ *
+ */
+
+VALUE
+rb_gc_enable_stats()
+{
+ rb_objspace_t *objspace = &rb_objspace;
+ int old = gc_statistics;
+ gc_statistics = 1;
+ return old ? Qtrue : Qfalse;
+}
+
+/*
+ * call-seq:
+ * GC.disable_stats => true or false
+ *
+ * Disables garbage collection statistics, returning <code>true</code> if garbage
+ * collection statistics was already disabled.
+ *
+ * GC.disable_stats #=> false or true
+ * GC.disable_stats #=> true
+ *
+ */
+
+VALUE
+rb_gc_disable_stats()
+{
+ rb_objspace_t *objspace = &rb_objspace;
+ int old = gc_statistics;
+ gc_statistics = 0;
+ return old ? Qtrue : Qfalse;
+}
+
+/*
+ * call-seq:
+ * GC.stats_enabled? => true or false
+ *
+ * Check whether GC stats have been enabled.
+ *
+ * GC.stats_enabled? #=> false or true
+ *
+ */
+
+VALUE
+rb_gc_stats_enabled()
+{
+ rb_objspace_t *objspace = &rb_objspace;
+ return gc_statistics ? Qtrue : Qfalse;
+}
+
+
+/*
+ * 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()
+{
+ rb_objspace_t *objspace = &rb_objspace;
+ gc_collections = 0;
+ gc_time_accumulator = 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()
+{
+ rb_objspace_t *objspace = &rb_objspace;
+#if HAVE_LONG_LONG
+ return ULL2NUM(gc_allocated_size);
+#else
+ return ULONG2NUM(gc_allocated_size);
+#endif
+}
+
+/*
+ * 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()
+{
+ rb_objspace_t *objspace = &rb_objspace;
+#if HAVE_LONG_LONG
+ return ULL2NUM(gc_num_allocations);
+#else
+ return ULONG2NUM(gc_num_allocations);
+#endif
+}
+
+/*
+ * call-seq:
+ * GC.enable_trace => true or false
+ *
+ * Enables garbage collection tracing, returning <code>true</code> if garbage
+ * collection tracing was already enabled.
+ *
+ * GC.enable_trace #=> false or true
+ * GC.enable_trace #=> true
+ *
+ */
+
+VALUE
+rb_gc_enable_trace()
+{
+ rb_objspace_t *objspace = &rb_objspace;
+ int old = verbose_gc_stats;
+ verbose_gc_stats = 1;
+ return old ? Qtrue : Qfalse;
+}
+
+/*
+ * call-seq:
+ * GC.disable_trace => true or false
+ *
+ * Disables garbage collection tracing, returning <code>true</code> if garbage
+ * collection tracing was already disabled.
+ *
+ * GC.disable_trace #=> false or true
+ * GC.disable_trace #=> true
+ *
+ */
+
+VALUE
+rb_gc_disable_trace()
+{
+ rb_objspace_t *objspace = &rb_objspace;
+ int old = verbose_gc_stats;
+ verbose_gc_stats = 0;
+ return old ? Qtrue : Qfalse;
+}
+
+/*
+ * call-seq:
+ * GC.trace_enabled? => true or false
+ *
+ * Check whether GC tracing has been enabled.
+ *
+ * GC.trace_enabled? #=> false or true
+ *
+ */
+
+VALUE
+rb_gc_trace_enabled()
+{
+ rb_objspace_t *objspace = &rb_objspace;
+ return verbose_gc_stats ? Qtrue : Qfalse;
+}
+
+
+const char* GC_LOGFILE_IVAR = "@gc_logfile_name";
+
+/*
+ * call-seq:
+ * GC.log_file(filename=nil, mode="w") => boolean
+ *
+ * Changes the GC data log file. Closes the currently open logfile.
+ * Returns true if the file was successfully opened for
+ * writing. Returns false if the file could not be opened for
+ * writing. Returns the name of the current logfile (or nil) if no
+ * parameter is given. Restores logging to stderr when given nil as
+ * an argument.
+ *
+ * GC.log_file #=> nil
+ * GC.log_file "/tmp/gc.log" #=> true
+ * GC.log_file #=> "/tmp/gc.log"
+ * GC.log_file nil #=> true
+ *
+ */
+
+VALUE
+rb_gc_log_file(int argc, VALUE *argv, VALUE self)
+{
+ rb_objspace_t *objspace = &rb_objspace;
+ VALUE filename = Qnil;
+ VALUE mode_str = Qnil;
+ FILE* f = NULL;
+ const char* mode = "w";
+
+ VALUE current_logfile_name = rb_iv_get(rb_mGC, GC_LOGFILE_IVAR);
+
+ if (argc==0)
+ return current_logfile_name;
+
+ rb_scan_args(argc, argv, "02", &filename, &mode_str);
+
+ if (filename == Qnil) {
+ /* close current logfile and reset logfile to stderr */
+ if (gc_data_file != stderr) {
+ fclose(gc_data_file);
+ gc_data_file = stderr;
+ rb_iv_set(rb_mGC, GC_LOGFILE_IVAR, Qnil);
+ }
+ return Qtrue;
+ }
+
+ /* we have a real logfile name */
+ filename = StringValue(filename);
+
+ if (rb_equal(current_logfile_name, filename) == Qtrue) {
+ /* do nothing if we get the file name we're already logging to */
+ return Qtrue;
+ }
+
+ /* get mode for file opening */
+ if (mode_str != Qnil)
+ {
+ mode = RSTRING_PTR(StringValue(mode_str));
+ }
+
+ /* try to open file in given mode */
+ if (f = fopen(RSTRING_PTR(filename), mode)) {
+ if (gc_data_file != stderr) {
+ fclose(gc_data_file);
+ }
+ gc_data_file = f;
+ rb_iv_set(rb_mGC, GC_LOGFILE_IVAR, filename);
+ } else {
+ return Qfalse;
+ }
+ return Qtrue;
+}
+
+/*
+ * 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(self, original_str)
+ VALUE self, original_str;
+{
+ rb_objspace_t *objspace = &rb_objspace;
+ 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;
+}
+
+/*
+ * 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()
+{
+ rb_objspace_t *objspace = &rb_objspace;
+ size_t i;
+
+ for (i = 0; i < heaps_used; i++) {
+ size_t limit = heaps[i].limit;
+ fprintf(gc_data_file, "HEAP[%2lu]: size=%7lu\n", (unsigned long)i, (unsigned long)limit);
+ }
+
+ return Qnil;
+}
+
+static const char* obj_type(VALUE tp);
+
+#ifdef GC_DEBUG
+/*
+ * call-seq:
+ * GC.dump_file_and_line_info(String, boolean) => nil
+ *
+ * dumps information on which currently allocated object was created by which file and on which line
+ *
+ * GC.dump_file_and_line_info(String, boolean) #=> nil
+ *
+ * The second parameter specifies whether class names should be included in the dump.
+ * Note that including class names will allocate additional string objects on the heap.
+ *
+ */
+
+VALUE
+rb_gc_dump_file_and_line_info(int argc, VALUE *argv)
+{
+ rb_objspace_t *objspace = &rb_objspace;
+ VALUE filename, str, include_classnames = Qnil;
+ char *fname = NULL;
+ char *klass = NULL;
+ FILE* f = NULL;
+ size_t i = 0;
+
+ rb_scan_args(argc, argv, "11", &filename, &include_classnames);
+
+ str = StringValue(filename);
+ fname = RSTRING_PTR(str);
+ f = fopen(fname, "w");
+
+ for (i = 0; i < heaps_used; i++) {
+ RVALUE *p, *pend;
+
+ p = heaps[i].slot; pend = p + heaps[i].limit;
+ for (;p < pend; p++) {
+ if (p->as.basic.flags) {
+ const char *src_filename = (p->file && p->file != Qnil )? RSTRING_PTR(p->file) : "";
+ fprintf(f, "%s:%s:%d", obj_type(p->as.basic.flags & T_MASK), src_filename, (int)p->line);
+ // rb_obj_classname will create objects on the heap, we need a better solution
+ if (include_classnames == Qtrue) {
+ /* write the class */
+ fprintf(f, ":");
+ switch (BUILTIN_TYPE(p)) {
+ case T_NONE:
+ fprintf(f, "__none__");
+ break;
+ case T_UNDEF:
+ fprintf(f, "__undef__");
+ break;
+ case T_NODE:
+ fprintf(f, "__node__");
+ break;
+ default:
+ if (!p->as.basic.klass) {
+ fprintf(f, "__unknown__");
+ } else {
+ fprintf(f, "%s", rb_obj_classname((VALUE)p));
+ }
+ }
+ /* print object size for some known object types */
+ switch (BUILTIN_TYPE(p)) {
+ case T_STRING:
+ fprintf(f, ":%lu", RSTRING_LEN(p));
+ break;
+ case T_ARRAY:
+ fprintf(f, ":%lu", RARRAY_LEN(p));
+ break;
+ case T_HASH:
+ fprintf(f, ":%lu", (long unsigned int)RHASH_SIZE(p));
+ break;
+ }
+ }
+ fprintf(f, "\n");
+ }
+ }
+ }
+ fclose(f);
+ return Qnil;
+}
+#endif
+
+/*
+ * call-seq:
+ * GC.heap_slots => Integer
+ *
+ * Returns the number of heap slots available for object allocations.
+ *
+ * GC.heap_slots #=> 10000
+ *
+ */
+VALUE
+rb_gc_heap_slots()
+{
+ rb_objspace_t *objspace = &rb_objspace;
+ return LONG2NUM(heap_size);
+}
+
+
+/*
+ * 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()
+{
+ rb_objspace_t *objspace = &rb_objspace;
+ return LONG2NUM(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()
+{
+ rb_objspace_t *objspace = &rb_objspace;
+#if HAVE_LONG_LONG
+ return LL2NUM(gc_time_accumulator);
+#else
+ return LONG2NUM(gc_time_accumulator);
+#endif
+}
+
VALUE rb_mGC;
void
@@ -944,6 +1500,7 @@ assign_heap_slot(rb_objspace_t *objspace)
if (lomem == 0 || lomem > p) lomem = p;
if (himem < pend) himem = pend;
heaps_used++;
+ heap_size += objs;
while (p < pend) {
p->as.free.flags = 0;
@@ -958,7 +1515,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 (!add) {
add = 1;
@@ -979,7 +1536,7 @@ init_heap(rb_objspace_t *objspace)
static void
set_heaps_increment(rb_objspace_t *objspace)
{
- size_t next_heaps_length = (size_t)(heaps_used * 1.8);
+ size_t next_heaps_length = (size_t)(heaps_used * heap_slots_growth_factor);
if (next_heaps_length == heaps_used) {
next_heaps_length++;
@@ -1005,6 +1562,22 @@ heaps_increment(rb_objspace_t *objspace)
#define RANY(o) ((RVALUE*)(o))
+#ifdef GC_DEBUG
+static VALUE
+_rb_sourcefile(void)
+{
+ rb_thread_t *th = GET_THREAD();
+ rb_control_frame_t *cfp = rb_vm_get_ruby_level_next_cfp(th, th->cfp);
+
+ if (cfp) {
+ return cfp->iseq->filename;
+ }
+ else {
+ return Qnil;
+ }
+}
+#endif
+
static VALUE
rb_newobj_from_heap(rb_objspace_t *objspace)
{
@@ -1022,9 +1595,11 @@ rb_newobj_from_heap(rb_objspace_t *objspace)
MEMZERO((void*)obj, RVALUE, 1);
#ifdef GC_DEBUG
- RANY(obj)->file = rb_sourcefile();
+ RANY(obj)->file = _rb_sourcefile();
RANY(obj)->line = rb_sourceline();
#endif
+ live_objects++;
+ allocated_objects++;
return obj;
}
@@ -1525,6 +2100,12 @@ gc_mark_children(rb_objspace_t *objspace, VALUE ptr, int lev)
{
register RVALUE *obj = RANY(ptr);
+#ifdef GC_DEBUG
+ if (obj->file && obj->file != Qnil && is_pointer_to_heap(objspace, (void*)obj->file)) {
+ gc_mark(objspace, obj->file, lev);
+ }
+#endif
+
goto marking; /* skip */
again:
@@ -1534,6 +2115,12 @@ gc_mark_children(rb_objspace_t *objspace, VALUE ptr, int lev)
if (obj->as.basic.flags & FL_MARK) return; /* already marked */
obj->as.basic.flags |= FL_MARK;
+#ifdef GC_DEBUG
+ if (obj->file && obj->file != Qnil && is_pointer_to_heap(objspace, (void*)obj->file)) {
+ gc_mark(objspace, obj->file, lev);
+ }
+#endif
+
marking:
if (FL_TEST(obj, FL_EXIVAR)) {
rb_mark_generic_ivar(ptr);
@@ -1851,6 +2438,39 @@ free_unused_heaps(rb_objspace_t *objspace)
}
}
+static const char* obj_type(VALUE type)
+{
+ switch (type) {
+ 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_COMPLEX: return "COMPLEX";
+ case T_RATIONAL: return "RATIONAL";
+ 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_ZOMBIE : return "ZOMBIE";
+
+ case T_UNDEF : return "UNDEF";
+ case T_NODE : return "NODE";
+ default: return "____";
+ }
+}
+
static void
gc_sweep(rb_objspace_t *objspace)
{
@@ -1859,12 +2479,27 @@ gc_sweep(rb_objspace_t *objspace)
size_t i;
size_t live = 0, free_min = 0, do_heap_free = 0;
+ long max_blocks_to_free = heaps_used - (heap_min_slots / HEAP_OBJ_LIMIT);
+ int freed_blocks = 0;
+
+ unsigned long processed = 0;
+ unsigned long freelist_size = 0;
+ unsigned long zombies = 0;
+ unsigned long free_counts[T_MASK];
+ unsigned long live_counts[T_MASK];
+ int do_gc_stats = gc_statistics & verbose_gc_stats;
+
+ if (do_gc_stats) {
+ MEMZERO((void*)free_counts, unsigned long, T_MASK);
+ MEMZERO((void*)live_counts, unsigned long, T_MASK);
+ }
+
do_heap_free = (size_t)((heaps_used * HEAP_OBJ_LIMIT) * 0.65);
free_min = (size_t)((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;
}
freelist = 0;
@@ -1881,10 +2516,15 @@ gc_sweep(rb_objspace_t *objspace)
p = heaps[i].slot; pend = p + heaps[i].limit;
while (p < pend) {
if (!(p->as.basic.flags & FL_MARK)) {
+ if (do_gc_stats && !p->as.basic.flags) {
+ /* slot was free before GC */
+ freelist_size++;
+ }
if (p->as.basic.flags &&
((deferred = obj_free(objspace, (VALUE)p)) ||
((FL_TEST(p, FL_FINALIZE)) && need_call_final))) {
if (!deferred) {
+ if (do_gc_stats) zombies++;
p->as.free.flags = T_ZOMBIE;
RDATA(p)->dfree = 0;
}
@@ -1894,6 +2534,10 @@ gc_sweep(rb_objspace_t *objspace)
final_num++;
}
else {
+ if (do_gc_stats) {
+ VALUE obt = p->as.basic.flags & T_MASK;
+ if (obt) free_counts[obt]++;
+ }
add_freelist(objspace, p);
free_num++;
}
@@ -1901,16 +2545,23 @@ gc_sweep(rb_objspace_t *objspace)
else if (BUILTIN_TYPE(p) == T_ZOMBIE) {
/* objects to be finalized */
/* do nothing remain marked */
+ if (do_gc_stats) zombies++;
}
else {
RBASIC(p)->flags &= ~FL_MARK;
live++;
+ if (do_gc_stats) {
+ live_counts[p->as.basic.flags & T_MASK]++;
+ }
}
p++;
}
- if (final_num + free_num == heaps[i].limit && freed > do_heap_free) {
+ if (final_num + free_num == heaps[i].limit && freed > do_heap_free && freed_blocks < max_blocks_to_free) {
RVALUE *pp;
+ freed_blocks += 1;
+ heap_size -= final_num + free_num;
+
for (pp = final_list; pp != final; pp = pp->as.free.next) {
RDATA(pp)->dmark = (void (*)())(VALUE)&heaps[i];
pp->as.free.flags |= FL_SINGLETON; /* freeing page mark */
@@ -1922,11 +2573,13 @@ gc_sweep(rb_objspace_t *objspace)
else {
freed += free_num;
}
+ processed += heaps[i].limit;
}
GC_PROF_SET_MALLOC_INFO;
if (malloc_increase > malloc_limit) {
malloc_limit += (size_t)((malloc_increase - malloc_limit) * (double)live / (live + freed));
- if (malloc_limit < GC_MALLOC_LIMIT) malloc_limit = GC_MALLOC_LIMIT;
+ if (malloc_limit < initial_malloc_limit) malloc_limit = initial_malloc_limit;
+
}
malloc_increase = 0;
if (freed < free_min) {
@@ -1934,6 +2587,25 @@ gc_sweep(rb_objspace_t *objspace)
heaps_increment(objspace);
}
during_gc = 0;
+ live_objects = live;
+
+ /* log gc stats if requested */
+ if (do_gc_stats) {
+ fprintf(gc_data_file, "objects processed: %.7lu\n", (unsigned long)processed);
+ fprintf(gc_data_file, "live objects : %.7lu\n", (unsigned long)live);
+ fprintf(gc_data_file, "freelist objects : %.7lu\n", (unsigned long)freelist_size);
+ fprintf(gc_data_file, "freed objects : %.7lu\n", (unsigned long)freed);
+ fprintf(gc_data_file, "zombies : %.7lu\n", (unsigned long)zombies);
+ for(i=0; i<T_MASK; i++) {
+ if (free_counts[i]>0 || live_counts[i]>0) {
+ fprintf(gc_data_file,
+ "kept %.7lu / freed %.7lu objects of type %s\n",
+ (unsigned long)live_counts[i], (unsigned long)free_counts[i], obj_type((int)i));
+ }
+ }
+ rb_gc_dump();
+ fflush(gc_data_file);
+ }
/* clear finalization list */
if (final_list) {
@@ -2140,6 +2812,7 @@ void rb_gc_mark_encodings(void);
static int
garbage_collect(rb_objspace_t *objspace)
{
+ struct timeval gctv1, gctv2;
struct gc_list *list;
rb_thread_t *th = GET_THREAD();
INIT_GC_PROF_PARAMS;
@@ -2162,6 +2835,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;
SET_STACK_END;
@@ -2216,6 +2897,19 @@ garbage_collect(rb_objspace_t *objspace)
GC_PROF_TIMER_STOP;
if (GC_NOTIFY) printf("end garbage_collect()\n");
+
+ if (gc_statistics) {
+ unsigned LONG_LONG musecs_used;
+ gettimeofday(&gctv2, NULL);
+ musecs_used = ((unsigned LONG_LONG)(gctv2.tv_sec - gctv1.tv_sec) * 1000000) + (gctv2.tv_usec - gctv1.tv_usec);
+ gc_time_accumulator += musecs_used;
+
+ if (verbose_gc_stats) {
+ fprintf(gc_data_file, "GC time: %lu msec\n", (unsigned long)(musecs_used / 1000));
+ fflush(gc_data_file);
+ }
+ }
+
return TRUE;
}
@@ -2298,6 +2992,7 @@ Init_stack(volatile VALUE *addr)
void
Init_heap(void)
{
+ set_gc_parameters(&rb_objspace);
init_heap(&rb_objspace);
}
@@ -2982,6 +3677,49 @@ count_objects(int argc, VALUE *argv, VALUE os)
return hash;
}
+/* 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)
+{
+ rb_objspace_t *objspace = &rb_objspace;
+ return ULONG2NUM(live_objects);
+}
+
+unsigned long rb_os_live_objects()
+{
+ rb_objspace_t *objspace = &rb_objspace;
+ return 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)
+{
+ rb_objspace_t *objspace = &rb_objspace;
+#if defined(HAVE_LONG_LONG)
+ return ULL2NUM(allocated_objects);
+#else
+ return ULONG2NUM(allocated_objects);
+#endif
+}
+
+unsigned LONG_LONG rb_os_allocated_objects()
+{
+ rb_objspace_t *objspace = &rb_objspace;
+ return allocated_objects;
+}
+
/*
* call-seq:
* GC.count -> Integer
@@ -3188,6 +3926,28 @@ Init_GC(void)
rb_define_singleton_method(rb_mGC, "count", gc_count, 0);
rb_define_method(rb_mGC, "garbage_collect", rb_gc_start, 0);
+ 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, "stats_enabled?", rb_gc_stats_enabled, 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, "heap_slots", rb_gc_heap_slots, 0);
+ rb_define_const(rb_mGC, "HEAP_SLOT_SIZE", INT2FIX(sizeof(RVALUE)));
+
+ rb_define_singleton_method(rb_mGC, "log", rb_gc_log, 1);
+ rb_define_singleton_method(rb_mGC, "log_file", rb_gc_log_file, -1);
+ rb_define_singleton_method(rb_mGC, "enable_trace", rb_gc_enable_trace, 0);
+ rb_define_singleton_method(rb_mGC, "disable_trace", rb_gc_disable_trace, 0);
+ rb_define_singleton_method(rb_mGC, "trace_enabled?", rb_gc_trace_enabled, 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);
+#ifdef GC_DEBUG
+ rb_define_singleton_method(rb_mGC, "dump_file_and_line_info", rb_gc_dump_file_and_line_info, -1);
+#endif
+
rb_mProfiler = rb_define_module_under(rb_mGC, "Profiler");
rb_define_singleton_method(rb_mProfiler, "enabled?", gc_profile_enable_get, 0);
rb_define_singleton_method(rb_mProfiler, "enable", gc_profile_enable, 0);
@@ -3201,6 +3961,9 @@ Init_GC(void)
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);
diff --git sample/test.rb.orig sample/test.rb
old mode 100644
new mode 100755