From: Brent Roman Date: 2007-11-13T05:06:37+09:00 Subject: [PATCH] Limit Ruby 1.8.6 process size This is a multi-part message in MIME format. --------------080708060903040407030903 Content-Type: text/plain; charset=ISO-8859-1 Content-Transfer-Encoding: 7bit Over the past few weeks I've seen quite a few posts to this list about Ruby 1.8.6 "memory leaks". I may have stumbed onto a source of these "leaks" while trying to track down another related problem. This code runs at the end of every GC sweep: (in gc.c) 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; } Does anyone know the intended purpose of this code? It is new in version 1.8. It apears to be trying to dynamically adjust the malloc_limit upwards in response to unsuccessful GC passes. I don't see any code that can pull the malloc_limit back down. So, I believe this causes the malloc_limit to slowly increase without bound. I think it will lessen the frequency of GC's, but any speed improvement gained will be lost when the OS's Virtual Memory system starts swapping as a result of ever growing process sizes. The attached patch does two things: 1) It eliminates the dynamic adjustment of malloc_limit 2) It addes methods in the GC module to control the malloc_limit from within the Ruby application so they can adjust it. - brent --------------080708060903040407030903 Content-Type: text/x-patch; name="gc.c.patch" Content-Transfer-Encoding: 7bit Content-Disposition: inline; filename="gc.c.patch" --- gc.c 2007-11-09 17:08:15.000000000 -0800 +++ limited/gc.c 2007-11-12 11:37:00.000000000 -0800 @@ -74,6 +74,48 @@ static VALUE nomem_error; static void garbage_collect(); + +/* + * call-seq: + * GC.limit => increase limit in bytes + * GC.limit= => new increase limit in bytes + * + * Get or set the # of bytes that may be allocated before triggering + * a mark and sweep by the garbarge collector to reclaim unused storage. + * Attempts to set the GC.limit= less than 0 will be ignored. + * + * GC.limit=5000000 #=> 5000000 + * GC.limit #=> 5000000 + * GC.limit=-50 #=> 5000000 + * GC.limit=0 #=> 0 + * + */ +static VALUE gc_getlimit(VALUE mod) +{ + return ULONG2NUM(malloc_limit); +} + +static VALUE gc_setlimit(VALUE mod, VALUE newLimit) +{ + long limit = NUM2LONG(newLimit); + if (limit < 0) return gc_getlimit(mod); + malloc_limit = limit; + return newLimit; +} + + +/* + * call-seq: + * GC.increase + * + * Get # of bytes that have been allocated since the last mark & sweep + * + */ +static VALUE gc_increase(VALUE mod) +{ + return ULONG2NUM(malloc_increase); +} + void rb_memerror() { @@ -1042,7 +1084,6 @@ RVALUE *p, *pend, *final_list; int freed = 0; int i; - unsigned long live = 0; unsigned long free_min = 0; for (i = 0; i < heaps_used; i++) { @@ -1102,7 +1143,6 @@ } else { RBASIC(p)->flags &= ~FL_MARK; - live++; } p++; } @@ -1119,10 +1159,6 @@ freed += n; } } - 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_increase = 0; if (freed < free_min) { add_heap(); @@ -2062,6 +2098,9 @@ rb_define_singleton_method(rb_mGC, "start", rb_gc_start, 0); rb_define_singleton_method(rb_mGC, "enable", rb_gc_enable, 0); rb_define_singleton_method(rb_mGC, "disable", rb_gc_disable, 0); + rb_define_singleton_method(rb_mGC, "limit", gc_getlimit, 0); + rb_define_singleton_method(rb_mGC, "limit=", gc_setlimit, 1); + rb_define_singleton_method(rb_mGC, "increase", gc_increase, 0); rb_define_method(rb_mGC, "garbage_collect", rb_gc_start, 0); rb_mObSpace = rb_define_module("ObjectSpace"); --------------080708060903040407030903--