Skip to content
Browse files

Apply the copy-on-write patches.

  • Loading branch information...
1 parent 5bafb12 commit 6378e5212fe5ae961f3856f8576928dfd2554614 @FooBarWidget FooBarWidget committed Sep 2, 2009
Showing with 1,031 additions and 26 deletions.
  1. +5 −1 common.mk
  2. +1 −1 eval.c
  3. +83 −0 fastmarktable.c
  4. +414 −23 gc.c
  5. +205 −0 marktable.c
  6. +17 −0 marktable.h
  7. +247 −0 pointerset.c
  8. +56 −0 pointerset.h
  9. +3 −1 ruby.h
View
6 common.mk
@@ -42,6 +42,7 @@ OBJS = array.$(OBJEXT) \
object.$(OBJEXT) \
pack.$(OBJEXT) \
parse.$(OBJEXT) \
+ pointerset.$(OBJEXT) \
process.$(OBJEXT) \
prec.$(OBJEXT) \
random.$(OBJEXT) \
@@ -396,7 +397,9 @@ file.$(OBJEXT): {$(VPATH)}file.c {$(VPATH)}ruby.h config.h \
gc.$(OBJEXT): {$(VPATH)}gc.c {$(VPATH)}ruby.h config.h \
{$(VPATH)}defines.h {$(VPATH)}intern.h {$(VPATH)}missing.h \
{$(VPATH)}rubysig.h {$(VPATH)}st.h {$(VPATH)}node.h \
- {$(VPATH)}env.h {$(VPATH)}re.h {$(VPATH)}regex.h
+ {$(VPATH)}env.h {$(VPATH)}re.h {$(VPATH)}regex.h \
+ {$(VPATH)}pointerset.h {$(VPATH)}marktable.h \
+ {$(VPATH)}marktable.c {$(VPATH)}fastmarktable.c
hash.$(OBJEXT): {$(VPATH)}hash.c {$(VPATH)}ruby.h config.h \
{$(VPATH)}defines.h {$(VPATH)}intern.h {$(VPATH)}missing.h \
{$(VPATH)}st.h {$(VPATH)}util.h {$(VPATH)}rubysig.h
@@ -425,6 +428,7 @@ parse.$(OBJEXT): {$(VPATH)}parse.c {$(VPATH)}ruby.h config.h \
{$(VPATH)}defines.h {$(VPATH)}intern.h {$(VPATH)}missing.h \
{$(VPATH)}env.h {$(VPATH)}node.h {$(VPATH)}st.h \
{$(VPATH)}regex.h {$(VPATH)}util.h {$(VPATH)}lex.c
+pointerset.$(OBJEXT): {$(VPATH)}pointerset.c {$(VPATH)}pointerset.h
prec.$(OBJEXT): {$(VPATH)}prec.c {$(VPATH)}ruby.h config.h \
{$(VPATH)}defines.h {$(VPATH)}intern.h {$(VPATH)}missing.h
process.$(OBJEXT): {$(VPATH)}process.c {$(VPATH)}ruby.h config.h \
View
2 eval.c
@@ -10787,7 +10787,7 @@ rb_gc_abort_threads()
return;
FOREACH_THREAD_FROM(main_thread, th) {
- if (FL_TEST(th->thread, FL_MARK)) continue;
+ if (rb_gc_is_thread_marked(th->thread)) continue;
if (th->status == THREAD_STOPPED) {
th->status = THREAD_TO_KILL;
rb_gc_mark(th->thread);
View
83 fastmarktable.c
@@ -0,0 +1,83 @@
+/**
+ * A mark table, used during a mark-and-sweep garbage collection cycle.
+ *
+ * This implementation is faster than marktable.c, but is *not*
+ * copy-on-write friendly. It stores mark information directly inside objects.
+ */
+#ifndef _FAST_MARK_TABLE_C_
+#define _FAST_MARK_TABLE_C_
+
+static void
+rb_fast_mark_table_init() {
+}
+
+static void
+rb_fast_mark_table_prepare() {
+}
+
+static void
+rb_fast_mark_table_finalize() {
+}
+
+static inline void
+rb_fast_mark_table_add(RVALUE *object) {
+ object->as.basic.flags |= FL_MARK;
+}
+
+static inline void
+rb_fast_mark_table_heap_add(struct heaps_slot *hs, RVALUE *object) {
+ object->as.basic.flags |= FL_MARK;
+}
+
+static inline int
+rb_fast_mark_table_contains(RVALUE *object) {
+ return object->as.basic.flags & FL_MARK;
+}
+
+static inline int
+rb_fast_mark_table_heap_contains(struct heaps_slot *hs, RVALUE *object) {
+ return object->as.basic.flags & FL_MARK;
+}
+
+static inline void
+rb_fast_mark_table_remove(RVALUE *object) {
+ object->as.basic.flags &= ~FL_MARK;
+}
+
+static inline void
+rb_fast_mark_table_heap_remove(struct heaps_slot *hs, RVALUE *object) {
+ object->as.basic.flags &= ~FL_MARK;
+}
+
+static inline void
+rb_fast_mark_table_add_filename(char *filename) {
+ filename[-1] = 1;
+}
+
+static inline int
+rb_fast_mark_table_contains_filename(const char *filename) {
+ return filename[-1];
+}
+
+static inline void
+rb_fast_mark_table_remove_filename(char *filename) {
+ filename[-1] = 0;
+}
+
+static void
+rb_use_fast_mark_table() {
+ rb_mark_table_init = rb_fast_mark_table_init;
+ rb_mark_table_prepare = rb_fast_mark_table_prepare;
+ rb_mark_table_finalize = rb_fast_mark_table_finalize;
+ rb_mark_table_add = rb_fast_mark_table_add;
+ rb_mark_table_heap_add = rb_fast_mark_table_heap_add;
+ rb_mark_table_contains = rb_fast_mark_table_contains;
+ rb_mark_table_heap_contains = rb_fast_mark_table_heap_contains;
+ rb_mark_table_remove = rb_fast_mark_table_remove;
+ rb_mark_table_heap_remove = rb_fast_mark_table_heap_remove;
+ rb_mark_table_add_filename = rb_fast_mark_table_add_filename;
+ rb_mark_table_contains_filename = rb_fast_mark_table_contains_filename;
+ rb_mark_table_remove_filename = rb_fast_mark_table_remove_filename;
+}
+
+#endif /* _FAST_MARK_TABLE_C_ */
View
437 gc.c
@@ -20,8 +20,15 @@
#include "re.h"
#include <stdio.h>
#include <setjmp.h>
+#include <math.h>
#include <sys/types.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <stdarg.h>
+
#ifdef HAVE_SYS_TIME_H
#include <sys/time.h>
#endif
@@ -43,6 +50,8 @@ void rb_io_fptr_finalize _((struct rb_io_t*));
int _setjmp(), _longjmp();
#endif
+#define T_DEFERRED 0x3a
+
#ifndef GC_MALLOC_LIMIT
#if defined(MSDOS) || defined(__human68k__)
#define GC_MALLOC_LIMIT 200000
@@ -292,6 +301,152 @@ static int need_call_final = 0;
static st_table *finalizer_table = 0;
+/************************************************************
+ * Heap and copy-on-write debugging support functions
+ ************************************************************/
+
+/* Compound structure, containing debugging options. */
+static struct {
+ FILE *terminal;
+
+ /* Whether to allocate Ruby heaps by mmapping a file. This makes it easier to see how many
+ * bytes in heaps have been made dirty, using memory analysis tools.
+ */
+ int alloc_heap_with_file;
+
+ /* Whether to ask the user to press Enter, before garbage collection starts.
+ * Can be used to check how many pages are made dirty by the garbage collector.
+ */
+ int prompt_before_gc;
+
+ /* Whether to ask the user to press Enter before the sweep phase of the garbage
+ * collector starts. */
+ int prompt_before_sweep;
+
+ /* Whether to ask the user to press Enter after the sweep phase of the garbage
+ * collector starts. */
+ int prompt_after_sweep;
+
+ int print_sweeped_objects;
+} debug_options;
+
+#define OPTION_ENABLED(name) (getenv((name)) && *getenv((name)) && *getenv((name)) != '0')
+
+static VALUE
+rb_gc_init_debugging(VALUE self)
+{
+ if (debug_options.terminal != NULL) {
+ fclose(debug_options.terminal);
+ debug_options.terminal = NULL;
+ }
+ if (getenv("RD_TERMINAL")) {
+ debug_options.terminal = fopen(getenv("RD_TERMINAL"), "a+");
+ if (debug_options.terminal == NULL) {
+ int e = errno;
+ fprintf(stderr, "Cannot open %s: %s (%d)\n", getenv("RD_TERMINAL"), strerror(e), e);
+ fflush(stderr);
+ }
+ }
+ debug_options.alloc_heap_with_file = OPTION_ENABLED("RD_ALLOC_HEAP_WITH_FILE");
+ debug_options.prompt_before_gc = OPTION_ENABLED("RD_PROMPT_BEFORE_GC");
+ debug_options.prompt_before_sweep = OPTION_ENABLED("RD_PROMPT_BEFORE_SWEEP");
+ debug_options.prompt_after_sweep = OPTION_ENABLED("RD_PROMPT_AFTER_SWEEP");
+ debug_options.print_sweeped_objects = OPTION_ENABLED("RD_PRINT_SWEEPED_OBJECTS");
+ return Qnil;
+}
+
+static void
+debug_print(const char *message, ...)
+{
+ va_list ap;
+
+ va_start(ap, message);
+ if (debug_options.terminal != NULL) {
+ vfprintf(debug_options.terminal, message, ap);
+ fflush(debug_options.terminal);
+ } else {
+ vfprintf(stderr, message, ap);
+ fflush(stderr);
+ }
+ va_end(ap);
+}
+
+#define debug_prompt(prompt) \
+ do { \
+ if (debug_options.terminal != NULL) { \
+ fprintf(debug_options.terminal, prompt); \
+ fflush(debug_options.terminal); \
+ getc(debug_options.terminal); \
+ } else { \
+ fprintf(stderr, prompt); \
+ fflush(stderr); \
+ getchar(); \
+ } \
+ } while (0)
+
+
+/************************************
+ * Heap (de)allocation functions
+ ************************************/
+
+typedef struct {
+ int fd;
+ size_t size;
+} FileHeapAllocatorMetaData;
+
+static void *
+alloc_ruby_heap_with_file(size_t size)
+{
+ FileHeapAllocatorMetaData meta;
+ meta.fd = open("/dev/zero", O_RDONLY);
+ meta.size = size;
+ if (meta.fd == -1) {
+ return NULL;
+ } else {
+ void *memory = mmap(NULL, size + sizeof(meta), PROT_READ | PROT_WRITE,
+ MAP_PRIVATE, meta.fd, 0);
+ if (memory == NULL) {
+ return NULL;
+ } else {
+ memcpy(memory, &meta, sizeof(meta));
+ return memory + sizeof(meta);
+ }
+ }
+}
+
+static void *
+alloc_ruby_heap(size_t size)
+{
+ if (debug_options.alloc_heap_with_file) {
+ return alloc_ruby_heap_with_file(size);
+ } else {
+ return malloc(size);
+ }
+}
+
+static void
+free_ruby_heap_with_file(void *heap)
+{
+ FileHeapAllocatorMetaData *meta = (FileHeapAllocatorMetaData *)
+ (heap - sizeof(FileHeapAllocatorMetaData));
+ close(meta->fd);
+ munmap(heap, meta->size + sizeof(FileHeapAllocatorMetaData));
+}
+
+static void
+free_ruby_heap(void *heap)
+{
+ if (debug_options.alloc_heap_with_file) {
+ free_ruby_heap_with_file(heap);
+ } else {
+ free(heap);
+ }
+}
+
+
+/*******************************************************************/
+
+
/*
* call-seq:
* GC.enable => true or false
@@ -431,6 +586,9 @@ static struct heaps_slot {
void *membase;
RVALUE *slot;
int limit;
+ RVALUE *slotlimit;
+ int *marks;
+ int marks_size;
} *heaps;
static int heaps_length = 0;
static int heaps_used = 0;
@@ -442,6 +600,12 @@ static int heap_slots = HEAP_MIN_SLOTS;
static RVALUE *himem, *lomem;
+#include "marktable.h"
+#include "marktable.c"
+#include "fastmarktable.c"
+
+static int gc_cycles = 0;
+
static void
add_heap()
{
@@ -466,7 +630,7 @@ add_heap()
}
for (;;) {
- RUBY_CRITICAL(p = (RVALUE*)malloc(sizeof(RVALUE)*(heap_slots+1)));
+ RUBY_CRITICAL(p = (RVALUE*)alloc_ruby_heap(sizeof(RVALUE)*(heap_slots+1)));
if (p == 0) {
if (heap_slots == HEAP_MIN_SLOTS) {
rb_memerror();
@@ -481,6 +645,9 @@ add_heap()
p = (RVALUE*)((VALUE)p + sizeof(RVALUE) - ((VALUE)p % sizeof(RVALUE)));
heaps[heaps_used].slot = p;
heaps[heaps_used].limit = heap_slots;
+ heaps[heaps_used].slotlimit = p + heap_slots;
+ heaps[heaps_used].marks_size = (int) (ceil(heap_slots / (sizeof(int) * 8.0)));
+ heaps[heaps_used].marks = (int *) calloc(heaps[heaps_used].marks_size, sizeof(int));
break;
}
pend = p + heap_slots;
@@ -702,19 +869,20 @@ mark_source_filename(f)
char *f;
{
if (f) {
- f[-1] = 1;
+ rb_mark_table_add_filename(f);
}
}
static int
sweep_source_filename(key, value)
char *key, *value;
{
- if (*value) {
- *value = 0;
+ if (rb_mark_table_contains_filename(value + 1)) {
+ rb_mark_table_remove_filename(value + 1);
return ST_CONTINUE;
}
else {
+ rb_mark_table_remove_filename(value + 1);
free(value);
return ST_DELETE;
}
@@ -733,8 +901,8 @@ gc_mark_all()
while (--heap >= heaps) {
p = heap->slot; pend = p + heap->limit;
while (p < pend) {
- if ((p->as.basic.flags & FL_MARK) &&
- (p->as.basic.flags != FL_MARK)) {
+ if (rb_mark_table_heap_contains(heap, p) &&
+ BUILTIN_TYPE(p) != T_DEFERRED) {
gc_mark_children((VALUE)p);
}
p++;
@@ -870,8 +1038,8 @@ rb_gc_mark(ptr)
if (rb_special_const_p(ptr)) return; /* special const not marked */
if (obj->as.basic.flags == 0) return; /* free cell */
- if (obj->as.basic.flags & FL_MARK) return; /* already marked */
- obj->as.basic.flags |= FL_MARK;
+ if (rb_mark_table_contains(obj)) return; /* already marked */
+ rb_mark_table_add(obj);
if (__stack_past(gc_stack_limit, STACK_END))
push_mark_stack(ptr);
@@ -892,8 +1060,8 @@ gc_mark_children(ptr)
obj = RANY(ptr);
if (rb_special_const_p(ptr)) return; /* special const not marked */
if (obj->as.basic.flags == 0) return; /* free cell */
- if (obj->as.basic.flags & FL_MARK) return; /* already marked */
- obj->as.basic.flags |= FL_MARK;
+ if (rb_mark_table_contains(obj)) return; /* already marked */
+ rb_mark_table_add(obj);
marking:
if (FL_TEST(obj, FL_EXIVAR)) {
@@ -1139,8 +1307,13 @@ static inline void
add_freelist(p)
RVALUE *p;
{
- p->as.free.flags = 0;
- p->as.free.next = freelist;
+ /* Do not touch the fields if they don't have to be modified.
+ * This is in order to preserve copy-on-write semantics.
+ */
+ if (p->as.free.flags != 0)
+ p->as.free.flags = 0;
+ if (p->as.free.next != freelist)
+ p->as.free.next = freelist;
freelist = p;
}
@@ -1151,7 +1324,12 @@ finalize_list(p)
while (p) {
RVALUE *tmp = p->as.free.next;
run_final((VALUE)p);
- if (!FL_TEST(p, FL_SINGLETON)) { /* not freeing page */
+ /* Don't free objects that are singletons, or objects that are already freed.
+ * The latter is to prevent the unnecessary marking of memory pages as dirty,
+ * which can destroy copy-on-write semantics.
+ */
+ if (!FL_TEST(p, FL_SINGLETON)) {
+ rb_mark_table_remove(p);
add_freelist(p);
}
p = tmp;
@@ -1165,7 +1343,8 @@ free_unused_heaps()
for (i = j = 1; j < heaps_used; i++) {
if (heaps[i].limit == 0) {
- free(heaps[i].membase);
+ free_ruby_heap(heaps[i].membase);
+ free(heaps[i].marks);
heaps_used--;
}
else {
@@ -1177,8 +1356,6 @@ free_unused_heaps()
}
}
-#define T_DEFERRED 0x3a
-
void rb_gc_abort_threads(void);
static void
@@ -1188,6 +1365,7 @@ gc_sweep()
int freed = 0;
int i;
long free_min = 0;
+ struct heaps_slot *heap;
for (i = 0; i < heaps_used; i++) {
free_min += heaps[i].limit;
@@ -1200,9 +1378,10 @@ gc_sweep()
/* should not reclaim nodes during compilation
if yacc's semantic stack is not allocated on machine stack */
for (i = 0; i < heaps_used; i++) {
- p = heaps[i].slot; pend = p + heaps[i].limit;
+ heap = &heaps[i];
+ p = heap->slot; pend = p + heap->limit;
while (p < pend) {
- if (!(p->as.basic.flags&FL_MARK) && BUILTIN_TYPE(p) == T_NODE)
+ if (!rb_mark_table_heap_contains(heap, p) && BUILTIN_TYPE(p) == T_NODE)
gc_mark((VALUE)p);
p++;
}
@@ -1223,17 +1402,19 @@ gc_sweep()
RVALUE *final = final_list;
int deferred;
- p = heaps[i].slot; pend = p + heaps[i].limit;
+ heap = &heaps[i];
+ p = heap->slot; pend = p + heap->limit;
while (p < pend) {
- if (!(p->as.basic.flags & FL_MARK)) {
+ if (!rb_mark_table_heap_contains(heap, p)) {
if (p->as.basic.flags &&
((deferred = obj_free((VALUE)p)) ||
((FL_TEST(p, FL_FINALIZE)) && need_call_final))) {
+ /* This object has a finalizer, so don't free it right now, but do it later. */
if (!deferred) {
p->as.free.flags = T_DEFERRED;
RDATA(p)->dfree = 0;
}
- p->as.free.flags |= FL_MARK;
+ rb_mark_table_heap_add(heap, p); /* remain marked */
p->as.free.next = final_list;
final_list = p;
}
@@ -1247,14 +1428,15 @@ gc_sweep()
/* do nothing remain marked */
}
else {
- RBASIC(p)->flags &= ~FL_MARK;
+ rb_mark_table_heap_remove(heap, p);
}
p++;
}
if (n == heaps[i].limit && freed > free_min) {
RVALUE *pp;
heaps[i].limit = 0;
+ heaps[i].slotlimit = heaps[i].slot;
for (pp = final_list; pp != final; pp = pp->as.free.next) {
pp->as.free.flags |= FL_SINGLETON; /* freeing page mark */
}
@@ -1283,6 +1465,7 @@ void
rb_gc_force_recycle(p)
VALUE p;
{
+ rb_mark_table_remove((RVALUE *) p);
add_freelist(p);
}
@@ -1489,6 +1672,7 @@ garbage_collect_0(VALUE *top_frame)
during_gc++;
gc_stack_limit = __stack_grow(STACK_END, GC_LEVEL_MAX);
+ rb_mark_table_prepare();
init_mark_stack();
gc_mark((VALUE)ruby_current_node);
@@ -1559,6 +1743,7 @@ garbage_collect_0(VALUE *top_frame)
rb_gc_abort_threads();
} while (!MARK_STACK_EMPTY);
gc_sweep();
+ rb_mark_table_finalize();
}
static void
@@ -1617,6 +1802,17 @@ rb_gc_start()
}
+int
+rb_gc_is_thread_marked(the_thread)
+ VALUE the_thread;
+{
+ if (FL_ABLE(the_thread)) {
+ return rb_mark_table_contains((RVALUE *) the_thread);
+ } else {
+ return 0;
+ }
+}
+
void
ruby_set_stack_size(size)
size_t size;
@@ -1770,6 +1966,9 @@ void ruby_init_stack(VALUE *addr
void
Init_heap()
{
+ rb_gc_init_debugging((VALUE) NULL);
+ rb_use_fast_mark_table();
+ rb_mark_table_init();
if (!rb_gc_stack_start) {
Init_stack(0);
}
@@ -2073,6 +2272,7 @@ void
rb_gc_call_finalizer_at_exit()
{
RVALUE *p, *pend;
+ struct heaps_slot *heap;
int i;
/* run finalizers */
@@ -2088,12 +2288,14 @@ rb_gc_call_finalizer_at_exit()
}
/* run data object's finalizers */
for (i = 0; i < heaps_used; i++) {
- p = heaps[i].slot; pend = p + heaps[i].limit;
+ heap = &heaps[i];
+ p = heap->slot; pend = p + heap->limit;
while (p < pend) {
if (BUILTIN_TYPE(p) == T_DATA &&
DATA_PTR(p) && RANY(p)->as.data.dfree &&
RANY(p)->as.basic.klass != rb_cThread) {
p->as.free.flags = 0;
+ rb_mark_table_heap_remove(heap, p);
if ((long)RANY(p)->as.data.dfree == -1) {
RUBY_CRITICAL(free(DATA_PTR(p)));
}
@@ -2103,6 +2305,7 @@ rb_gc_call_finalizer_at_exit()
}
else if (BUILTIN_TYPE(p) == T_FILE) {
p->as.free.flags = 0;
+ rb_mark_table_heap_remove(heap, p);
rb_io_fptr_finalize(RANY(p)->as.file.fptr);
}
p++;
@@ -2222,6 +2425,189 @@ rb_obj_id(VALUE obj)
return (VALUE)((long)obj|FIXNUM_FLAG);
}
+static VALUE
+os_statistics()
+{
+ int i;
+ int n = 0;
+ unsigned int objects = 0;
+ unsigned int total_objects_size = 0;
+ unsigned int total_heap_size = 0;
+ unsigned int total_heap_slots = 0;
+ unsigned int ast_nodes = 0;
+ char message[1024];
+ unsigned int total_leading_free_slots = 0;
+ unsigned int total_trailing_free_slots = 0;
+ const unsigned int group_size = 16;
+ unsigned int contiguous_free_groups = 0;
+ unsigned int terminal_objects = 0; /* Number of objects that cannot possibly refer to other objects. */
+
+ for (i = 0; i < heaps_used; i++) {
+ RVALUE *p, *pend;
+ unsigned int leading_free_slots = 0;
+ unsigned int trailing_free_slots = 0;
+ unsigned int slot_index = 0;
+ unsigned int free_slots_in_current_group = 0;
+ enum { BEGIN, MIDDLE, END } mode = BEGIN;
+
+ p = heaps[i].slot;
+ pend = p + heaps[i].limit;
+ for (;p < pend; p++, slot_index++) {
+ switch (mode) {
+ case BEGIN:
+ if (p->as.basic.flags) {
+ mode = MIDDLE;
+ } else {
+ leading_free_slots++;
+ }
+ break;
+ case MIDDLE:
+ if (p->as.basic.flags == 0) {
+ mode = END;
+ trailing_free_slots++;
+ }
+ break;
+ case END:
+ if (p->as.basic.flags == 0) {
+ trailing_free_slots++;
+ } else {
+ trailing_free_slots = 0;
+ mode = MIDDLE;
+ }
+ break;
+ };
+
+ if (slot_index % group_size == 0) {
+ if (free_slots_in_current_group == group_size) {
+ contiguous_free_groups++;
+ }
+ free_slots_in_current_group = 0;
+ }
+ if (p->as.basic.flags == 0) {
+ free_slots_in_current_group++;
+ }
+
+ if (p->as.basic.flags) {
+ int isAST = 0;
+ switch (TYPE(p)) {
+ case T_ICLASS:
+ case T_VARMAP:
+ case T_SCOPE:
+ case T_NODE:
+ isAST = 1;
+ break;
+ case T_CLASS:
+ if (FL_TEST(p, FL_SINGLETON)) {
+ isAST = 1;
+ break;
+ }
+ case T_FILE:
+ case T_REGEXP:
+ case T_FLOAT:
+ case T_BIGNUM:
+ case T_BLKTAG:
+ terminal_objects++;
+ break;
+ default:
+ break;
+ }
+ objects++;
+ if (isAST) {
+ ast_nodes++;
+ }
+ }
+ }
+ total_heap_size += (void *) pend - heaps[i].membase;
+ total_leading_free_slots += leading_free_slots;
+ total_trailing_free_slots += trailing_free_slots;
+ }
+
+ total_objects_size = objects * sizeof(RVALUE);
+ total_heap_slots = total_heap_size / sizeof(RVALUE);
+ snprintf(message, sizeof(message),
+ "Number of objects : %d (%d AST nodes, %.2f%%)\n"
+ "Heap slot size : %d\n"
+ "GC cycles so far : %d\n"
+ "Number of heaps : %d\n"
+ "Total size of objects: %.2f KB\n"
+ "Total size of heaps : %.2f KB (%.2f KB = %.2f%% unused)\n"
+ "Leading free slots : %d (%.2f KB = %.2f%%)\n"
+ "Trailing free slots : %d (%.2f KB = %.2f%%)\n"
+ "Number of contiguous groups of %d slots: %d (%.2f%%)\n"
+ "Number of terminal objects: %d (%.2f%%)\n",
+ objects, ast_nodes, ast_nodes * 100 / (double) objects,
+ sizeof(RVALUE),
+ gc_cycles,
+ heaps_used,
+ total_objects_size / 1024.0,
+ total_heap_size / 1024.0,
+ (total_heap_size - total_objects_size) / 1024.0,
+ (total_heap_size - total_objects_size) * 100.0 / total_heap_size,
+ total_leading_free_slots,
+ total_leading_free_slots * sizeof(RVALUE) / 1024.0,
+ total_leading_free_slots * 100.0 / total_heap_slots,
+ total_trailing_free_slots,
+ total_trailing_free_slots * sizeof(RVALUE) / 1024.0,
+ total_trailing_free_slots * 100.0 / total_heap_slots,
+ group_size,
+ contiguous_free_groups,
+ (contiguous_free_groups * group_size * 100.0) / total_heap_slots,
+ terminal_objects,
+ terminal_objects * 100.0 / total_heap_slots
+ );
+ return rb_str_new2(message);
+}
+
+/*
+ * call-seq:
+ * GC.copy_on_write_friendly? => true or false
+ *
+ * Returns whether the garbage collector is copy-on-write friendly.
+ *
+ * This method only has meaning on platforms that support the _fork_ system call.
+ * Please consult the documentation for GC.copy_on_write_friendly= for additional
+ * notes.
+ */
+static VALUE
+rb_gc_copy_on_write_friendly()
+{
+ if (rb_mark_table_init == rb_fast_mark_table_init) {
+ return Qfalse;
+ } else {
+ return Qtrue;
+ }
+}
+
+/*
+ * call-seq:
+ * GC.copy_on_write_friendly = _boolean_
+ *
+ * Tell the garbage collector whether to be copy-on-write friendly.
+ *
+ * Note that this is an implementation detail of the garbage collector. On some Ruby
+ * implementations, the garbage collector may always be copy-on-write friendly. In that
+ * case, this method will do nothing. Furthermore, copy-on-write friendliness has no
+ * meaning on some platforms (such as Microsoft Windows), so setting this flag on those
+ * platform is futile.
+ *
+ * Please keep in mind that this flag is only advisory. Do not rely on it for anything
+ * truly important.
+ *
+ * In the mainline Ruby implementation, the copy-on-write friendly garbage collector is
+ * slightly slower the non-copy-on-write friendly version.
+ */
+static VALUE
+rb_gc_set_copy_on_write_friendly(VALUE self, VALUE val)
+{
+ if (RTEST(val)) {
+ rb_use_bf_mark_table();
+ } else {
+ rb_use_fast_mark_table();
+ }
+ rb_mark_table_init();
+ return Qnil;
+}
+
/*
* The <code>GC</code> module provides an interface to Ruby's mark and
* sweep garbage collection mechanism. Some of the underlying methods
@@ -2249,6 +2635,9 @@ Init_GC()
rb_define_singleton_method(rb_mGC, "stress", gc_stress_get, 0);
rb_define_singleton_method(rb_mGC, "stress=", gc_stress_set, 1);
rb_define_method(rb_mGC, "garbage_collect", rb_gc_start, 0);
+ rb_define_singleton_method(rb_mGC, "initialize_debugging", rb_gc_init_debugging, 0);
+ rb_define_singleton_method(rb_mGC, "copy_on_write_friendly?", rb_gc_copy_on_write_friendly, 0);
+ rb_define_singleton_method(rb_mGC, "copy_on_write_friendly=", rb_gc_set_copy_on_write_friendly, 1);
rb_mObSpace = rb_define_module("ObjectSpace");
rb_define_module_function(rb_mObSpace, "each_object", os_each_obj, -1);
@@ -2263,6 +2652,8 @@ Init_GC()
rb_define_module_function(rb_mObSpace, "_id2ref", id2ref, 1);
+ rb_define_module_function(rb_mObSpace, "statistics", os_statistics, 0);
+
rb_gc_register_address(&rb_mObSpace);
rb_global_variable(&finalizers);
rb_gc_unregister_address(&rb_mObSpace);
View
205 marktable.c
@@ -0,0 +1,205 @@
+/**
+ * A mark table, used during a mark-and-sweep garbage collection cycle.
+ *
+ * This implementation is somewhat slower than fastmarktable.c, but is
+ * copy-on-write friendly. It stores mark information for objects in a bit
+ * field located at the beginning of the heap. Mark information for filenames
+ * are stored in a pointer set.
+ */
+#ifndef _MARK_TABLE_C_
+#define _MARK_TABLE_C_
+
+#include "pointerset.h"
+
+/* A mark table for filenames and objects that are not on the heap. */
+static PointerSet *mark_table = NULL;
+static struct heaps_slot *last_heap = NULL;
+
+
+static inline struct heaps_slot *
+find_heap_slot_for_object(RVALUE *object)
+{
+ register int i;
+
+ /* Look in the cache first. */
+ if (last_heap != NULL && object >= last_heap->slot
+ && object < last_heap->slotlimit) {
+ return last_heap;
+ }
+ for (i = 0; i < heaps_used; i++) {
+ struct heaps_slot *heap = &heaps[i];
+ if (object >= heap->slot
+ && object < heap->slotlimit) {
+ /* Cache this result. According to empirical evidence, the chance is
+ * high that the next lookup will be for the same heap slot.
+ */
+ last_heap = heap;
+ return heap;
+ }
+ }
+ return NULL;
+}
+
+static inline void
+find_position_in_bitfield(struct heaps_slot *hs, RVALUE *object,
+ unsigned int *bitfield_index, unsigned int *bitfield_offset)
+{
+ unsigned int index;
+ index = object - hs->slot;
+
+ /*
+ * We use bit operators to calculate the position in the bit field, whenever possible.
+ * This only works if sizeof(int) is a multiple of 2, but I don't know of any platform
+ * on which that is not true.
+ */
+ if (sizeof(int) == 4 || sizeof(int) == 8 || sizeof(int) == 16) {
+ int int_bits_log; /* Must be equal to the base 2 logarithm of sizeof(int) * 8 */
+
+ switch (sizeof(int)) {
+ case 4:
+ int_bits_log = 5;
+ break;
+ case 8:
+ int_bits_log = 6;
+ break;
+ case 16:
+ int_bits_log = 7;
+ break;
+ default:
+ int_bits_log = 0; /* Shut up compiler warning. */
+ abort();
+ }
+ *bitfield_index = index >> int_bits_log;
+ *bitfield_offset = index & ((sizeof(int) * 8) - 1);
+ } else {
+ *bitfield_index = index / (sizeof(int) * 8);
+ *bitfield_offset = index % (sizeof(int) * 8);
+ }
+}
+
+
+static void
+rb_bf_mark_table_init()
+{
+ if (mark_table == NULL) {
+ mark_table = pointer_set_new();
+ }
+}
+
+static void
+rb_bf_mark_table_prepare()
+{
+ last_heap = NULL;
+}
+
+static void
+rb_bf_mark_table_finalize()
+{
+ /* Do nothing. */
+}
+
+static inline void
+rb_bf_mark_table_add(RVALUE *object)
+{
+ struct heaps_slot *hs;
+ unsigned int bitfield_index, bitfield_offset;
+
+ hs = find_heap_slot_for_object(object);
+ if (hs != NULL) {
+ find_position_in_bitfield(hs, object, &bitfield_index, &bitfield_offset);
+ hs->marks[bitfield_index] |= (1 << bitfield_offset);
+ } else {
+ pointer_set_insert(mark_table, (void *) object);
+ }
+}
+
+static inline void
+rb_bf_mark_table_heap_add(struct heaps_slot *hs, RVALUE *object)
+{
+ unsigned int bitfield_index, bitfield_offset;
+ find_position_in_bitfield(hs, object, &bitfield_index, &bitfield_offset);
+ hs->marks[bitfield_index] |= (1 << bitfield_offset);
+}
+
+static inline int
+rb_bf_mark_table_contains(RVALUE *object)
+{
+ struct heaps_slot *hs;
+ unsigned int bitfield_index, bitfield_offset;
+
+ hs = find_heap_slot_for_object(object);
+ if (hs != NULL) {
+ find_position_in_bitfield(hs, object, &bitfield_index, &bitfield_offset);
+ return hs->marks[bitfield_index] & (1 << bitfield_offset);
+ } else {
+ return pointer_set_contains(mark_table, (void *) object);
+ }
+}
+
+static inline int
+rb_bf_mark_table_heap_contains(struct heaps_slot *hs, RVALUE *object)
+{
+ unsigned int bitfield_index, bitfield_offset;
+ find_position_in_bitfield(hs, object, &bitfield_index, &bitfield_offset);
+ last_heap = hs;
+ return hs->marks[bitfield_index] & (1 << bitfield_offset);
+}
+
+static inline void
+rb_bf_mark_table_remove(RVALUE *object)
+{
+ struct heaps_slot *hs;
+ unsigned int bitfield_index, bitfield_offset;
+
+ hs = find_heap_slot_for_object(object);
+ if (hs != NULL) {
+ find_position_in_bitfield(hs, object, &bitfield_index, &bitfield_offset);
+ hs->marks[bitfield_index] &= ~(1 << bitfield_offset);
+ } else {
+ pointer_set_delete(mark_table, (void *) object);
+ }
+}
+
+static inline void
+rb_bf_mark_table_heap_remove(struct heaps_slot *hs, RVALUE *object)
+{
+ unsigned int bitfield_index, bitfield_offset;
+ find_position_in_bitfield(hs, object, &bitfield_index, &bitfield_offset);
+ hs->marks[bitfield_index] &= ~(1 << bitfield_offset);
+}
+
+static inline void
+rb_bf_mark_table_add_filename(char *filename)
+{
+ pointer_set_insert(mark_table, (void *) filename);
+}
+
+static inline int
+rb_bf_mark_table_contains_filename(const char *filename)
+{
+ return pointer_set_contains(mark_table, (void *) filename);
+}
+
+static inline void
+rb_bf_mark_table_remove_filename(char *filename)
+{
+ pointer_set_delete(mark_table, (void *) filename);
+}
+
+static void
+rb_use_bf_mark_table() {
+ rb_mark_table_init = rb_bf_mark_table_init;
+ rb_mark_table_prepare = rb_bf_mark_table_prepare;
+ rb_mark_table_finalize = rb_bf_mark_table_finalize;
+ rb_mark_table_add = rb_bf_mark_table_add;
+ rb_mark_table_heap_add = rb_bf_mark_table_heap_add;
+ rb_mark_table_contains = rb_bf_mark_table_contains;
+ rb_mark_table_heap_contains = rb_bf_mark_table_heap_contains;
+ rb_mark_table_remove = rb_bf_mark_table_remove;
+ rb_mark_table_heap_remove = rb_bf_mark_table_heap_remove;
+ rb_mark_table_add_filename = rb_bf_mark_table_add_filename;
+ rb_mark_table_contains_filename = rb_bf_mark_table_contains_filename;
+ rb_mark_table_remove_filename = rb_bf_mark_table_remove_filename;
+}
+
+#endif /* _MARK_TABLE_C_ */
View
17 marktable.h
@@ -0,0 +1,17 @@
+#ifndef _MARK_TABLE_H_
+#define _MARK_TABLE_H_
+
+static void (*rb_mark_table_init)();
+static void (*rb_mark_table_prepare)();
+static void (*rb_mark_table_finalize)();
+static void (*rb_mark_table_add)(RVALUE *object);
+static void (*rb_mark_table_heap_add)(struct heaps_slot *hs, RVALUE *object);
+static int (*rb_mark_table_contains)(RVALUE *object);
+static int (*rb_mark_table_heap_contains)(struct heaps_slot *hs, RVALUE *object);
+static void (*rb_mark_table_remove)(RVALUE *object);
+static void (*rb_mark_table_heap_remove)(struct heaps_slot *hs, RVALUE *object);
+static void (*rb_mark_table_add_filename)(char *filename);
+static int (*rb_mark_table_contains_filename)(const char *filename);
+static void (*rb_mark_table_remove_filename)(char *filename);
+
+#endif /* _MARK_TABLE_H_ */
View
247 pointerset.c
@@ -0,0 +1,247 @@
+#include "config.h"
+#include "defines.h"
+#ifdef HAVE_STDLIB_H
+ #include <stdlib.h>
+#endif
+#include <string.h>
+#include "pointerset.h"
+
+
+typedef struct _PointerSetEntry PointerSetEntry;
+
+struct _PointerSet {
+ unsigned int num_bins;
+ unsigned int num_entries;
+ PointerSetEntry **bins;
+};
+
+struct _PointerSetEntry {
+ PointerSetElement element;
+ PointerSetEntry *next;
+};
+
+/* Table of prime numbers 2^n+a, 2<=n<=30. */
+static const long primes[] = {
+ 8 + 3,
+ 16 + 3,
+ 32 + 5,
+ 64 + 3,
+ 128 + 3,
+ 256 + 27,
+ 512 + 9,
+ 1024 + 9,
+ 2048 + 5,
+ 4096 + 3,
+ 8192 + 27,
+ 16384 + 43,
+ 32768 + 3,
+ 65536 + 45,
+ 131072 + 29,
+ 262144 + 3,
+ 524288 + 21,
+ 1048576 + 7,
+ 2097152 + 17,
+ 4194304 + 15,
+ 8388608 + 9,
+ 16777216 + 43,
+ 33554432 + 35,
+ 67108864 + 15,
+ 134217728 + 29,
+ 268435456 + 3,
+ 536870912 + 11,
+ 1073741824 + 85,
+ 0
+};
+
+
+/* The percentage of nonempty buckets, before increasing the number of bins. 1.0 == 100%
+ * A value larger than 1.0 means that it's very likely that some buckets contain more than
+ * 1 entry.
+ */
+#define MAX_LOAD_FACTOR 2.0
+/* The default for the number of bins allocated initially. Must be a prime number. */
+#define DEFAULT_TABLE_SIZE 11
+/* MINSIZE is the minimum size of a dictionary. */
+#define MINSIZE 8
+
+#if SIZEOF_LONG == SIZEOF_VOIDP
+ typedef unsigned long PointerInt;
+#elif SIZEOF_LONG_LONG == SIZEOF_VOIDP
+ typedef unsigned LONG_LONG PointerInt;
+#else
+ #error ---->> pointerset.c requires sizeof(void*) == sizeof(long) to be compiled. <<---
+ -
+#endif
+
+#define alloc(type) (type*)malloc((unsigned)sizeof(type))
+#define Calloc(n,s) (char*)calloc((n),(s))
+
+#define HASH(element, num_bins) ((PointerInt) element) % num_bins
+#define FIND_ENTRY(set, entry, element) \
+ do { \
+ unsigned int bin_pos = HASH(element, set->num_bins); \
+ entry = (set)->bins[bin_pos]; \
+ while (entry != NULL && entry->element != element) { \
+ entry = entry->next; \
+ } \
+ } while (0)
+
+
+static int
+new_size(int size)
+{
+ int i;
+ int newsize;
+
+ for (i = 0, newsize = MINSIZE;
+ i < sizeof(primes)/sizeof(primes[0]);
+ i++, newsize <<= 1)
+ {
+ if (newsize > size)
+ return primes[i];
+ }
+ /* Ran out of polynomials */
+ return -1; /* should raise exception */
+}
+
+PointerSet *
+pointer_set_new()
+{
+ PointerSet *set;
+
+ set = alloc(PointerSet);
+ if (set != NULL) {
+ set->num_entries = 0;
+ set->num_bins = DEFAULT_TABLE_SIZE;
+ set->bins = (PointerSetEntry **) Calloc(DEFAULT_TABLE_SIZE, sizeof(PointerSetEntry *));
+ }
+ return set;
+}
+
+static void
+free_bin_contents(PointerSet *set)
+{
+ PointerSetEntry *entry, *next;
+ int i;
+
+ for(i = 0; i < set->num_bins; i++) {
+ entry = set->bins[i];
+ while (entry != NULL) {
+ next = entry->next;
+ free(entry);
+ entry = next;
+ }
+ set->bins[i] = NULL;
+ }
+ set->num_entries = 0;
+}
+
+void
+pointer_set_free(PointerSet *set)
+{
+ free_bin_contents(set);
+ free(set->bins);
+ free(set);
+}
+
+int
+pointer_set_contains(PointerSet *set, PointerSetElement element)
+{
+ PointerSetEntry *entry;
+ FIND_ENTRY(set, entry, element);
+ return entry != NULL;
+}
+
+static void
+rehash(PointerSet *set, int new_num_bins)
+{
+ PointerSetEntry *entry, **new_bins;
+ int i;
+
+ new_bins = (PointerSetEntry **) Calloc(new_num_bins, sizeof(PointerSetEntry *));
+ for (i = 0; i < set->num_bins; i++) {
+ entry = set->bins[i];
+ while (entry != NULL) {
+ unsigned int new_bin_pos;
+ PointerSetEntry *next;
+
+ new_bin_pos = HASH(entry->element, new_num_bins);
+ next = entry->next;
+ entry->next = new_bins[new_bin_pos];
+ new_bins[new_bin_pos] = entry;
+ entry = next;
+ }
+ }
+ free(set->bins);
+ set->num_bins = new_num_bins;
+ set->bins = new_bins;
+}
+
+void
+pointer_set_insert(PointerSet *set, PointerSetElement element)
+{
+ PointerSetEntry *entry;
+
+ FIND_ENTRY(set, entry, element);
+ if (entry == NULL) {
+ unsigned int bin_pos;
+
+ if (set->num_entries / (double) set->num_bins > MAX_LOAD_FACTOR) {
+ /* Increase number of bins to the next prime number. */
+ rehash(set, new_size(set->num_bins + 1));
+ }
+
+ bin_pos = HASH(element, set->num_bins);
+ entry = malloc(sizeof(PointerSetEntry));
+ entry->element = element;
+ entry->next = set->bins[bin_pos];
+ set->bins[bin_pos] = entry;
+ set->num_entries++;
+ }
+}
+
+void
+pointer_set_delete(PointerSet *set, PointerSetElement element)
+{
+ unsigned int bin_pos;
+ PointerSetEntry *entry, *prev;
+
+ bin_pos = HASH(element, set->num_bins);
+ entry = set->bins[bin_pos];
+ prev = NULL;
+ while (entry != NULL && entry->element != element) {
+ prev = entry;
+ entry = entry->next;
+ }
+ if (entry != NULL) {
+ if (prev != NULL) {
+ prev->next = entry->next;
+ } else {
+ set->bins[bin_pos] = entry->next;
+ }
+ free(entry);
+ set->num_entries--;
+ /* TODO: is it a good idea to reduce the number of bins? */
+ }
+}
+
+void
+pointer_set_reset(PointerSet *set)
+{
+ free_bin_contents(set);
+ set->bins = realloc(set->bins, sizeof(PointerSetEntry *) * DEFAULT_TABLE_SIZE);
+ set->num_bins = DEFAULT_TABLE_SIZE;
+ memset(set->bins, 0, sizeof(PointerSetEntry *) * DEFAULT_TABLE_SIZE);
+}
+
+unsigned int
+pointer_set_get_size(PointerSet *set)
+{
+ return set->num_entries;
+}
+
+unsigned int
+pointer_set_get_capacity(PointerSet *set)
+{
+ return set->num_bins;
+}
View
56 pointerset.h
@@ -0,0 +1,56 @@
+/**
+ * A specialized set data structure, designed to only contain pointers.
+ * It will grow and shrink dynamically.
+ */
+#ifndef _POINTER_SET_H_
+#define _POINTER_SET_H_
+
+typedef void * PointerSetElement;
+typedef struct _PointerSet PointerSet;
+
+/**
+ * Create a new, empty pointer set.
+ */
+PointerSet *pointer_set_new();
+
+/**
+ * Free the given pointer set.
+ */
+void pointer_set_free(PointerSet *set);
+
+/**
+ * Insert the given pointer into the pointer set. The data that the
+ * pointer pointers to is not touched, so <tt>element</tt> may even be
+ * an invalid pointer.
+ */
+void pointer_set_insert(PointerSet *set, PointerSetElement element);
+
+/**
+ * Remove the given pointer from the pointer set. Nothing will happen
+ * if the pointer isn't already in the set.
+ */
+void pointer_set_delete(PointerSet *set, PointerSetElement element);
+
+/**
+ * Check whether the given pointer is in the pointer set.
+ */
+int pointer_set_contains(PointerSet *set, PointerSetElement element);
+
+/**
+ * Clear the pointer set.
+ */
+void pointer_set_reset(PointerSet *set);
+
+/**
+ * Return the number of pointers in the pointer set.
+ */
+unsigned int pointer_set_get_size(PointerSet *set);
+
+/**
+ * Return the amount of space that is used to store the pointers in the set.
+ *
+ * @invariant pointer_set_get_capacity(set) >= pointer_set_get_size(set)
+ */
+unsigned int pointer_set_get_capacity(PointerSet *set);
+
+#endif /* _POINTER_SET_H_ */
View
4 ruby.h
@@ -460,7 +460,7 @@ struct RBignum {
#define FL_EXIVAR (1<<9)
#define FL_FREEZE (1<<10)
-#define FL_USHIFT 11
+#define FL_USHIFT 12
#define FL_USER0 (1<<(FL_USHIFT+0))
#define FL_USER1 (1<<(FL_USHIFT+1))
@@ -537,6 +537,8 @@ void rb_global_variable _((VALUE*));
void rb_gc_register_address _((VALUE*));
void rb_gc_unregister_address _((VALUE*));
+int rb_gc_is_thread_marked _((VALUE));
+
ID rb_intern _((const char*));
const char *rb_id2name _((ID));
ID rb_to_id _((VALUE));

0 comments on commit 6378e52

Please sign in to comment.
Something went wrong with that request. Please try again.