From 8db40406a06d6f9bd9e390994dcfa104c30f2c18 Mon Sep 17 00:00:00 2001 From: Jesse McDonald Date: Mon, 26 Oct 2009 13:54:25 -0500 Subject: [PATCH] Refactor into a GC library (gc.c and gc.h) and a test program (gc_test.c). Also, allocate GC memory ranges dynamically during startup (gc_init()) rather than statically. --- Makefile | 22 +++-- gc.c | 244 +++++++++++------------------------------------------- gc.h | 126 ++++++++++++++++++++++++++++ gc_test.c | 69 +++++++++++++++ 4 files changed, 262 insertions(+), 199 deletions(-) create mode 100644 gc.h create mode 100644 gc_test.c diff --git a/Makefile b/Makefile index 4885988..c78765a 100644 --- a/Makefile +++ b/Makefile @@ -1,18 +1,30 @@ -CFLAGS = -std=c99 -O2 -DNDEBUG +CFLAGS = -std=c99 -all: gc +ifeq ($(DEBUG),yes) +CFLAGS += -g +PROFILE = no +dummy := $(shell $(MAKE) clean) +else +CFLAGS += -O2 -DNDEBUG +endif + +all: gc_test .PHONY: all clean ifneq ($(PROFILE),no) CFLAGS += -fprofile-generate +LDFLAGS += -fprofile-generate endif ifneq (,$(wildcard *.gcda)) CFLAGS += -fprofile-use -dummy := $(shell rm -f gc) +dummy := $(shell rm -f gc_test *.o) endif clean: - -rm -f gc gc.gcda gc.gcno + -rm -f gc_test *.o *.gcda *.gcno -gc: gc.c +gc_test: gc_test.o gc.o + +gc_test.o: gc_test.c gc.h +gc.o: gc.c gc.h diff --git a/gc.c b/gc.c index b014a16..e9afff6 100644 --- a/gc.c +++ b/gc.c @@ -1,5 +1,3 @@ -#include - #include #include #include @@ -8,115 +6,13 @@ #include #include -#ifndef NDEBUG -# define debug(printf_args) ((void)printf printf_args) -#else -# define debug(printf_args) ((void)0) -#endif +#include "gc.h" -typedef uintptr_t value_t; +extern int gc_counter; +extern int gc_ticks; -/* NIL: 00000000 00000000 00000000 00000000 */ -/* Pair: aaaaaaaa aaaaaaaa aaaaaaaa aaaaaa00 (where aa... >= 1024) */ -/* Object: aaaaaaaa aaaaaaaa aaaaaaaa aaaaaa10 */ -/* Fixnum: snnnnnnn nnnnnnnn nnnnnnnn nnnnnnn1 */ - - -/* Special values (0 <= n < 1024) */ -/* These correspond to pairs within the first page of memory */ -#define SPECIAL_VALUE(n) ((value_t)(4*n)) -#define MAX_SPECIAL SPECIAL_VALUE(1023) - -#define NIL SPECIAL_VALUE(0) -#define BROKEN_HEART SPECIAL_VALUE(1) -#define TYPE_TAG_BOX SPECIAL_VALUE(2) -#define TYPE_TAG_VECTOR SPECIAL_VALUE(3) -#define TYPE_TAG_BYTESTR SPECIAL_VALUE(4) - -typedef struct pair -{ - value_t car; - value_t cdr; -} pair_t; - -typedef struct object -{ - value_t tag; - union { - value_t values[0]; - char bytes[0]; - } payload; -} object_t; - -typedef struct vector -{ - value_t tag; - size_t size; - value_t elements[0]; -} vector_t; - -typedef struct byte_string -{ - value_t tag; - size_t size; - uint8_t bytes[0]; -} byte_string_t; - -typedef struct gc_root -{ - value_t value; - struct gc_root *prev; - struct gc_root *next; -} gc_root_t; - -static inline bool is_pair(value_t v) -{ - return ((v & 0x3) == 0) && (v > MAX_SPECIAL); -} - -static inline bool is_object(value_t v) -{ - return ((v & 0x1) == 0) && (v > MAX_SPECIAL); -} - -static inline bool is_fixnum(value_t v) -{ - return (v & 1) != 0; -} - -static inline value_t to_fixnum(intptr_t n) -{ - return (value_t)(n << 1) | 1; -} - -static inline intptr_t from_fixnum(value_t n) -{ - return ((intptr_t)n) >> 1; -} - -static inline value_t pair_value(pair_t *p) -{ - return (value_t)p; -} - -static inline value_t object_value(void *obj) -{ - return (value_t)obj | 2; -} - -object_t *get_object(value_t v); -pair_t *get_pair(value_t pair); -value_t get_cdr(value_t pair); -void replace_car(value_t pair, value_t car); -void replace_cdr(value_t pair, value_t cdr); -value_t cons(value_t car, value_t cdr); - -void register_gc_root(gc_root_t *root, value_t v); -void unregister_gc_root(gc_root_t *root); -void *gc_alloc(size_t nbytes); -void collect_garbage(size_t min_free); - -/***********************************************/ +/* Alignment must ensure each object has enough room to hold a pair (BH . new_addr) */ +#define GC_ALIGNMENT ((size_t)(sizeof(pair_t))) /* Pairs are a type of object, but the value representation is different */ object_t *get_object(value_t v) @@ -130,13 +26,11 @@ object_t *get_object(value_t v) pair_t *get_pair(value_t v) { if (is_pair(v)) - return (pair_t*)v; + return (pair_t*)(v - 2); else abort(); } -static int cons_counter = 0; - value_t cons(value_t car, value_t cdr) { gc_root_t car_root, cdr_root; @@ -152,26 +46,23 @@ value_t cons(value_t car, value_t cdr) unregister_gc_root(&car_root); unregister_gc_root(&cdr_root); - ++cons_counter; - return pair_value(p); } -/* Equal to the max. active set size. */ -#define GC_RANGE_SIZE (256*1024*1024) +static char *gc_ranges[2]; +static size_t gc_min_size; +static size_t gc_max_size; +static size_t gc_soft_limit; -/* GC starts after this much space has been allocated. Increases by 5% each time. */ -#define GC_INIT_SIZE 1024 +static int gc_current_range; +static size_t gc_free_space; +static char *gc_free_ptr; -/* Alignment must ensure each object has enough room to hold a pair (BH . new_addr) */ -#define GC_ALIGNMENT ((size_t)(sizeof(pair_t))) - -static pair_t gc_ranges[2][GC_RANGE_SIZE / sizeof(pair_t)]; -static char *gc_free_ptr = (char*)&gc_ranges[0][0]; -static size_t gc_free_space = GC_INIT_SIZE; -static size_t gc_soft_limit = GC_INIT_SIZE; -static int current_gc_range = 0; -static gc_root_t gc_root_list = { NIL, &gc_root_list, &gc_root_list }; +static gc_root_t gc_root_list = { + .value = NIL, + .prev = &gc_root_list, + .next = &gc_root_list +}; static inline size_t gc_align(size_t nbytes) __attribute__ ((const)); static int gc_range_of(void *object) __attribute__ ((const)); @@ -194,6 +85,24 @@ static int gc_range_of(void *object) return -1; } +void gc_init(size_t min_size, size_t max_size) +{ + assert(min_size <= max_size); + + gc_ranges[0] = (char*)malloc(max_size); + gc_ranges[1] = (char*)malloc(max_size); + + assert(gc_ranges[0] && gc_ranges[1]); + + gc_current_range = 0; + gc_free_ptr = gc_ranges[gc_current_range]; + + gc_min_size = min_size; + gc_max_size = max_size; + gc_soft_limit = gc_min_size; + gc_free_space = gc_soft_limit; +} + void register_gc_root(gc_root_t *root, value_t v) { root->value = v; @@ -216,13 +125,6 @@ void unregister_gc_root(gc_root_t *root) root->prev = root->next = root; } -static void out_of_memory(void) __attribute__ ((noreturn)); -static void out_of_memory(void) -{ - fprintf(stderr, "Out of memory!\n"); - abort(); -} - void *gc_alloc(size_t nbytes) { nbytes = gc_align(nbytes); @@ -244,7 +146,7 @@ static void transfer_object(value_t *value) object_t *obj = get_object(*value); value_t new_value; - assert(gc_range_of(obj) != current_gc_range); + assert(gc_range_of(obj) != gc_current_range); if (obj->tag == BROKEN_HEART) { @@ -296,10 +198,10 @@ static void transfer_object(value_t *value) #ifndef NDEBUG if (is_pair(new_value)) { - if (gc_range_of(get_pair(new_value)) != current_gc_range) + if (gc_range_of(get_pair(new_value)) != gc_current_range) { debug(("Invalid address after transfer: 0x%0.8X. Current GC: %d.\n", - get_pair(new_value), current_gc_range)); + get_pair(new_value), gc_current_range)); abort(); } } @@ -360,9 +262,12 @@ void collect_garbage(size_t min_free) else collecting = true; + ++gc_counter; + gc_ticks -= (int)clock(); + /* Swap ranges; new "current" range is initially empty, old one is full */ - current_gc_range = 1 - current_gc_range; - gc_free_ptr = (char*)&gc_ranges[current_gc_range][0]; + gc_current_range = 1 - gc_current_range; + gc_free_ptr = (char*)&gc_ranges[gc_current_range][0]; gc_free_space = gc_soft_limit; object_ptr = gc_free_ptr; @@ -388,12 +293,12 @@ void collect_garbage(size_t min_free) size_t min_limit = bytes_used + min_free; size_t new_limit = (3 * min_limit) / 2; - if (new_limit > GC_RANGE_SIZE) - new_limit = GC_RANGE_SIZE; - else if (new_limit < GC_INIT_SIZE) - new_limit = GC_INIT_SIZE; + if (new_limit > gc_max_size) + new_limit = gc_max_size; + else if (new_limit < gc_min_size) + new_limit = gc_min_size; - gc_free_space += (new_limit - gc_soft_limit); + gc_free_space = (gc_free_space + new_limit) - gc_soft_limit; gc_soft_limit = new_limit; } @@ -404,57 +309,8 @@ void collect_garbage(size_t min_free) /* Done collecting. */ collecting = false; -} -int main(int argc, char **argv) -{ - struct timeval start_time; - gc_root_t list_root; - int count = 0; - - gettimeofday(&start_time, NULL); - srand((unsigned int)start_time.tv_usec); - - register_gc_root(&list_root, NIL); - - while (1) - { - int r = rand() & 0xffff; - - if (r == 0) - list_root.value = to_fixnum(rand()); - else if (r & 1) - list_root.value = cons(to_fixnum(rand()), list_root.value); - else if (r & 2) - list_root.value = cons(list_root.value, to_fixnum(rand())); - else if (r & 4) - { - list_root.value = cons(list_root.value, cons(to_fixnum(-1), NIL)); - get_pair(get_pair(list_root.value)->cdr)->cdr = list_root.value; - } - - if (cons_counter >= 1000000) - { - struct timeval end_time; - double seconds; - gettimeofday(&end_time, NULL); - - seconds = (end_time.tv_sec - start_time.tv_sec) - + 0.000001 * (end_time.tv_usec - start_time.tv_usec); - - printf("%d conses took %.6f seconds.\n", cons_counter, seconds); - - gettimeofday(&start_time, NULL); - cons_counter = 0; - - if (++count == 50) - break; - } - } - - unregister_gc_root(&list_root); - - return 0; + gc_ticks += (int)clock(); } /* vim:set sw=2 expandtab: */ diff --git a/gc.h b/gc.h new file mode 100644 index 0000000..f7b741d --- /dev/null +++ b/gc.h @@ -0,0 +1,126 @@ +#ifndef GC_H_6b8a27c99f2eb5eb5437e045fa4af1c3 +#define GC_H_6b8a27c99f2eb5eb5437e045fa4af1c3 + +#include +#include +#include +#include + +#ifndef NDEBUG +# define debug(printf_args) ((void)printf printf_args) +#else +# define debug(printf_args) ((void)0) +#endif + +typedef uintptr_t value_t; + +/* NIL: 00000000 00000000 00000000 00000000 */ +/* Object: aaaaaaaa aaaaaaaa aaaaaaaa aaaaaa00 (where aa... >= 1024) */ +/* Pair: aaaaaaaa aaaaaaaa aaaaaaaa aaaaaa10 */ +/* Fixnum: snnnnnnn nnnnnnnn nnnnnnnn nnnnnnn1 */ + +#define NIL ((value_t)0) + +/* Special values (0 <= n < 1024) */ +/* These correspond to objects within the first page of memory */ +#define SPECIAL_VALUE(n) ((value_t)(4*n+2)) +#define MAX_SPECIAL SPECIAL_VALUE(1023) + +#define BROKEN_HEART SPECIAL_VALUE(0) +#define TYPE_TAG_BOX SPECIAL_VALUE(1) +#define TYPE_TAG_VECTOR SPECIAL_VALUE(2) +#define TYPE_TAG_BYTESTR SPECIAL_VALUE(3) + +typedef struct pair +{ + value_t car; + value_t cdr; +} pair_t; + +typedef struct object +{ + value_t tag; + union { + value_t values[0]; + char bytes[0]; + } payload; +} object_t; + +typedef struct vector +{ + value_t tag; + size_t size; + value_t elements[0]; +} vector_t; + +typedef struct byte_string +{ + value_t tag; + size_t size; + uint8_t bytes[0]; +} byte_string_t; + +typedef struct gc_root +{ + value_t value; + struct gc_root *prev; + struct gc_root *next; +} gc_root_t; + +static inline bool is_pair(value_t v) +{ + return ((v & 0x3) == 2); +} + +static inline bool is_object(value_t v) +{ + /* Neither pairs nor objects can exist below (void*)4096. */ + return ((v & 0x1) == 0) && (v > MAX_SPECIAL); +} + +static inline bool is_fixnum(value_t v) +{ + return (v & 1) != 0; +} + +static inline value_t to_fixnum(intptr_t n) +{ + return (value_t)(n << 1) | 1; +} + +static inline intptr_t from_fixnum(value_t n) +{ + return ((intptr_t)n) >> 1; +} + +static inline value_t pair_value(pair_t *p) +{ + assert((uintptr_t)p >= 4096); + assert(((uintptr_t)p & 3) == 0); + return (value_t)p + 2; +} + +static inline value_t object_value(void *obj) +{ + assert((uintptr_t)obj >= 4096); + assert(((uintptr_t)obj & 3) == 0); + return (value_t)obj; +} + +object_t *get_object(value_t v); +pair_t *get_pair(value_t pair); +value_t get_cdr(value_t pair); +value_t cons(value_t car, value_t cdr); + +void gc_init(size_t min_size, size_t max_size); +void register_gc_root(gc_root_t *root, value_t v); +void unregister_gc_root(gc_root_t *root); +void *gc_alloc(size_t nbytes); +void collect_garbage(size_t min_free); + +/* To be provided by the main application */ +void out_of_memory(void) __attribute__ ((noreturn)); + +#endif + +/* vim:set sw=2 expandtab: */ diff --git a/gc_test.c b/gc_test.c new file mode 100644 index 0000000..fa46b86 --- /dev/null +++ b/gc_test.c @@ -0,0 +1,69 @@ +#include + +#include +#include +#include +#include +#include +#include + +#include "gc.h" + +int gc_counter; +int gc_ticks; + +void out_of_memory(void) +{ + fprintf(stderr, "Out of memory!\n"); + abort(); +} + +int main(int argc, char **argv) +{ + gc_root_t list_root; + int count = 0; + + gc_init(1024, 256*1024*1024); + + gc_counter = 0; + gc_ticks = 0; + + srand((unsigned int)time(NULL)); + + register_gc_root(&list_root, NIL); + + while (1) + { + int r = rand() & 0xffff; + + if (r == 0) + list_root.value = to_fixnum(rand()); + else if (r & 1) + list_root.value = cons(to_fixnum(rand()), list_root.value); + else if (r & 2) + list_root.value = cons(list_root.value, to_fixnum(rand())); + else if (r & 4) + { + list_root.value = cons(list_root.value, cons(to_fixnum(-1), NIL)); + get_pair(get_pair(list_root.value)->cdr)->cdr = list_root.value; + } + + if (gc_counter >= 1000) + { + fprintf(stderr, "%d collections in %0.3f seconds = %0.3f usec / GC\n", + gc_counter, + gc_ticks / (double)CLOCKS_PER_SEC, + (1000000 * (gc_ticks / (double)CLOCKS_PER_SEC)) / gc_counter); + gc_counter = gc_ticks = 0; + + if (++count == 10) + break; + } + } + + unregister_gc_root(&list_root); + + return 0; +} + +/* vim:set sw=2 expandtab: */