diff --git a/Makefile b/Makefile index c1ba15f43..db5f1a7c2 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -TESTS = quads mt-gcbench ephemerons # MT_GCBench MT_GCBench2 +TESTS = quads mt-gcbench ephemerons finalizers COLLECTORS = \ bdw \ semi \ diff --git a/benchmarks/finalizers-embedder.h b/benchmarks/finalizers-embedder.h new file mode 100644 index 000000000..0dde1ae29 --- /dev/null +++ b/benchmarks/finalizers-embedder.h @@ -0,0 +1,55 @@ +#ifndef FINALIZERS_EMBEDDER_H +#define FINALIZERS_EMBEDDER_H + +#include + +#include "finalizers-types.h" +#include "gc-finalizer.h" + +struct gc_heap; + +#define DEFINE_METHODS(name, Name, NAME) \ + static inline size_t name##_size(Name *obj) GC_ALWAYS_INLINE; \ + static inline void visit_##name##_fields(Name *obj,\ + void (*visit)(struct gc_edge edge, \ + struct gc_heap *heap, \ + void *visit_data), \ + struct gc_heap *heap, \ + void *visit_data) GC_ALWAYS_INLINE; +FOR_EACH_HEAP_OBJECT_KIND(DEFINE_METHODS) +#undef DEFINE_METHODS + +static inline size_t small_object_size(SmallObject *obj) { return sizeof(*obj); } +static inline size_t finalizer_size(Finalizer *obj) { return gc_finalizer_size(); } +static inline size_t pair_size(Pair *obj) { return sizeof(*obj); } + +static inline void +visit_small_object_fields(SmallObject *obj, + void (*visit)(struct gc_edge edge, struct gc_heap *heap, + void *visit_data), + struct gc_heap *heap, + void *visit_data) {} + +static inline void +visit_finalizer_fields(Finalizer *finalizer, + void (*visit)(struct gc_edge edge, struct gc_heap *heap, + void *visit_data), + + struct gc_heap *heap, + void *visit_data) { + gc_trace_finalizer((struct gc_finalizer*)finalizer, visit, heap, visit_data); +} + +static inline void +visit_pair_fields(Pair *pair, + void (*visit)(struct gc_edge edge, struct gc_heap *heap, + void *visit_data), + struct gc_heap *heap, + void *visit_data) { + visit(gc_edge(&pair->car), heap, visit_data); + visit(gc_edge(&pair->cdr), heap, visit_data); +} + +#include "simple-gc-embedder.h" + +#endif // FINALIZERS_EMBEDDER_H diff --git a/benchmarks/finalizers-types.h b/benchmarks/finalizers-types.h new file mode 100644 index 000000000..3597ad5d7 --- /dev/null +++ b/benchmarks/finalizers-types.h @@ -0,0 +1,22 @@ +#ifndef FINALIZERS_TYPES_H +#define FINALIZERS_TYPES_H + +#define FOR_EACH_HEAP_OBJECT_KIND(M) \ + M(pair, Pair, PAIR) \ + M(finalizer, Finalizer, FINALIZER) \ + M(small_object, SmallObject, SMALL_OBJECT) + +#include "heap-objects.h" +#include "simple-tagging-scheme.h" + +struct SmallObject { + struct gc_header header; +}; + +struct Pair { + struct gc_header header; + void *car; + void *cdr; +}; + +#endif // FINALIZERS_TYPES_H diff --git a/benchmarks/finalizers.c b/benchmarks/finalizers.c new file mode 100644 index 000000000..434283a53 --- /dev/null +++ b/benchmarks/finalizers.c @@ -0,0 +1,266 @@ +#include +#include +#include +#include +#include +#include + +#include "assert.h" +#include "gc-api.h" +#include "gc-basic-stats.h" +#include "gc-finalizer.h" +#include "simple-roots-api.h" +#include "finalizers-types.h" +#include "simple-allocator.h" + +typedef HANDLE_TO(SmallObject) SmallObjectHandle; +typedef HANDLE_TO(struct gc_finalizer) FinalizerHandle; +typedef HANDLE_TO(Pair) PairHandle; + +static SmallObject* allocate_small_object(struct gc_mutator *mut) { + return gc_allocate_with_kind(mut, ALLOC_KIND_SMALL_OBJECT, sizeof(SmallObject)); +} + +static Pair* allocate_pair(struct gc_mutator *mut) { + return gc_allocate_with_kind(mut, ALLOC_KIND_PAIR, sizeof(Pair)); +} + +static struct gc_finalizer* allocate_finalizer(struct gc_mutator *mut) { + struct gc_finalizer *ret = gc_allocate_finalizer(mut); + *tag_word(gc_ref_from_heap_object(ret)) = tag_live(ALLOC_KIND_FINALIZER); + return ret; +} + +/* Get the current time in microseconds */ +static unsigned long current_time(void) +{ + struct timeval t; + if (gettimeofday(&t, NULL) == -1) + return 0; + return t.tv_sec * 1000 * 1000 + t.tv_usec; +} + +struct thread { + struct gc_mutator *mut; + struct gc_mutator_roots roots; +}; + +static void print_elapsed(const char *what, unsigned long start) { + unsigned long end = current_time(); + unsigned long msec = (end - start) / 1000; + unsigned long usec = (end - start) % 1000; + printf("Completed %s in %lu.%.3lu msec\n", what, msec, usec); +} + +struct call_with_gc_data { + void* (*f)(struct thread *); + struct gc_heap *heap; +}; +static void* call_with_gc_inner(struct gc_stack_addr *addr, void *arg) { + struct call_with_gc_data *data = arg; + struct gc_mutator *mut = gc_init_for_thread(addr, data->heap); + struct thread t = { mut, }; + gc_mutator_set_roots(mut, &t.roots); + void *ret = data->f(&t); + gc_finish_for_thread(mut); + return ret; +} +static void* call_with_gc(void* (*f)(struct thread *), + struct gc_heap *heap) { + struct call_with_gc_data data = { f, heap }; + return gc_call_with_stack_addr(call_with_gc_inner, &data); +} + +#define CHECK(x) \ + do { \ + if (!(x)) { \ + fprintf(stderr, "%s:%d: check failed: %s\n", __FILE__, __LINE__, #x); \ + exit(1); \ + } \ + } while (0) + +#define CHECK_EQ(x, y) CHECK((x) == (y)) +#define CHECK_NE(x, y) CHECK((x) != (y)) +#define CHECK_NULL(x) CHECK_EQ(x, NULL) +#define CHECK_NOT_NULL(x) CHECK_NE(x, NULL) + +static double heap_size; +static double heap_multiplier; +static size_t nthreads; + +static void cause_gc(struct gc_mutator *mut) { + // Doing a full collection lets us reason precisely about liveness. + gc_collect(mut, GC_COLLECTION_MAJOR); +} + +static Pair* make_finalizer_chain(struct thread *t, size_t length) { + PairHandle head = { NULL }; + PairHandle tail = { NULL }; + PUSH_HANDLE(t, head); + PUSH_HANDLE(t, tail); + + for (size_t i = 0; i < length; i++) { + HANDLE_SET(tail, HANDLE_REF(head)); + HANDLE_SET(head, allocate_pair(t->mut)); + HANDLE_REF(head)->car = allocate_small_object(t->mut); + HANDLE_REF(head)->cdr = HANDLE_REF(tail); + struct gc_finalizer *finalizer = allocate_finalizer(t->mut); + gc_finalizer_attach(t->mut, finalizer, 0, + gc_ref_from_heap_object(HANDLE_REF(head)), + gc_ref_from_heap_object(HANDLE_REF(head)->car)); + } + + Pair *ret = HANDLE_REF(head); + POP_HANDLE(t); + POP_HANDLE(t); + return ret; +} + +static void* run_one_test(struct thread *t) { + size_t unit_size = gc_finalizer_size() + sizeof(Pair); + size_t list_length = heap_size / nthreads / heap_multiplier / unit_size; + ssize_t outstanding = list_length; + + printf("Allocating list %zu nodes long. Total size %.3fGB.\n", + list_length, list_length * unit_size / 1e9); + + unsigned long thread_start = current_time(); + + PairHandle chain = { NULL }; + PUSH_HANDLE(t, chain); + + HANDLE_SET(chain, make_finalizer_chain(t, list_length)); + cause_gc(t->mut); + + size_t finalized = 0; + for (struct gc_finalizer *f = gc_pop_finalizable(t->mut); + f; + f = gc_pop_finalizable(t->mut)) { + Pair* p = gc_ref_heap_object(gc_finalizer_object(f)); + SmallObject* o = gc_ref_heap_object(gc_finalizer_closure(f)); + CHECK_EQ(p->car, o); + finalized++; + } + printf("thread %p: GC before clear finalized %zu nodes.\n", t, finalized); + outstanding -= finalized; + + HANDLE_SET(chain, NULL); + cause_gc(t->mut); + + finalized = 0; + for (struct gc_finalizer *f = gc_pop_finalizable(t->mut); + f; + f = gc_pop_finalizable(t->mut)) { + Pair* p = gc_ref_heap_object(gc_finalizer_object(f)); + SmallObject* o = gc_ref_heap_object(gc_finalizer_closure(f)); + CHECK_EQ(p->car, o); + finalized++; + } + printf("thread %p: GC after clear finalized %zu nodes.\n", t, finalized); + outstanding -= finalized; + + print_elapsed("thread", thread_start); + + POP_HANDLE(t); + + return (void*)outstanding; +} + +static void* run_one_test_in_thread(void *arg) { + struct gc_heap *heap = arg; + return call_with_gc(run_one_test, heap); +} + +struct join_data { int status; pthread_t thread; }; +static void *join_thread(void *data) { + struct join_data *join_data = data; + void *ret; + join_data->status = pthread_join(join_data->thread, &ret); + return ret; +} + +#define MAX_THREAD_COUNT 256 + +int main(int argc, char *argv[]) { + if (argc < 4 || 5 < argc) { + fprintf(stderr, "usage: %s HEAP_SIZE MULTIPLIER NTHREADS [GC-OPTIONS]\n", argv[0]); + return 1; + } + + heap_size = atof(argv[1]); + heap_multiplier = atof(argv[2]); + nthreads = atol(argv[3]); + + if (heap_size < 8192) { + fprintf(stderr, + "Heap size should probably be at least 8192, right? '%s'\n", + argv[1]); + return 1; + } + if (!(1.0 < heap_multiplier && heap_multiplier < 100)) { + fprintf(stderr, "Failed to parse heap multiplier '%s'\n", argv[2]); + return 1; + } + if (nthreads < 1 || nthreads > MAX_THREAD_COUNT) { + fprintf(stderr, "Expected integer between 1 and %d for thread count, got '%s'\n", + (int)MAX_THREAD_COUNT, argv[2]); + return 1; + } + + printf("Allocating heap of %.3fGB (%.2f multiplier of live data).\n", + heap_size / 1e9, heap_multiplier); + + struct gc_options *options = gc_allocate_options(); + gc_options_set_int(options, GC_OPTION_HEAP_SIZE_POLICY, GC_HEAP_SIZE_FIXED); + gc_options_set_size(options, GC_OPTION_HEAP_SIZE, heap_size); + if (argc == 5) { + if (!gc_options_parse_and_set_many(options, argv[4])) { + fprintf(stderr, "Failed to set GC options: '%s'\n", argv[4]); + return 1; + } + } + + struct gc_heap *heap; + struct gc_mutator *mut; + struct gc_basic_stats stats; + if (!gc_init(options, NULL, &heap, &mut, GC_BASIC_STATS, &stats)) { + fprintf(stderr, "Failed to initialize GC with heap size %zu bytes\n", + (size_t)heap_size); + return 1; + } + struct thread main_thread = { mut, }; + gc_mutator_set_roots(mut, &main_thread.roots); + + pthread_t threads[MAX_THREAD_COUNT]; + // Run one of the threads in the main thread. + for (size_t i = 1; i < nthreads; i++) { + int status = pthread_create(&threads[i], NULL, run_one_test_in_thread, heap); + if (status) { + errno = status; + perror("Failed to create thread"); + return 1; + } + } + ssize_t outstanding = (size_t)run_one_test(&main_thread); + for (size_t i = 1; i < nthreads; i++) { + struct join_data data = { 0, threads[i] }; + void *ret = gc_call_without_gc(mut, join_thread, &data); + if (data.status) { + errno = data.status; + perror("Failed to join thread"); + return 1; + } + ssize_t thread_outstanding = (ssize_t)ret; + outstanding += thread_outstanding; + } + + if (outstanding) + printf("\n\nWARNING: %zd nodes outstanding!!!\n\n", outstanding); + + gc_basic_stats_finish(&stats); + fputs("\n", stdout); + gc_basic_stats_print(&stats, stdout); + + return 0; +} + diff --git a/src/bdw.c b/src/bdw.c index e9b7cb9f5..8eae3b4ee 100644 --- a/src/bdw.c +++ b/src/bdw.c @@ -219,14 +219,14 @@ void gc_finalizer_attach(struct gc_mutator *mut, struct gc_finalizer *finalizer, void *prev_data = NULL; gc_finalizer_init_internal(finalizer, object, closure); gc_finalizer_externally_activated(finalizer); - GC_REGISTER_FINALIZER_NO_ORDER (gc_ref_heap_object(object), finalize_object, - finalizer, &prev, &prev_data); + GC_REGISTER_FINALIZER_NO_ORDER(gc_ref_heap_object(object), finalize_object, + finalizer, &prev, &prev_data); // FIXME: Allow multiple finalizers per object. GC_ASSERT(prev == NULL); GC_ASSERT(prev_data == NULL); } -struct gc_finalizer* gc_finalizer_pop(struct gc_mutator *mut) { +struct gc_finalizer* gc_pop_finalizable(struct gc_mutator *mut) { GC_invoke_finalizers(); return gc_finalizer_state_pop(mut->heap->finalizer_state); } diff --git a/src/pcc.c b/src/pcc.c index fa7342e4d..abee8ce49 100644 --- a/src/pcc.c +++ b/src/pcc.c @@ -592,7 +592,7 @@ static void request_mutators_to_stop(struct gc_heap *heap) { static void allow_mutators_to_continue(struct gc_heap *heap) { GC_ASSERT(mutators_are_stopping(heap)); GC_ASSERT(all_mutators_stopped(heap)); - heap->paused_mutator_count = 0; + heap->paused_mutator_count--; atomic_store_explicit(&heap->collecting, 0, memory_order_relaxed); GC_ASSERT(!mutators_are_stopping(heap)); pthread_cond_broadcast(&heap->mutator_cond); @@ -683,6 +683,7 @@ pause_mutator_for_collection(struct gc_heap *heap, struct gc_mutator *mut) { do { pthread_cond_wait(&heap->mutator_cond, &heap->lock); } while (mutators_are_stopping(heap)); + heap->paused_mutator_count--; MUTATOR_EVENT(mut, mutator_restarted); } @@ -929,7 +930,7 @@ void gc_finalizer_attach(struct gc_mutator *mut, struct gc_finalizer *finalizer, // No write barrier. } -struct gc_finalizer* gc_finalizer_pop(struct gc_mutator *mut) { +struct gc_finalizer* gc_pop_finalizable(struct gc_mutator *mut) { return gc_finalizer_state_pop(mutator_heap(mut)->finalizer_state); } diff --git a/src/semi.c b/src/semi.c index 739a21d75..0ed954727 100644 --- a/src/semi.c +++ b/src/semi.c @@ -526,7 +526,7 @@ void gc_finalizer_attach(struct gc_mutator *mut, struct gc_finalizer *finalizer, // No write barrier. } -struct gc_finalizer* gc_finalizer_pop(struct gc_mutator *mut) { +struct gc_finalizer* gc_pop_finalizable(struct gc_mutator *mut) { return gc_finalizer_state_pop(mutator_heap(mut)->finalizer_state); } diff --git a/src/whippet.c b/src/whippet.c index 3771babde..1f3edda2b 100644 --- a/src/whippet.c +++ b/src/whippet.c @@ -2380,7 +2380,7 @@ void gc_finalizer_attach(struct gc_mutator *mut, struct gc_finalizer *finalizer, // No write barrier. } -struct gc_finalizer* gc_finalizer_pop(struct gc_mutator *mut) { +struct gc_finalizer* gc_pop_finalizable(struct gc_mutator *mut) { return gc_finalizer_state_pop(mutator_heap(mut)->finalizer_state); }