wingo pushed a commit to branch wip-whippet
in repository guile.

commit d785f082b11c78de830bb223ae7c03a8a3452656
Author: Andy Wingo <wi...@igalia.com>
AuthorDate: Sun Sep 15 11:01:49 2024 +0200

    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.
---
 src/adaptive-heap-sizer.h |  80 ++++++++++-----------------
 src/background-thread.h   | 138 ++++++++++++++++++++++++++++++++++++++++++++++
 src/heap-sizer.h          |   5 +-
 src/mmc.c                 |   6 +-
 src/pcc.c                 |   6 +-
 5 files changed, 179 insertions(+), 56 deletions(-)

diff --git a/src/adaptive-heap-sizer.h b/src/adaptive-heap-sizer.h
index 796f07469..126a493b8 100644
--- a/src/adaptive-heap-sizer.h
+++ b/src/adaptive-heap-sizer.h
@@ -7,6 +7,7 @@
 #include <string.h>
 
 #include "assert.h"
+#include "background-thread.h"
 #include "debug.h"
 #include "heap-sizer.h"
 #include "gc-platform.h"
@@ -37,10 +38,10 @@ struct gc_adaptive_heap_sizer {
   double maximum_multiplier;
   double minimum_free_space;
   double expansiveness;
-  int stopping;
-  pthread_t thread;
   pthread_mutex_t lock;
-  pthread_cond_t cond;
+  int background_task_id;
+  uint64_t last_bytes_allocated;
+  uint64_t last_heartbeat;
 };
 
 // With lock
@@ -86,46 +87,31 @@ gc_adaptive_heap_sizer_on_gc(struct gc_adaptive_heap_sizer 
*sizer,
   pthread_mutex_unlock(&sizer->lock);
 }
 
