From 30b5c8a6c8835cb580d257331ad4e5c768b6b40d Mon Sep 17 00:00:00 2001 From: Andy Wingo Date: Mon, 28 Feb 2022 21:35:28 +0100 Subject: [PATCH] Use handle API, add semispace collector --- GCBench.c | 169 ++++++++++++++++++++++++++++++----------------- Makefile | 5 +- bdw.h | 46 ++++++++----- semi.h | 193 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 336 insertions(+), 77 deletions(-) create mode 100644 semi.h diff --git a/GCBench.c b/GCBench.c index 8331e82be..a16eb6eb1 100644 --- a/GCBench.c +++ b/GCBench.c @@ -42,17 +42,65 @@ #include #include +#ifdef GC_BDW +#include "bdw.h" +#elif defined(GC_SEMI) +#include "semi.h" +#else +#error unknown gc +#endif + +static const int kStretchTreeDepth = 18; // about 16Mb +static const int kLongLivedTreeDepth = 16; // about 4Mb +static const int kArraySize = 500000; // about 4Mb +static const int kMinTreeDepth = 4; +static const int kMaxTreeDepth = 16; + typedef struct Node { + GC_HEADER; struct Node * left; struct Node * right; int i, j; } Node; -#ifdef GC_BDW -#include "bdw.h" -#else -#error unknown gc -#endif +typedef struct DoubleArray { + GC_HEADER; + size_t length; + double values[0]; +} DoubleArray; + +static inline size_t node_size(void *obj) { + return sizeof(Node); +} +static inline size_t double_array_size(void *obj) { + DoubleArray *array = obj; + return sizeof(*array) + array->length * sizeof(double); +} +static inline void visit_node_fields(struct context *cx, void *obj, + field_visitor visit) { + Node *node = obj; + visit(cx, (void**)&node->left); + visit(cx, (void**)&node->right); +} +static inline void visit_double_array_fields(struct context *cx, void *obj, + field_visitor visit) { +} + +typedef HANDLE_TO(Node) NodeHandle; +typedef HANDLE_TO(DoubleArray) DoubleArrayHandle; + +static Node* allocate_node(struct context *cx) { + // memset to 0 by the collector. + return allocate(cx, NODE, sizeof (Node)); +} + +static struct DoubleArray* allocate_double_array(struct context *cx, + size_t size) { + // note, not memset to 0 by the collector. + DoubleArray *ret = allocate(cx, DOUBLE_ARRAY, sizeof (double) * size); + ret->length = size; + return ret; +} /* Get the current time in milliseconds */ static unsigned currentTime(void) @@ -65,12 +113,6 @@ static unsigned currentTime(void) return (t.tv_sec * 1000 + t.tv_usec / 1000); } -static const int kStretchTreeDepth = 18; // about 16Mb -static const int kLongLivedTreeDepth = 16; // about 4Mb -static const int kArraySize = 500000; // about 4Mb -static const int kMinTreeDepth = 4; -static const int kMaxTreeDepth = 16; - void init_Node(Node *me, Node *l, Node *r) { init_field((void**)&me->left, l); init_field((void**)&me->right, r); @@ -87,57 +129,57 @@ static int NumIters(int i) { } // Build tree top down, assigning to older objects. -static void Populate(int iDepth, Node *node) { +static void Populate(struct context *cx, int iDepth, Node *node) { if (iDepth<=0) { return; } else { iDepth--; NodeHandle self = { node }; - PUSH_HANDLE(self); - NodeHandle l = { allocate_node() }; - PUSH_HANDLE(l); - NodeHandle r = { allocate_node() }; - PUSH_HANDLE(r); + PUSH_HANDLE(cx, self); + NodeHandle l = { allocate_node(cx) }; + PUSH_HANDLE(cx, l); + NodeHandle r = { allocate_node(cx) }; + PUSH_HANDLE(cx, r); set_field((void**)&HANDLE_REF(self)->left, HANDLE_REF(l)); set_field((void**)&HANDLE_REF(self)->right, HANDLE_REF(r)); - Populate (iDepth, HANDLE_REF(self)->left); - Populate (iDepth, HANDLE_REF(self)->right); - POP_HANDLE(r); - POP_HANDLE(l); - POP_HANDLE(self); + Populate (cx, iDepth, HANDLE_REF(self)->left); + Populate (cx, iDepth, HANDLE_REF(self)->right); + POP_HANDLE(cx, r); + POP_HANDLE(cx, l); + POP_HANDLE(cx, self); } } // Build tree bottom-up -static Node* MakeTree(int iDepth) { +static Node* MakeTree(struct context *cx, int iDepth) { if (iDepth<=0) { - return allocate_node(); + return allocate_node(cx); } else { - NodeHandle left = { MakeTree(iDepth-1) }; - PUSH_HANDLE(left); - NodeHandle right = { MakeTree(iDepth-1) }; - PUSH_HANDLE(right); - Node *result = allocate_node(); + NodeHandle left = { MakeTree(cx, iDepth-1) }; + PUSH_HANDLE(cx, left); + NodeHandle right = { MakeTree(cx, iDepth-1) }; + PUSH_HANDLE(cx, right); + Node *result = allocate_node(cx); init_Node(result, HANDLE_REF(left), HANDLE_REF(right)); - POP_HANDLE(left); - POP_HANDLE(right); + POP_HANDLE(cx, right); + POP_HANDLE(cx, left); return result; } } -static void TimeConstruction(int depth) { +static void TimeConstruction(struct context *cx, int depth) { int iNumIters = NumIters(depth); NodeHandle tempTree = { NULL }; - PUSH_HANDLE(tempTree); + PUSH_HANDLE(cx, tempTree); printf("Creating %d trees of depth %d\n", iNumIters, depth); { long tStart = currentTime(); for (int i = 0; i < iNumIters; ++i) { - HANDLE_SET(tempTree, allocate_node()); - Populate(depth, HANDLE_REF(tempTree)); + HANDLE_SET(tempTree, allocate_node(cx)); + Populate(cx, depth, HANDLE_REF(tempTree)); HANDLE_SET(tempTree, NULL); } long tFinish = currentTime(); @@ -148,7 +190,7 @@ static void TimeConstruction(int depth) { { long tStart = currentTime(); for (int i = 0; i < iNumIters; ++i) { - HANDLE_SET(tempTree, MakeTree(depth)); + HANDLE_SET(tempTree, MakeTree(cx, depth)); HANDLE_SET(tempTree, NULL); } long tFinish = currentTime(); @@ -156,54 +198,61 @@ static void TimeConstruction(int depth) { tFinish - tStart); } - POP_HANDLE(tempTree); + POP_HANDLE(cx, tempTree); } int main() { + size_t kHeapMaxLive = + 2 * sizeof(struct Node) * TreeSize(kLongLivedTreeDepth) + + sizeof(double) * kArraySize; + double kHeapMultiplier = 3; + size_t kHeapSize = kHeapMaxLive * kHeapMultiplier; + + struct context _cx; + struct context *cx = &_cx; + initialize_gc(cx, kHeapSize); + NodeHandle root = { NULL }; NodeHandle longLivedTree = { NULL }; NodeHandle tempTree = { NULL }; - HANDLE_TO(double) array = { NULL }; + DoubleArrayHandle array = { NULL }; - PUSH_HANDLE(root); - PUSH_HANDLE(longLivedTree); - PUSH_HANDLE(tempTree); - PUSH_HANDLE(array); - - initialize_gc(); + PUSH_HANDLE(cx, root); + PUSH_HANDLE(cx, longLivedTree); + PUSH_HANDLE(cx, tempTree); + PUSH_HANDLE(cx, array); printf("Garbage Collector Test\n"); - printf(" Live storage will peak at %zd bytes.\n\n", - 2 * sizeof(struct Node) * TreeSize(kLongLivedTreeDepth) + - sizeof(double) * kArraySize); + printf(" Live storage will peak at %zd bytes.\n\n", kHeapMaxLive); printf(" Stretching memory with a binary tree of depth %d\n", kStretchTreeDepth); - print_start_gc_stats(); + print_start_gc_stats(cx); long tStart = currentTime(); // Stretch the memory space quickly - HANDLE_SET(tempTree, MakeTree(kStretchTreeDepth)); + HANDLE_SET(tempTree, MakeTree(cx, kStretchTreeDepth)); HANDLE_SET(tempTree, NULL); // Create a long lived object printf(" Creating a long-lived binary tree of depth %d\n", kLongLivedTreeDepth); - HANDLE_SET(longLivedTree, allocate_node()); - Populate(kLongLivedTreeDepth, HANDLE_REF(longLivedTree)); + HANDLE_SET(longLivedTree, allocate_node(cx)); + Populate(cx, kLongLivedTreeDepth, HANDLE_REF(longLivedTree)); // Create long-lived array, filling half of it printf(" Creating a long-lived array of %d doubles\n", kArraySize); - HANDLE_SET(array, allocate_double_array(kArraySize)); + HANDLE_SET(array, allocate_double_array(cx, kArraySize)); for (int i = 0; i < kArraySize/2; ++i) { - HANDLE_REF(array)[i] = 1.0/i; + HANDLE_REF(array)->values[i] = 1.0/i; } for (int d = kMinTreeDepth; d <= kMaxTreeDepth; d += 2) { - TimeConstruction(d); + TimeConstruction(cx, d); } - if (HANDLE_REF(longLivedTree) == 0 || HANDLE_REF(array)[1000] != 1.0/1000) + if (HANDLE_REF(longLivedTree) == 0 + || HANDLE_REF(array)->values[1000] != 1.0/1000) fprintf(stderr, "Failed\n"); // fake reference to LongLivedTree // and array @@ -212,11 +261,11 @@ int main() { long tFinish = currentTime(); long tElapsed = tFinish - tStart; printf("Completed in %ld msec\n", tElapsed); - print_end_gc_stats(); + print_end_gc_stats(cx); - POP_HANDLE(array); - POP_HANDLE(tempTree); - POP_HANDLE(longLivedTree); - POP_HANDLE(root); + POP_HANDLE(cx, array); + POP_HANDLE(cx, tempTree); + POP_HANDLE(cx, longLivedTree); + POP_HANDLE(cx, root); } diff --git a/Makefile b/Makefile index 819cf3f5d..c5573977c 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,5 @@ TESTS=GCBench MT_GCBench MT_GCBench2 -COLLECTORS=bdw +COLLECTORS=bdw semi CC=gcc CFLAGS=-Wall -O2 -g @@ -11,6 +11,9 @@ all: $(ALL_TESTS) bdw-%: bdw.h %.c $(CC) $(CFLAGS) -lpthread `pkg-config --libs --cflags bdw-gc` -I. -DGC_BDW -o $@ $*.c +semi-%: semi.h %.c + $(CC) $(CFLAGS) -I. -DGC_SEMI -o $@ $*.c + check: $(addprefix test-$(TARGET),$(TARGETS)) test-%: $(ALL_TESTS) diff --git a/bdw.h b/bdw.h index aea294ac6..571f67639 100644 --- a/bdw.h +++ b/bdw.h @@ -12,14 +12,24 @@ #include -static Node* allocate_node(void) { - // memset to 0 by the collector. - return GC_malloc (sizeof (Node)); -} +struct context {}; -static double* allocate_double_array(size_t size) { - // note, not memset to 0 by the collector. - return GC_malloc_atomic (sizeof (double) * size); +enum alloc_kind { NODE, DOUBLE_ARRAY }; + +typedef void (*field_visitor)(struct context *, void **ref); + +#define GC_HEADER /**/ + +static inline void* allocate(struct context *cx, enum alloc_kind kind, + size_t size) { + // memset to 0 by the collector. + switch (kind) { + case NODE: + return GC_malloc(size); + case DOUBLE_ARRAY: + return GC_malloc_atomic(size); + } + abort(); } struct handle { @@ -29,15 +39,13 @@ struct handle { #define HANDLE_TO(T) union { T* v; struct handle handle; } #define HANDLE_REF(h) h.v #define HANDLE_SET(h,val) do { h.v = val; } while (0) -#define PUSH_HANDLE(h) push_handle(&h.handle) -#define POP_HANDLE(h) pop_handle(&h.handle) +#define PUSH_HANDLE(cx, h) push_handle(cx, &h.handle) +#define POP_HANDLE(cx, h) pop_handle(cx, &h.handle) -typedef HANDLE_TO(Node) NodeHandle; - -static inline void push_handle(struct handle *handle) { +static inline void push_handle(struct context *cx, struct handle *handle) { } -static inline void pop_handle(struct handle *handle) { +static inline void pop_handle(struct context *cx, struct handle *handle) { } static inline void init_field(void **addr, void *val) { @@ -50,16 +58,22 @@ static inline void* get_field(void **addr) { return *addr; } -static inline void initialize_gc(void) { +static inline void initialize_gc(struct context* cx, size_t heap_size) { // GC_full_freq = 30; // GC_free_space_divisor = 16; // GC_enable_incremental(); + GC_INIT(); + size_t current_heap_size = GC_get_heap_size(); + if (heap_size > current_heap_size) { + GC_set_max_heap_size (heap_size); + GC_expand_hp(heap_size - current_heap_size); + } } -static inline void print_start_gc_stats(void) { +static inline void print_start_gc_stats(struct context *cx) { } -static inline void print_end_gc_stats(void) { +static inline void print_end_gc_stats(struct context *cx) { printf("Completed %ld collections\n", (long)GC_get_gc_no()); printf("Heap size is %ld\n", (long)GC_get_heap_size()); } diff --git a/semi.h b/semi.h new file mode 100644 index 000000000..16bb8566c --- /dev/null +++ b/semi.h @@ -0,0 +1,193 @@ +#include +#include +#include +#include +#include + +struct handle { + void *v; + struct handle *next; +}; + +struct context { + uintptr_t hp; + uintptr_t limit; + uintptr_t base; + size_t size; + struct handle *roots; + long count; +}; + +static const uintptr_t ALIGNMENT = 8; + +static uintptr_t align_up(uintptr_t addr, size_t align) { + return (addr + align - 1) & ~(align-1); +} + +#define GC_HEADER uintptr_t _gc_header + +enum alloc_kind { NODE, DOUBLE_ARRAY }; + +typedef void (*field_visitor)(struct context *, void **ref); + +static inline size_t node_size(void *obj) __attribute__((always_inline)); +static inline size_t double_array_size(void *obj) __attribute__((always_inline)); +static inline void visit_node_fields(struct context *cx, void *obj, field_visitor visit) __attribute__((always_inline)); +static inline void visit_double_array_fields(struct context *cx, void *obj, field_visitor visit) __attribute__((always_inline)); + +static inline void clear_memory(uintptr_t addr, size_t size) { + memset((char*)addr, 0, size); +} + +static void collect(struct context *cx, size_t bytes) __attribute__((noinline)); + +static void process(struct context *cx, void **loc); + +static void flip(struct context *cx) { + uintptr_t split = cx->base + (cx->size >> 1); + if (cx->hp <= split) { + cx->hp = split; + cx->limit = cx->base + cx->size; + } else { + cx->hp = cx->base; + cx->limit = split; + } + cx->count++; +} + +static void* copy(struct context *cx, uintptr_t kind, void *obj) { + size_t size; + switch (kind) { + case NODE: + size = node_size(obj); + break; + case DOUBLE_ARRAY: + size = double_array_size(obj); + break; + default: + abort (); + } + void *new_obj = (void*)cx->hp; + memcpy(new_obj, obj, size); + *(uintptr_t*) obj = cx->hp; + cx->hp += align_up (size, ALIGNMENT); + return new_obj; +} + +static uintptr_t scan(struct context *cx, uintptr_t grey) { + void *obj = (void*)grey; + uintptr_t kind = *(uintptr_t*) obj; + switch (kind) { + case NODE: + visit_node_fields(cx, obj, process); + return grey + align_up (node_size(obj), ALIGNMENT); + break; + case DOUBLE_ARRAY: + visit_double_array_fields(cx, obj, process); + return grey + align_up (double_array_size(obj), ALIGNMENT); + break; + default: + abort (); + } +} + +static void* forward(struct context *cx, void *obj) { + uintptr_t header_word = *(uintptr_t*)obj; + switch (header_word) { + case NODE: + case DOUBLE_ARRAY: + return copy(cx, header_word, obj); + default: + return (void*)header_word; + } +} + +static void process(struct context *cx, void **loc) { + void *obj = *loc; + if (obj != NULL) + *loc = forward(cx, obj); +} +static void collect(struct context *cx, size_t bytes) { + // fprintf(stderr, "start collect #%ld:\n", cx->count); + flip(cx); + uintptr_t grey = cx->hp; + for (struct handle *h = cx->roots; h; h = h->next) + process(cx, &h->v); + // fprintf(stderr, "pushed %zd bytes in roots\n", cx->hp - grey); + while(grey < cx->hp) + grey = scan(cx, grey); + // fprintf(stderr, "%zd bytes copied\n", (cx->size>>1)-(cx->limit-cx->hp)); + + if (cx->limit - cx->hp < bytes) { + fprintf(stderr, "ran out of space, heap size %zu\n", cx->size); + abort(); + } +} + +static inline void* allocate(struct context *cx, enum alloc_kind kind, + size_t size) { + while (1) { + uintptr_t addr = cx->hp; + uintptr_t new_hp = align_up (addr + size, ALIGNMENT); + if (cx->limit < new_hp) { + collect(cx, size); + continue; + } + cx->hp = new_hp; + void *ret = (void *)addr; + uintptr_t *header_word = ret; + *header_word = kind; + if (kind == NODE) + clear_memory(addr + sizeof(uintptr_t), size - sizeof(uintptr_t)); + return ret; + } +} + +#define HANDLE_TO(T) union { T* v; struct handle handle; } +#define HANDLE_REF(h) h.v +#define HANDLE_SET(h,val) do { h.v = val; } while (0) +#define PUSH_HANDLE(cx, h) push_handle(cx, &h.handle) +#define POP_HANDLE(cx, h) pop_handle(cx, &h.handle) + +static inline void push_handle(struct context *cx, struct handle *handle) { + handle->next = cx->roots; + cx->roots = handle; +} + +static inline void pop_handle(struct context *cx, struct handle *handle) { + cx->roots = handle->next; +} + +static inline void init_field(void **addr, void *val) { + *addr = val; +} +static inline void set_field(void **addr, void *val) { + *addr = val; +} +static inline void* get_field(void **addr) { + return *addr; +} + +static inline void initialize_gc(struct context *cx, size_t size) { + size = align_up(size, getpagesize()); + + void *mem = mmap(NULL, size, PROT_READ|PROT_WRITE, + MAP_PRIVATE|MAP_ANONYMOUS, -1, 0); + if (mem == MAP_FAILED) { + perror("mmap failed"); + abort(); + } + cx->hp = cx->base = (uintptr_t) mem; + cx->size = size; + cx->count = -1; + flip(cx); + cx->roots = NULL; +} + +static inline void print_start_gc_stats(struct context *cx) { +} + +static inline void print_end_gc_stats(struct context *cx) { + printf("Completed %ld collections\n", cx->count); + printf("Heap size is %zd\n", cx->size); +}