mirror of
https://git.savannah.gnu.org/git/guile.git
synced 2025-05-09 15:10:29 +02:00
Users will want to set options from an environment variable or something like that. Particular GC implementations will want to expose an expanded set of options. For these reasons we make the options interface a bit more generalized and include parsing.
272 lines
8.2 KiB
C
272 lines
8.2 KiB
C
#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-ephemeron.h"
|
|
#include "simple-roots-api.h"
|
|
#include "ephemerons-types.h"
|
|
#include "simple-allocator.h"
|
|
|
|
typedef HANDLE_TO(SmallObject) SmallObjectHandle;
|
|
typedef HANDLE_TO(struct gc_ephemeron) EphemeronHandle;
|
|
typedef HANDLE_TO(Box) BoxHandle;
|
|
|
|
static SmallObject* allocate_small_object(struct gc_mutator *mut) {
|
|
return gc_allocate_with_kind(mut, ALLOC_KIND_SMALL_OBJECT, sizeof(SmallObject));
|
|
}
|
|
|
|
static Box* allocate_box(struct gc_mutator *mut) {
|
|
return gc_allocate_with_kind(mut, ALLOC_KIND_BOX, sizeof(Box));
|
|
}
|
|
|
|
static struct gc_ephemeron* allocate_ephemeron(struct gc_mutator *mut) {
|
|
struct gc_ref ret = gc_allocate_ephemeron(mut);
|
|
*tag_word(ret) = tag_live(ALLOC_KIND_EPHEMERON);
|
|
return gc_ref_heap_object(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 size_t ephemeron_chain_length(struct gc_ephemeron **loc,
|
|
SmallObject *key) {
|
|
struct gc_ephemeron *head = gc_ephemeron_chain_head(loc);
|
|
size_t len = 0;
|
|
while (head) {
|
|
CHECK_EQ(key, (SmallObject*)gc_ref_value(gc_ephemeron_key(head)));
|
|
Box *value = gc_ref_heap_object(gc_ephemeron_value(head));
|
|
CHECK_NOT_NULL(value);
|
|
key = value->obj;
|
|
CHECK_NOT_NULL(key);
|
|
head = gc_ephemeron_chain_next(head);
|
|
len++;
|
|
}
|
|
return len;
|
|
}
|
|
|
|
static double heap_size;
|
|
static double heap_multiplier;
|
|
static size_t nthreads;
|
|
|
|
static void cause_gc(struct gc_mutator *mut) {
|
|
gc_collect(mut);
|
|
}
|
|
|
|
static void make_ephemeron_chain(struct thread *t, EphemeronHandle *head,
|
|
SmallObjectHandle *head_key, size_t length) {
|
|
BoxHandle tail_box = { NULL };
|
|
PUSH_HANDLE(t, tail_box);
|
|
|
|
CHECK_NULL(HANDLE_REF(*head_key));
|
|
HANDLE_SET(*head_key, allocate_small_object(t->mut));
|
|
|
|
for (size_t i = 0; i < length; i++) {
|
|
HANDLE_SET(tail_box, allocate_box(t->mut));
|
|
HANDLE_REF(tail_box)->obj = HANDLE_REF(*head_key);
|
|
HANDLE_SET(*head_key, allocate_small_object(t->mut));
|
|
struct gc_ephemeron *ephemeron = allocate_ephemeron(t->mut);
|
|
gc_ephemeron_init(t->mut, ephemeron,
|
|
gc_ref_from_heap_object(HANDLE_REF(*head_key)),
|
|
gc_ref_from_heap_object(HANDLE_REF(tail_box)));
|
|
gc_ephemeron_chain_push(HANDLE_LOC(*head), ephemeron);
|
|
}
|
|
|
|
POP_HANDLE(t);
|
|
}
|
|
|
|
static void* run_one_test(struct thread *t) {
|
|
size_t unit_size = gc_ephemeron_size() + sizeof(Box);
|
|
size_t list_length = heap_size / nthreads / heap_multiplier / unit_size;
|
|
|
|
printf("Allocating ephemeron list %zu nodes long. Total size %.3fGB.\n",
|
|
list_length, list_length * unit_size / 1e9);
|
|
|
|
unsigned long thread_start = current_time();
|
|
|
|
SmallObjectHandle head_key = { NULL };
|
|
EphemeronHandle head = { NULL };
|
|
|
|
PUSH_HANDLE(t, head_key);
|
|
PUSH_HANDLE(t, head);
|
|
|
|
make_ephemeron_chain(t, &head, &head_key, list_length);
|
|
|
|
size_t measured_length = ephemeron_chain_length(HANDLE_LOC(head),
|
|
HANDLE_REF(head_key));
|
|
CHECK_EQ(measured_length, list_length);
|
|
|
|
cause_gc(t->mut);
|
|
measured_length = ephemeron_chain_length(HANDLE_LOC(head),
|
|
HANDLE_REF(head_key));
|
|
CHECK_EQ(measured_length, list_length);
|
|
|
|
if (!GC_CONSERVATIVE_ROOTS) {
|
|
HANDLE_SET(head_key, NULL);
|
|
cause_gc(t->mut);
|
|
measured_length = ephemeron_chain_length(HANDLE_LOC(head),
|
|
HANDLE_REF(head_key));
|
|
CHECK_EQ(measured_length, 0);
|
|
}
|
|
|
|
// swap head_key for a key halfway in, cause gc
|
|
// check length is expected half-length; warn, or error if precise
|
|
// clear and return
|
|
|
|
print_elapsed("thread", thread_start);
|
|
|
|
POP_HANDLE(t);
|
|
POP_HANDLE(t);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
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 != 5) {
|
|
fprintf(stderr, "usage: %s HEAP_SIZE MULTIPLIER NTHREADS PARALLELISM\n", argv[0]);
|
|
return 1;
|
|
}
|
|
|
|
heap_size = atof(argv[1]);
|
|
heap_multiplier = atof(argv[2]);
|
|
nthreads = atol(argv[3]);
|
|
size_t parallelism = atol(argv[4]);
|
|
|
|
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;
|
|
}
|
|
if (parallelism < 1 || parallelism > MAX_THREAD_COUNT) {
|
|
fprintf(stderr, "Expected integer between 1 and %d for parallelism, got '%s'\n",
|
|
(int)MAX_THREAD_COUNT, argv[3]);
|
|
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);
|
|
gc_options_set_int(options, GC_OPTION_PARALLELISM, parallelism);
|
|
|
|
struct gc_heap *heap;
|
|
struct gc_mutator *mut;
|
|
if (!gc_init(options, NULL, &heap, &mut)) {
|
|
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);
|
|
|
|
unsigned long test_start = current_time();
|
|
|
|
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;
|
|
}
|
|
}
|
|
run_one_test(&main_thread);
|
|
for (size_t i = 1; i < nthreads; i++) {
|
|
struct join_data data = { 0, threads[i] };
|
|
gc_call_without_gc(mut, join_thread, &data);
|
|
if (data.status) {
|
|
errno = data.status;
|
|
perror("Failed to join thread");
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
print_elapsed("test", test_start);
|
|
|
|
gc_print_stats(heap);
|
|
|
|
return 0;
|
|
}
|
|
|