forked from Lainports/freebsd-ports
535 lines
13 KiB
Text
535 lines
13 KiB
Text
--- gc.c.orig 2006-10-30 04:35:55.000000000 +0100
|
|
+++ gc.c 2006-11-06 20:25:12.562500000 +0100
|
|
@@ -22,8 +22,16 @@
|
|
#include <setjmp.h>
|
|
#include <sys/types.h>
|
|
|
|
+#ifdef _WIN32
|
|
+#include <string.h>
|
|
+#else
|
|
+#include <strings.h>
|
|
+#endif
|
|
+
|
|
#ifdef HAVE_SYS_TIME_H
|
|
#include <sys/time.h>
|
|
+#elif defined(_WIN32)
|
|
+#include <time.h>
|
|
#endif
|
|
|
|
#ifdef HAVE_SYS_RESOURCE_H
|
|
@@ -40,7 +48,6 @@
|
|
#if !defined(setjmp) && defined(HAVE__SETJMP)
|
|
#define setjmp(env) _setjmp(env)
|
|
#endif
|
|
-
|
|
/* Make alloca work the best possible way. */
|
|
#ifdef __GNUC__
|
|
# ifndef atarist
|
|
@@ -159,8 +166,17 @@
|
|
RUBY_CRITICAL(free(x));
|
|
}
|
|
|
|
+#if HAVE_LONG_LONG
|
|
+#define GC_TIME_TYPE LONG_LONG
|
|
+#else
|
|
+#define GC_TIME_TYPE long
|
|
+#endif
|
|
+
|
|
extern int ruby_in_compile;
|
|
static int dont_gc;
|
|
+static int gc_statistics = 0;
|
|
+static GC_TIME_TYPE gc_time = 0;
|
|
+static int gc_collections = 0;
|
|
static int during_gc;
|
|
static int need_call_final = 0;
|
|
static st_table *finalizer_table = 0;
|
|
@@ -195,7 +211,7 @@
|
|
* Disables garbage collection, returning <code>true</code> if garbage
|
|
* collection was already disabled.
|
|
*
|
|
- * GC.disable #=> false
|
|
+ * GC.disable #=> false or true
|
|
* GC.disable #=> true
|
|
*
|
|
*/
|
|
@@ -209,6 +225,104 @@
|
|
return old;
|
|
}
|
|
|
|
+/*
|
|
+ * 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()
|
|
+{
|
|
+ int old = gc_statistics;
|
|
+ gc_statistics = Qtrue;
|
|
+ return old;
|
|
+}
|
|
+
|
|
+/*
|
|
+ * 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()
|
|
+{
|
|
+ int old = gc_statistics;
|
|
+ gc_statistics = Qfalse;
|
|
+ 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;
|
|
+ return Qnil;
|
|
+}
|
|
+
|
|
+/*
|
|
+ * 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;
|
|
|
|
static struct gc_list {
|
|
@@ -300,7 +414,7 @@
|
|
static RVALUE *freelist = 0;
|
|
static RVALUE *deferred_final_list = 0;
|
|
|
|
-#define HEAPS_INCREMENT 10
|
|
+static int heaps_increment = 10;
|
|
static struct heaps_slot {
|
|
void *membase;
|
|
RVALUE *slot;
|
|
@@ -309,13 +423,141 @@
|
|
static int heaps_length = 0;
|
|
static int heaps_used = 0;
|
|
|
|
-#define HEAP_MIN_SLOTS 10000
|
|
-static int heap_slots = HEAP_MIN_SLOTS;
|
|
+static int heap_min_slots = 10000;
|
|
+static int heap_slots = 10000;
|
|
+
|
|
+static int heap_free_min = 4096;
|
|
+
|
|
+static long initial_malloc_limit = GC_MALLOC_LIMIT;
|
|
|
|
-#define FREE_MIN 4096
|
|
+static int verbose_gc_stats = Qfalse;
|
|
+
|
|
+static FILE* gc_data_file = NULL;
|
|
|
|
static RVALUE *himem, *lomem;
|
|
|
|
+static void set_gc_parameters()
|
|
+{
|
|
+ char *gc_stats_ptr, *min_slots_ptr, *free_min_ptr,
|
|
+ *heap_incr_ptr, *malloc_limit_ptr, *gc_heap_file_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_slots = min_slots_i;
|
|
+ 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) {
|
|
+ heaps_increment = heap_incr_i;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ 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;
|
|
+
|
|
+ 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(self, original_str)
|
|
+ VALUE self, original_str;
|
|
+{
|
|
+ if (original_str == Qnil) {
|
|
+ fprintf(gc_data_file, "\n");
|
|
+ }
|
|
+ else {
|
|
+ VALUE str = StringValue(original_str);
|
|
+ char *p = RSTRING(str)->ptr;
|
|
+ fprintf(gc_data_file, "%s\n", p);
|
|
+ }
|
|
+ return original_str;
|
|
+}
|
|
+
|
|
+
|
|
static void
|
|
add_heap()
|
|
{
|
|
@@ -326,7 +568,7 @@
|
|
struct heaps_slot *p;
|
|
int length;
|
|
|
|
- heaps_length += HEAPS_INCREMENT;
|
|
+ heaps_length += heaps_increment;
|
|
length = heaps_length*sizeof(struct heaps_slot);
|
|
RUBY_CRITICAL(
|
|
if (heaps_used > 0) {
|
|
@@ -342,10 +584,10 @@
|
|
for (;;) {
|
|
RUBY_CRITICAL(p = (RVALUE*)malloc(sizeof(RVALUE)*(heap_slots+1)));
|
|
if (p == 0) {
|
|
- if (heap_slots == HEAP_MIN_SLOTS) {
|
|
+ if (heap_slots == heap_min_slots) {
|
|
rb_memerror();
|
|
}
|
|
- heap_slots = HEAP_MIN_SLOTS;
|
|
+ heap_slots = heap_min_slots;
|
|
continue;
|
|
}
|
|
heaps[heaps_used].membase = p;
|
|
@@ -362,7 +604,7 @@
|
|
if (himem < pend) himem = pend;
|
|
heaps_used++;
|
|
heap_slots *= 1.8;
|
|
- if (heap_slots <= 0) heap_slots = HEAP_MIN_SLOTS;
|
|
+ if (heap_slots <= 0) heap_slots = heap_min_slots;
|
|
|
|
while (p < pend) {
|
|
p->as.free.flags = 0;
|
|
@@ -1015,6 +1257,39 @@
|
|
}
|
|
}
|
|
|
|
+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_VARMAP : return "VARMAP";
|
|
+ case T_SCOPE : return "SCOPE";
|
|
+ case T_NODE : return "NODE";
|
|
+ default: return "____";
|
|
+ }
|
|
+}
|
|
+
|
|
static void
|
|
free_unused_heaps()
|
|
{
|
|
@@ -1045,12 +1320,21 @@
|
|
unsigned long live = 0;
|
|
unsigned long free_min = 0;
|
|
|
|
+ unsigned long really_freed = 0;
|
|
+ int free_counts[256];
|
|
+ int live_counts[256];
|
|
+ int do_gc_stats = gc_statistics & verbose_gc_stats;
|
|
+
|
|
for (i = 0; i < heaps_used; i++) {
|
|
free_min += heaps[i].limit;
|
|
}
|
|
free_min = free_min * 0.2;
|
|
- if (free_min < FREE_MIN)
|
|
- free_min = FREE_MIN;
|
|
+ if (free_min < heap_free_min)
|
|
+ free_min = heap_free_min;
|
|
+
|
|
+ if (do_gc_stats) {
|
|
+ for (i = 0 ; i< 256; i++) { free_counts[i] = live_counts[i] = 0; }
|
|
+ }
|
|
|
|
if (ruby_in_compile && ruby_parser_stack_on_heap()) {
|
|
/* should not reclaim nodes during compilation
|
|
@@ -1083,6 +1367,9 @@
|
|
if (!(p->as.basic.flags & FL_MARK)) {
|
|
if (p->as.basic.flags) {
|
|
obj_free((VALUE)p);
|
|
+ if (do_gc_stats) {
|
|
+ really_freed++;
|
|
+ }
|
|
}
|
|
if (need_call_final && FL_TEST(p, FL_FINALIZE)) {
|
|
p->as.free.flags = FL_MARK; /* remain marked */
|
|
@@ -1090,6 +1377,12 @@
|
|
final_list = p;
|
|
}
|
|
else {
|
|
+ if (do_gc_stats) {
|
|
+ int obt = p->as.basic.flags & T_MASK;
|
|
+ if (obt) {
|
|
+ free_counts[obt]++;
|
|
+ }
|
|
+ }
|
|
p->as.free.flags = 0;
|
|
p->as.free.next = freelist;
|
|
freelist = p;
|
|
@@ -1103,6 +1396,9 @@
|
|
else {
|
|
RBASIC(p)->flags &= ~FL_MARK;
|
|
live++;
|
|
+ if (do_gc_stats) {
|
|
+ live_counts[RANY((VALUE)p)->as.basic.flags & T_MASK]++;
|
|
+ }
|
|
}
|
|
p++;
|
|
}
|
|
@@ -1121,7 +1417,7 @@
|
|
}
|
|
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;
|
|
+ if (malloc_limit < initial_malloc_limit) malloc_limit = initial_malloc_limit;
|
|
}
|
|
malloc_increase = 0;
|
|
if (freed < free_min) {
|
|
@@ -1129,6 +1425,20 @@
|
|
}
|
|
during_gc = 0;
|
|
|
|
+ if (do_gc_stats) {
|
|
+ fprintf(gc_data_file, "objects processed: %.7d\n", live+freed);
|
|
+ fprintf(gc_data_file, "live objects : %.7d\n", live);
|
|
+ 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) {
|
|
deferred_final_list = final_list;
|
|
@@ -1323,6 +1633,7 @@
|
|
struct gc_list *list;
|
|
struct FRAME * volatile frame; /* gcc 2.7.2.3 -O2 bug?? */
|
|
jmp_buf save_regs_gc_mark;
|
|
+ struct timeval gctv1, gctv2;
|
|
SET_STACK_END;
|
|
|
|
#ifdef HAVE_NATIVETHREAD
|
|
@@ -1339,6 +1650,14 @@
|
|
if (during_gc) return;
|
|
during_gc++;
|
|
|
|
+ if (gc_statistics) {
|
|
+ gc_collections++;
|
|
+ gettimeofday(&gctv1, NULL);
|
|
+ if (verbose_gc_stats) {
|
|
+ fprintf(gc_data_file, "Garbage collection started\n");
|
|
+ }
|
|
+ }
|
|
+
|
|
init_mark_stack();
|
|
|
|
gc_mark((VALUE)ruby_current_node, 0);
|
|
@@ -1414,6 +1733,17 @@
|
|
} while (!MARK_STACK_EMPTY);
|
|
|
|
gc_sweep();
|
|
+
|
|
+ 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);
|
|
+ }
|
|
+ }
|
|
}
|
|
|
|
void
|
|
@@ -1582,6 +1912,7 @@
|
|
if (!rb_gc_stack_start) {
|
|
Init_stack(0);
|
|
}
|
|
+ set_gc_parameters();
|
|
add_heap();
|
|
}
|
|
|
|
@@ -2051,6 +2382,14 @@
|
|
rb_define_singleton_method(rb_mGC, "disable", rb_gc_disable, 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, "clear_stats", rb_gc_clear_stats, 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);
|