mirror of
https://git.savannah.gnu.org/git/guile.git
synced 2025-06-08 21:20:19 +02:00
Factor out adapative heap sizer background thread to own file
This will let us piggy-back on the thread to asynchronously release memory to the OS.
This commit is contained in:
parent
2818958c59
commit
d785f082b1
5 changed files with 179 additions and 56 deletions
|
@ -7,6 +7,7 @@
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
#include "assert.h"
|
#include "assert.h"
|
||||||
|
#include "background-thread.h"
|
||||||
#include "debug.h"
|
#include "debug.h"
|
||||||
#include "heap-sizer.h"
|
#include "heap-sizer.h"
|
||||||
#include "gc-platform.h"
|
#include "gc-platform.h"
|
||||||
|
@ -37,10 +38,10 @@ struct gc_adaptive_heap_sizer {
|
||||||
double maximum_multiplier;
|
double maximum_multiplier;
|
||||||
double minimum_free_space;
|
double minimum_free_space;
|
||||||
double expansiveness;
|
double expansiveness;
|
||||||
int stopping;
|
|
||||||
pthread_t thread;
|
|
||||||
pthread_mutex_t lock;
|
pthread_mutex_t lock;
|
||||||
pthread_cond_t cond;
|
int background_task_id;
|
||||||
|
uint64_t last_bytes_allocated;
|
||||||
|
uint64_t last_heartbeat;
|
||||||
};
|
};
|
||||||
|
|
||||||
// With lock
|
// With lock
|
||||||
|
@ -86,46 +87,31 @@ gc_adaptive_heap_sizer_on_gc(struct gc_adaptive_heap_sizer *sizer,
|
||||||
pthread_mutex_unlock(&sizer->lock);
|
pthread_mutex_unlock(&sizer->lock);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void*
|
static void
|
||||||
gc_adaptive_heap_sizer_thread(void *data) {
|
gc_adaptive_heap_sizer_background_task(void *data) {
|
||||||
struct gc_adaptive_heap_sizer *sizer = data;
|
struct gc_adaptive_heap_sizer *sizer = data;
|
||||||
uint64_t last_bytes_allocated =
|
|
||||||
sizer->get_allocation_counter(sizer->callback_data);
|
|
||||||
uint64_t last_heartbeat = gc_platform_monotonic_nanoseconds();
|
|
||||||
pthread_mutex_lock(&sizer->lock);
|
|
||||||
while (!sizer->stopping) {
|
|
||||||
{
|
|
||||||
struct timespec ts;
|
|
||||||
if (clock_gettime(CLOCK_REALTIME, &ts)) {
|
|
||||||
perror("adaptive heap sizer thread: failed to get time!");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
ts.tv_sec += 1;
|
|
||||||
pthread_cond_timedwait(&sizer->cond, &sizer->lock, &ts);
|
|
||||||
}
|
|
||||||
uint64_t bytes_allocated =
|
uint64_t bytes_allocated =
|
||||||
sizer->get_allocation_counter(sizer->callback_data);
|
sizer->get_allocation_counter(sizer->callback_data);
|
||||||
uint64_t heartbeat = gc_platform_monotonic_nanoseconds();
|
uint64_t heartbeat = gc_platform_monotonic_nanoseconds();
|
||||||
double rate = (double) (bytes_allocated - last_bytes_allocated) /
|
double rate = (double) (bytes_allocated - sizer->last_bytes_allocated) /
|
||||||
(double) (heartbeat - last_heartbeat);
|
(double) (heartbeat - sizer->last_heartbeat);
|
||||||
// Just smooth the rate, under the assumption that the denominator is almost
|
// Just smooth the rate, under the assumption that the denominator is almost
|
||||||
// always 1.
|
// always 1.
|
||||||
sizer->smoothed_allocation_rate *= 1.0 - sizer->allocation_smoothing_factor;
|
sizer->smoothed_allocation_rate *= 1.0 - sizer->allocation_smoothing_factor;
|
||||||
sizer->smoothed_allocation_rate += rate * sizer->allocation_smoothing_factor;
|
sizer->smoothed_allocation_rate += rate * sizer->allocation_smoothing_factor;
|
||||||
last_heartbeat = heartbeat;
|
sizer->last_heartbeat = heartbeat;
|
||||||
last_bytes_allocated = bytes_allocated;
|
sizer->last_bytes_allocated = bytes_allocated;
|
||||||
sizer->set_heap_size(gc_adaptive_heap_sizer_calculate_size(sizer),
|
sizer->set_heap_size(gc_adaptive_heap_sizer_calculate_size(sizer),
|
||||||
sizer->callback_data);
|
sizer->callback_data);
|
||||||
}
|
|
||||||
pthread_mutex_unlock(&sizer->lock);
|
pthread_mutex_unlock(&sizer->lock);
|
||||||
return NULL;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static struct gc_adaptive_heap_sizer*
|
static struct gc_adaptive_heap_sizer*
|
||||||
gc_make_adaptive_heap_sizer(double expansiveness,
|
gc_make_adaptive_heap_sizer(double expansiveness,
|
||||||
uint64_t (*get_allocation_counter)(void *),
|
uint64_t (*get_allocation_counter)(void *),
|
||||||
void (*set_heap_size)(size_t , void *),
|
void (*set_heap_size)(size_t , void *),
|
||||||
void *callback_data) {
|
void *callback_data,
|
||||||
|
struct gc_background_thread *thread) {
|
||||||
struct gc_adaptive_heap_sizer *sizer;
|
struct gc_adaptive_heap_sizer *sizer;
|
||||||
sizer = malloc(sizeof(*sizer));
|
sizer = malloc(sizeof(*sizer));
|
||||||
if (!sizer)
|
if (!sizer)
|
||||||
|
@ -149,25 +135,15 @@ gc_make_adaptive_heap_sizer(double expansiveness,
|
||||||
sizer->maximum_multiplier = 5;
|
sizer->maximum_multiplier = 5;
|
||||||
sizer->minimum_free_space = 4 * 1024 * 1024;
|
sizer->minimum_free_space = 4 * 1024 * 1024;
|
||||||
sizer->expansiveness = expansiveness;
|
sizer->expansiveness = expansiveness;
|
||||||
pthread_mutex_init(&sizer->lock, NULL);
|
pthread_mutex_init(&thread->lock, NULL);
|
||||||
pthread_cond_init(&sizer->cond, NULL);
|
sizer->last_bytes_allocated = get_allocation_counter(callback_data);
|
||||||
if (pthread_create(&sizer->thread, NULL, gc_adaptive_heap_sizer_thread,
|
sizer->last_heartbeat = gc_platform_monotonic_nanoseconds();
|
||||||
sizer)) {
|
sizer->background_task_id = thread
|
||||||
perror("spawning adaptive heap size thread failed");
|
? gc_background_thread_add_task(thread, GC_BACKGROUND_TASK_FIRST,
|
||||||
GC_CRASH();
|
gc_adaptive_heap_sizer_background_task,
|
||||||
}
|
sizer)
|
||||||
|
: -1;
|
||||||
return sizer;
|
return sizer;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
|
||||||
gc_destroy_adaptive_heap_sizer(struct gc_adaptive_heap_sizer *sizer) {
|
|
||||||
pthread_mutex_lock(&sizer->lock);
|
|
||||||
GC_ASSERT(!sizer->stopping);
|
|
||||||
sizer->stopping = 1;
|
|
||||||
pthread_mutex_unlock(&sizer->lock);
|
|
||||||
pthread_cond_signal(&sizer->cond);
|
|
||||||
pthread_join(sizer->thread, NULL);
|
|
||||||
free(sizer);
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif // ADAPTIVE_HEAP_SIZER_H
|
#endif // ADAPTIVE_HEAP_SIZER_H
|
||||||
|
|
138
src/background-thread.h
Normal file
138
src/background-thread.h
Normal file
|
@ -0,0 +1,138 @@
|
||||||
|
#ifndef BACKGROUND_THREAD_H
|
||||||
|
#define BACKGROUND_THREAD_H
|
||||||
|
|
||||||
|
#include <pthread.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include "assert.h"
|
||||||
|
#include "debug.h"
|
||||||
|
|
||||||
|
enum {
|
||||||
|
GC_BACKGROUND_TASK_FIRST = 0,
|
||||||
|
GC_BACKGROUND_TASK_NORMAL = 100,
|
||||||
|
GC_BACKGROUND_TASK_LAST = 200
|
||||||
|
};
|
||||||
|
|
||||||
|
struct gc_background_task {
|
||||||
|
int id;
|
||||||
|
int priority;
|
||||||
|
void (*run)(void *data);
|
||||||
|
void *data;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct gc_background_thread {
|
||||||
|
size_t count;
|
||||||
|
size_t capacity;
|
||||||
|
struct gc_background_task *tasks;
|
||||||
|
int next_id;
|
||||||
|
int stopping;
|
||||||
|
pthread_t thread;
|
||||||
|
pthread_mutex_t lock;
|
||||||
|
pthread_cond_t cond;
|
||||||
|
};
|
||||||
|
|
||||||
|
static void*
|
||||||
|
gc_background_thread(void *data) {
|
||||||
|
struct gc_background_thread *thread = data;
|
||||||
|
struct timespec ts;
|
||||||
|
if (clock_gettime(CLOCK_REALTIME, &ts)) {
|
||||||
|
perror("background thread: failed to get time!");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
pthread_mutex_lock(&thread->lock);
|
||||||
|
while (!thread->stopping) {
|
||||||
|
ts.tv_sec += 1;
|
||||||
|
pthread_cond_timedwait(&thread->cond, &thread->lock, &ts);
|
||||||
|
if (thread->stopping)
|
||||||
|
break;
|
||||||
|
for (size_t i = 0; i < thread->count; i++)
|
||||||
|
thread->tasks[i].run(thread->tasks[i].data);
|
||||||
|
}
|
||||||
|
pthread_mutex_unlock(&thread->lock);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct gc_background_thread*
|
||||||
|
gc_make_background_thread(void) {
|
||||||
|
struct gc_background_thread *thread;
|
||||||
|
thread = malloc(sizeof(*thread));
|
||||||
|
if (!thread)
|
||||||
|
GC_CRASH();
|
||||||
|
memset(thread, 0, sizeof(*thread));
|
||||||
|
thread->tasks = NULL;
|
||||||
|
thread->count = 0;
|
||||||
|
thread->capacity = 0;
|
||||||
|
pthread_mutex_init(&thread->lock, NULL);
|
||||||
|
pthread_cond_init(&thread->cond, NULL);
|
||||||
|
if (pthread_create(&thread->thread, NULL, gc_background_thread, thread)) {
|
||||||
|
perror("spawning background thread failed");
|
||||||
|
GC_CRASH();
|
||||||
|
}
|
||||||
|
return thread;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
gc_background_thread_add_task(struct gc_background_thread *thread,
|
||||||
|
int priority, void (*run)(void *data),
|
||||||
|
void *data) {
|
||||||
|
pthread_mutex_lock(&thread->lock);
|
||||||
|
if (thread->count == thread->capacity) {
|
||||||
|
size_t new_capacity = thread->capacity * 2 + 1;
|
||||||
|
struct gc_background_task *new_tasks =
|
||||||
|
realloc(thread->tasks, sizeof(struct gc_background_task) * new_capacity);
|
||||||
|
if (!new_tasks) {
|
||||||
|
perror("ran out of space for background tasks!");
|
||||||
|
GC_CRASH();
|
||||||
|
}
|
||||||
|
thread->capacity = new_capacity;
|
||||||
|
thread->tasks = new_tasks;
|
||||||
|
}
|
||||||
|
size_t insert = 0;
|
||||||
|
for (; insert < thread->count; insert++) {
|
||||||
|
if (priority < thread->tasks[insert].priority)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
size_t bytes_to_move =
|
||||||
|
(thread->count - insert) * sizeof(struct gc_background_task);
|
||||||
|
memmove(&thread->tasks[insert + 1], &thread->tasks[insert], bytes_to_move);
|
||||||
|
int id = thread->next_id++;
|
||||||
|
thread->tasks[insert].id = id;
|
||||||
|
thread->tasks[insert].priority = priority;
|
||||||
|
thread->tasks[insert].run = run;
|
||||||
|
thread->tasks[insert].data = data;
|
||||||
|
thread->count++;
|
||||||
|
pthread_mutex_unlock(&thread->lock);
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
gc_background_thread_remove_task(struct gc_background_thread *thread,
|
||||||
|
int id) {
|
||||||
|
pthread_mutex_lock(&thread->lock);
|
||||||
|
size_t remove = 0;
|
||||||
|
for (; remove < thread->count; remove++) {
|
||||||
|
if (thread->tasks[remove].id == id)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (remove == thread->count)
|
||||||
|
GC_CRASH();
|
||||||
|
size_t bytes_to_move =
|
||||||
|
(thread->count - (remove + 1)) * sizeof(struct gc_background_task);
|
||||||
|
memmove(&thread->tasks[remove], &thread->tasks[remove + 1], bytes_to_move);
|
||||||
|
pthread_mutex_unlock(&thread->lock);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
gc_destroy_background_thread(struct gc_background_thread *thread) {
|
||||||
|
pthread_mutex_lock(&thread->lock);
|
||||||
|
GC_ASSERT(!thread->stopping);
|
||||||
|
thread->stopping = 1;
|
||||||
|
pthread_mutex_unlock(&thread->lock);
|
||||||
|
pthread_cond_signal(&thread->cond);
|
||||||
|
pthread_join(thread->thread, NULL);
|
||||||
|
free(thread->tasks);
|
||||||
|
free(thread);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // BACKGROUND_THREAD_H
|
|
@ -20,7 +20,8 @@ gc_make_heap_sizer(struct gc_heap *heap,
|
||||||
const struct gc_common_options *options,
|
const struct gc_common_options *options,
|
||||||
uint64_t (*get_allocation_counter_from_thread)(void*),
|
uint64_t (*get_allocation_counter_from_thread)(void*),
|
||||||
void (*set_heap_size_from_thread)(size_t, void*),
|
void (*set_heap_size_from_thread)(size_t, void*),
|
||||||
void *data) {
|
void *data,
|
||||||
|
struct gc_background_thread *thread) {
|
||||||
struct gc_heap_sizer ret = { options->heap_size_policy, };
|
struct gc_heap_sizer ret = { options->heap_size_policy, };
|
||||||
switch (options->heap_size_policy) {
|
switch (options->heap_size_policy) {
|
||||||
case GC_HEAP_SIZE_FIXED:
|
case GC_HEAP_SIZE_FIXED:
|
||||||
|
@ -35,7 +36,7 @@ gc_make_heap_sizer(struct gc_heap *heap,
|
||||||
gc_make_adaptive_heap_sizer (options->heap_expansiveness,
|
gc_make_adaptive_heap_sizer (options->heap_expansiveness,
|
||||||
get_allocation_counter_from_thread,
|
get_allocation_counter_from_thread,
|
||||||
set_heap_size_from_thread,
|
set_heap_size_from_thread,
|
||||||
heap);
|
heap, thread);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
#define GC_IMPL 1
|
#define GC_IMPL 1
|
||||||
#include "gc-internal.h"
|
#include "gc-internal.h"
|
||||||
|
|
||||||
|
#include "background-thread.h"
|
||||||
#include "debug.h"
|
#include "debug.h"
|
||||||
#include "gc-align.h"
|
#include "gc-align.h"
|
||||||
#include "gc-inline.h"
|
#include "gc-inline.h"
|
||||||
|
@ -58,6 +59,7 @@ struct gc_heap {
|
||||||
double minimum_major_gc_yield_threshold;
|
double minimum_major_gc_yield_threshold;
|
||||||
double pending_ephemerons_size_factor;
|
double pending_ephemerons_size_factor;
|
||||||
double pending_ephemerons_size_slop;
|
double pending_ephemerons_size_slop;
|
||||||
|
struct gc_background_thread *background_thread;
|
||||||
struct gc_heap_sizer sizer;
|
struct gc_heap_sizer sizer;
|
||||||
struct gc_event_listener event_listener;
|
struct gc_event_listener event_listener;
|
||||||
void *event_listener_data;
|
void *event_listener_data;
|
||||||
|
@ -1071,10 +1073,12 @@ gc_init(const struct gc_options *options, struct gc_stack_addr *stack_base,
|
||||||
if (!large_object_space_init(heap_large_object_space(*heap), *heap))
|
if (!large_object_space_init(heap_large_object_space(*heap), *heap))
|
||||||
GC_CRASH();
|
GC_CRASH();
|
||||||
|
|
||||||
|
(*heap)->background_thread = gc_make_background_thread();
|
||||||
(*heap)->sizer = gc_make_heap_sizer(*heap, &options->common,
|
(*heap)->sizer = gc_make_heap_sizer(*heap, &options->common,
|
||||||
allocation_counter_from_thread,
|
allocation_counter_from_thread,
|
||||||
set_heap_size_from_thread,
|
set_heap_size_from_thread,
|
||||||
(*heap));
|
(*heap),
|
||||||
|
(*heap)->background_thread);
|
||||||
|
|
||||||
*mut = calloc(1, sizeof(struct gc_mutator));
|
*mut = calloc(1, sizeof(struct gc_mutator));
|
||||||
if (!*mut) GC_CRASH();
|
if (!*mut) GC_CRASH();
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
#define GC_IMPL 1
|
#define GC_IMPL 1
|
||||||
#include "gc-internal.h"
|
#include "gc-internal.h"
|
||||||
|
|
||||||
|
#include "background-thread.h"
|
||||||
#include "copy-space.h"
|
#include "copy-space.h"
|
||||||
#include "debug.h"
|
#include "debug.h"
|
||||||
#include "gc-align.h"
|
#include "gc-align.h"
|
||||||
|
@ -48,6 +49,7 @@ struct gc_heap {
|
||||||
struct gc_tracer tracer;
|
struct gc_tracer tracer;
|
||||||
double pending_ephemerons_size_factor;
|
double pending_ephemerons_size_factor;
|
||||||
double pending_ephemerons_size_slop;
|
double pending_ephemerons_size_slop;
|
||||||
|
struct gc_background_thread *background_thread;
|
||||||
struct gc_heap_sizer sizer;
|
struct gc_heap_sizer sizer;
|
||||||
struct gc_event_listener event_listener;
|
struct gc_event_listener event_listener;
|
||||||
void *event_listener_data;
|
void *event_listener_data;
|
||||||
|
@ -658,10 +660,12 @@ int gc_init(const struct gc_options *options, struct gc_stack_addr *stack_base,
|
||||||
if (!large_object_space_init(heap_large_object_space(*heap), *heap))
|
if (!large_object_space_init(heap_large_object_space(*heap), *heap))
|
||||||
GC_CRASH();
|
GC_CRASH();
|
||||||
|
|
||||||
|
(*heap)->background_thread = gc_make_background_thread();
|
||||||
(*heap)->sizer = gc_make_heap_sizer(*heap, &options->common,
|
(*heap)->sizer = gc_make_heap_sizer(*heap, &options->common,
|
||||||
allocation_counter_from_thread,
|
allocation_counter_from_thread,
|
||||||
set_heap_size_from_thread,
|
set_heap_size_from_thread,
|
||||||
(*heap));
|
(*heap),
|
||||||
|
(*heap)->background_thread);
|
||||||
|
|
||||||
*mut = calloc(1, sizeof(struct gc_mutator));
|
*mut = calloc(1, sizeof(struct gc_mutator));
|
||||||
if (!*mut) GC_CRASH();
|
if (!*mut) GC_CRASH();
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue