summaryrefslogtreecommitdiff
path: root/libguile/whippet/benchmarks/finalizers.c
diff options
context:
space:
mode:
Diffstat (limited to 'libguile/whippet/benchmarks/finalizers.c')
-rw-r--r--libguile/whippet/benchmarks/finalizers.c284
1 files changed, 284 insertions, 0 deletions
diff --git a/libguile/whippet/benchmarks/finalizers.c b/libguile/whippet/benchmarks/finalizers.c
new file mode 100644
index 000000000..537307118
--- /dev/null
+++ b/libguile/whippet/benchmarks/finalizers.c
@@ -0,0 +1,284 @@
+#include <errno.h>
+#include <pthread.h>
+#include <stdio.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <sys/time.h>
+
+#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 inline void set_car(struct gc_mutator *mut, Pair *obj, void *val) {
+ void **field = &obj->car;
+ if (val)
+ gc_write_barrier(mut, gc_ref_from_heap_object(obj), sizeof(Pair),
+ gc_edge(field),
+ gc_ref_from_heap_object(val));
+ *field = val;
+}
+
+static inline void set_cdr(struct gc_mutator *mut, Pair *obj, void *val) {
+ void **field = &obj->cdr;
+ if (val)
+ gc_write_barrier(mut, gc_ref_from_heap_object(obj), sizeof(Pair),
+ gc_edge(field),
+ gc_ref_from_heap_object(val));
+ field = val;
+}
+
+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));
+ set_car(t->mut, HANDLE_REF(head), allocate_small_object(t->mut));
+ set_cdr(t->mut, HANDLE_REF(head), 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;
+}
+