-static void*
-gc_adaptive_heap_sizer_thread(void *data) {
+static void
+gc_adaptive_heap_sizer_background_task(void *data) {
   struct gc_adaptive_heap_sizer *sizer = data;
-  uint64_t last_bytes_allocated =
+  uint64_t 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 =
-      sizer->get_allocation_counter(sizer->callback_data);
-    uint64_t heartbeat = gc_platform_monotonic_nanoseconds();
-    double rate = (double) (bytes_allocated - last_bytes_allocated) /
-      (double) (heartbeat - last_heartbeat);
-    // Just smooth the rate, under the assumption that the denominator is 
almost
-    // always 1.
-    sizer->smoothed_allocation_rate *= 1.0 - 
sizer->allocation_smoothing_factor;
-    sizer->smoothed_allocation_rate += rate * 
sizer->allocation_smoothing_factor;
-    last_heartbeat = heartbeat;
-    last_bytes_allocated = bytes_allocated;
-    sizer->set_heap_size(gc_adaptive_heap_sizer_calculate_size(sizer),
-                         sizer->callback_data);
-  }
+  uint64_t heartbeat = gc_platform_monotonic_nanoseconds();
+  double rate = (double) (bytes_allocated - sizer->last_bytes_allocated) /
+    (double) (heartbeat - sizer->last_heartbeat);
+  // Just smooth the rate, under the assumption that the denominator is almost
+  // always 1.
+  sizer->smoothed_allocation_rate *= 1.0 - sizer->allocation_smoothing_factor;
+  sizer->smoothed_allocation_rate += rate * sizer->allocation_smoothing_factor;
+  sizer->last_heartbeat = heartbeat;
+  sizer->last_bytes_allocated = bytes_allocated;
+  sizer->set_heap_size(gc_adaptive_heap_sizer_calculate_size(sizer),
+                       sizer->callback_data);
   pthread_mutex_unlock(&sizer->lock);
-  return NULL;
 }
 
 static struct gc_adaptive_heap_sizer*
 gc_make_adaptive_heap_sizer(double expansiveness,
                             uint64_t (*get_allocation_counter)(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;
   sizer = malloc(sizeof(*sizer));
   if (!sizer)
@@ -149,25 +135,15 @@ gc_make_adaptive_heap_sizer(double expansiveness,
   sizer->maximum_multiplier = 5;
   sizer->minimum_free_space = 4 * 1024 * 1024;
   sizer->expansiveness = expansiveness;
-  pthread_mutex_init(&sizer->lock, NULL);
-  pthread_cond_init(&sizer->cond, NULL);
-  if (pthread_create(&sizer->thread, NULL, gc_adaptive_heap_sizer_thread,
-                     sizer)) {
-    perror("spawning adaptive heap size thread failed");
-    GC_CRASH();
-  }
+  pthread_mutex_init(&thread->lock, NULL);
+  sizer->last_bytes_allocated = get_allocation_counter(callback_data);
+  sizer->last_heartbeat = gc_platform_monotonic_nanoseconds();
+  sizer->background_task_id = thread
+    ? gc_background_thread_add_task(thread, GC_BACKGROUND_TASK_FIRST,
+                                    gc_adaptive_heap_sizer_background_task,
+                                    sizer)
+    : -1;
   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
diff --git a/src/background-thread.h b/src/background-thread.h
new file mode 100644
index 000000000..ee858ac58
--- /dev/null
+++ b/src/background-thread.h
@@ -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
diff --git a/src/heap-sizer.h b/src/heap-sizer.h
index dc6f3d2ef..eb038cca9 100644
--- a/src/heap-sizer.h
+++ b/src/heap-sizer.h
@@ -20,7 +20,8 @@ gc_make_heap_sizer(struct gc_heap *heap,
                    const struct gc_common_options *options,
                    uint64_t (*get_allocation_counter_from_thread)(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, };
   switch (options->heap_size_policy) {
     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,
                                      get_allocation_counter_from_thread,
                                      set_heap_size_from_thread,
-                                     heap);
+                                     heap, thread);
       break;
 
     default:
diff --git a/src/mmc.c b/src/mmc.c
index 34a96e409..061d3b80f 100644
--- a/src/mmc.c
+++ b/src/mmc.c
@@ -9,6 +9,7 @@
 #define GC_IMPL 1
 #include "gc-internal.h"
 
+#include "background-thread.h"
 #include "debug.h"
 #include "gc-align.h"
 #include "gc-inline.h"
@@ -58,6 +59,7 @@ struct gc_heap {
   double minimum_major_gc_yield_threshold;
   double pending_ephemerons_size_factor;
   double pending_ephemerons_size_slop;
+  struct gc_background_thread *background_thread;
   struct gc_heap_sizer sizer;
   struct gc_event_listener event_listener;
   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))
     GC_CRASH();
 
+  (*heap)->background_thread = gc_make_background_thread();
   (*heap)->sizer = gc_make_heap_sizer(*heap, &options->common,
                                       allocation_counter_from_thread,
                                       set_heap_size_from_thread,
-                                      (*heap));
+                                      (*heap),
+                                      (*heap)->background_thread);
 
   *mut = calloc(1, sizeof(struct gc_mutator));
   if (!*mut) GC_CRASH();
diff --git a/src/pcc.c b/src/pcc.c
index 40ba60f8b..2cd919e50 100644
--- a/src/pcc.c
+++ b/src/pcc.c
@@ -9,6 +9,7 @@
 #define GC_IMPL 1
 #include "gc-internal.h"
 
+#include "background-thread.h"
 #include "copy-space.h"
 #include "debug.h"
 #include "gc-align.h"
@@ -48,6 +49,7 @@ struct gc_heap {
   struct gc_tracer tracer;
   double pending_ephemerons_size_factor;
   double pending_ephemerons_size_slop;
+  struct gc_background_thread *background_thread;
   struct gc_heap_sizer sizer;
   struct gc_event_listener event_listener;
   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))
     GC_CRASH();
 
+  (*heap)->background_thread = gc_make_background_thread();
   (*heap)->sizer = gc_make_heap_sizer(*heap, &options->common,
                                       allocation_counter_from_thread,
                                       set_heap_size_from_thread,
-                                      (*heap));
+                                      (*heap),
+                                      (*heap)->background_thread);
 
   *mut = calloc(1, sizeof(struct gc_mutator));
   if (!*mut) GC_CRASH();

Reply via email to