From 9befc6df097cc3c8d85ad4f6805fd6c9d96802a9 Mon Sep 17 00:00:00 2001 From: Steven Eker Date: Tue, 10 Dec 2024 03:25:15 +0100 Subject: [PATCH 1/6] arena_start_ptr() -> start_ptr(); arena_end_ptr() -> end_ptr() --- include/runtime/arena.h | 6 +++--- runtime/collect/collect.cpp | 12 ++++++------ runtime/lto/alloc.cpp | 6 +++--- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/include/runtime/arena.h b/include/runtime/arena.h index 826db686c..8792175b8 100644 --- a/include/runtime/arena.h +++ b/include/runtime/arena.h @@ -36,12 +36,12 @@ class arena { // Returns the address of the first byte that belongs in the given arena. // Returns nullptr if nothing has been allocated ever in that arena. - char *arena_start_ptr() const { return current_addr_ptr; } + char *start_ptr() const { return current_addr_ptr; } // Returns a pointer to a location holding the address of last allocated // byte in the given arena plus 1. // This address is nullptr if nothing has been allocated ever in that arena. - char *arena_end_ptr() { return allocation_ptr; } + char *end_ptr() { return allocation_ptr; } // Clears the current allocation space by setting its start back to its first // block. It is used during garbage collection to effectively collect all of the @@ -187,7 +187,7 @@ inline void arena::arena_clear() { // // We set the allocation pointer to the first available address. // - allocation_ptr = arena_start_ptr(); + allocation_ptr = current_addr_ptr; // // If the number of blocks we've touched is >= threshold, we want to trigger // a garbage collection if we get within 1 block of the end of this area. diff --git a/runtime/collect/collect.cpp b/runtime/collect/collect.cpp index f49123950..2b71616b6 100644 --- a/runtime/collect/collect.cpp +++ b/runtime/collect/collect.cpp @@ -255,7 +255,7 @@ char *arena::evacuate(char *scan_ptr) { migrate_child(curr_block, layout_data->args, i, false); } } - return move_ptr(scan_ptr, get_size(hdr, layout_int), arena_end_ptr()); + return move_ptr(scan_ptr, get_size(hdr, layout_int), end_ptr()); } // Contains the decision logic for collecting the old generation. @@ -295,7 +295,7 @@ void kore_collect( if (!last_alloc_ptr) { last_alloc_ptr = youngspace_ptr(); } - char *current_alloc_ptr = youngspace.arena_end_ptr(); + char *current_alloc_ptr = youngspace.end_ptr(); #endif kore_alloc_swap(collect_old); #ifdef GC_DBG @@ -303,13 +303,13 @@ void kore_collect( numBytesLiveAtCollection[i] = 0; } #endif - char *previous_oldspace_alloc_ptr = oldspace.arena_end_ptr(); + char *previous_oldspace_alloc_ptr = oldspace.end_ptr(); for (int i = 0; i < nroots; i++) { migrate_root(roots, type_info, i); } migrate_static_roots(); char *scan_ptr = youngspace_ptr(); - if (scan_ptr != youngspace.arena_end_ptr()) { + if (scan_ptr != youngspace.end_ptr()) { MEM_LOG("Evacuating young generation\n"); while (scan_ptr) { scan_ptr = youngspace.evacuate(scan_ptr); @@ -320,7 +320,7 @@ void kore_collect( } else { scan_ptr = previous_oldspace_alloc_ptr; } - if (scan_ptr != oldspace.arena_end_ptr()) { + if (scan_ptr != oldspace.end_ptr()) { MEM_LOG("Evacuating old generation\n"); while (scan_ptr) { scan_ptr = oldspace.evacuate(scan_ptr); @@ -331,7 +331,7 @@ void kore_collect( = arena::ptr_diff(current_alloc_ptr, last_alloc_ptr); assert(numBytesAllocedSinceLastCollection >= 0); fwrite(&numBytesAllocedSinceLastCollection, sizeof(ssize_t), 1, stderr); - last_alloc_ptr = youngspace.arena_end_ptr(); + last_alloc_ptr = youngspace.end_ptr(); fwrite( numBytesLiveAtCollection, sizeof(numBytesLiveAtCollection[0]), sizeof(numBytesLiveAtCollection) / sizeof(numBytesLiveAtCollection[0]), diff --git a/runtime/lto/alloc.cpp b/runtime/lto/alloc.cpp index aaafc932d..3d7f0663a 100644 --- a/runtime/lto/alloc.cpp +++ b/runtime/lto/alloc.cpp @@ -16,11 +16,11 @@ REGISTER_ARENA(oldspace, OLDSPACE_ID); REGISTER_ARENA(alwaysgcspace, ALWAYSGCSPACE_ID); char *youngspace_ptr() { - return youngspace.arena_start_ptr(); + return youngspace.start_ptr(); } char *oldspace_ptr() { - return oldspace.arena_start_ptr(); + return oldspace.start_ptr(); } char youngspace_collection_id() { @@ -73,7 +73,7 @@ kore_resize_last_alloc(void *oldptr, size_t newrequest, size_t last_size) { newrequest = (newrequest + 7) & ~7; last_size = (last_size + 7) & ~7; - if (oldptr != youngspace.arena_end_ptr() - last_size) { + if (oldptr != youngspace.end_ptr() - last_size) { MEM_LOG( "May only reallocate last allocation. Tried to reallocate %p to %zd\n", oldptr, newrequest); From 369d3bce7859cdaef323a820620ed3f513b6278d Mon Sep 17 00:00:00 2001 From: Steven Eker Date: Wed, 11 Dec 2024 02:23:48 +0100 Subject: [PATCH 2/6] use update_tripwire() to set tripwire after a collection --- include/runtime/arena.h | 37 ++++++++++++++++++++----------------- runtime/alloc/arena.cpp | 4 ++-- runtime/collect/collect.cpp | 1 + 3 files changed, 23 insertions(+), 19 deletions(-) diff --git a/include/runtime/arena.h b/include/runtime/arena.h index 8792175b8..1a4a29574 100644 --- a/include/runtime/arena.h +++ b/include/runtime/arena.h @@ -14,6 +14,16 @@ extern "C" { size_t const HYPERBLOCK_SIZE = (size_t)BLOCK_SIZE * 1024 * 1024; +// After a garbage collect we change the tripwire to the amount of non-garbage times +// this factor, so we do a decent amount of allocations between collections even +// when there is very little garbage +size_t const EXPAND_FACTOR = 2; + +// We don't consider collecting garbage until at least this amount of space has +// been allocated, to avoid collections near startup when there is little garbage. +size_t const MIN_SPACE = 1024 * 1024; + + // An arena can be used to allocate objects that can then be deallocated all at // once. class arena { @@ -45,8 +55,8 @@ class arena { // Clears the current allocation space by setting its start back to its first // block. It is used during garbage collection to effectively collect all of the - // arena. Resets the tripwire. - void arena_clear(); + // arena. + void arena_clear() { allocation_ptr = current_addr_ptr; } // Resizes the last allocation. // Returns the address of the byte following the last newly allocated byte. @@ -67,6 +77,14 @@ class arena { // It is used before garbage collection. void arena_swap_and_clear(); + // Decide how much space to use in arena before setting the flag for a collection. + void update_tripwire() { + size_t space = EXPAND_FACTOR * (allocation_ptr - current_addr_ptr); + if (space < MIN_SPACE) + space = MIN_SPACE; + tripwire = current_addr_ptr + space; + } + // Given two pointers to objects allocated in the same arena, return the number // of bytes they are apart. Undefined behavior will result if the pointers // don't belong to the same arena @@ -183,21 +201,6 @@ inline void *arena::kore_arena_alloc(size_t requested) { return result; } -inline void arena::arena_clear() { - // - // We set the allocation pointer to the first available address. - // - allocation_ptr = current_addr_ptr; - // - // If the number of blocks we've touched is >= threshold, we want to trigger - // a garbage collection if we get within 1 block of the end of this area. - // Otherwise we only want to generate a garbage collect if we allocate off the - // end of this area. - // - tripwire = current_addr_ptr - + (num_blocks - (num_blocks >= get_gc_threshold())) * BLOCK_SIZE; -} - inline void arena::arena_swap_and_clear() { update_num_blocks(); // so we save the correct number of touched blocks std::swap(current_addr_ptr, collection_addr_ptr); diff --git a/runtime/alloc/arena.cpp b/runtime/alloc/arena.cpp index fbfd7e527..8e3d496cc 100644 --- a/runtime/alloc/arena.cpp +++ b/runtime/alloc/arena.cpp @@ -72,9 +72,9 @@ void arena::initialize_semispace() { current_addr_ptr[HYPERBLOCK_SIZE - 1] = allocation_semispace_id; allocation_ptr = current_addr_ptr; // - // We set the tripwire for this space so we get trigger a garbage collection when we pass BLOCK_SIZE of memory + // We set the tripwire for this space so we get trigger a garbage collection when we pass MIN_SPACE of memory // allocated from this space. // - tripwire = current_addr_ptr + BLOCK_SIZE; + tripwire = current_addr_ptr + MIN_SPACE; num_blocks = 1; } diff --git a/runtime/collect/collect.cpp b/runtime/collect/collect.cpp index 2b71616b6..981b29955 100644 --- a/runtime/collect/collect.cpp +++ b/runtime/collect/collect.cpp @@ -337,6 +337,7 @@ void kore_collect( sizeof(numBytesLiveAtCollection) / sizeof(numBytesLiveAtCollection[0]), stderr); #endif + youngspace.update_tripwire(); MEM_LOG("Finishing garbage collection\n"); is_gc = false; } From c24f3620361d90d30797c10ba4a5fa44bbfa9991 Mon Sep 17 00:00:00 2001 From: Steven Eker Date: Wed, 11 Dec 2024 03:49:01 +0100 Subject: [PATCH 3/6] get rid of num_blocks calculations --- include/runtime/arena.h | 29 +++-------------------------- runtime/alloc/arena.cpp | 4 +--- 2 files changed, 4 insertions(+), 29 deletions(-) diff --git a/include/runtime/arena.h b/include/runtime/arena.h index 1a4a29574..f624848cd 100644 --- a/include/runtime/arena.h +++ b/include/runtime/arena.h @@ -110,20 +110,6 @@ class arena { static char get_arena_semispace_id_of_object(void *ptr); private: - // - // We update the number of 1MB blocks actually written to, only when we need this value, - // or before a garbage collection rather than trying to determine when we write to a fresh block. - // - void update_num_blocks() const { - // - // Calculate how many 1M blocks of the current arena we used. - // - size_t num_used_blocks - = (allocation_ptr - current_addr_ptr - 1) / BLOCK_SIZE + 1; - if (num_used_blocks > num_blocks) - num_blocks = num_used_blocks; - } - void initialize_semispace(); // // Current semispace where allocations are being made. @@ -132,16 +118,12 @@ class arena { char *allocation_ptr = nullptr; // next available location in current semispace char *tripwire = nullptr; // allocating past this triggers slow allocation - mutable size_t num_blocks - = 0; // notional number of BLOCK_SIZE blocks in current semispace char allocation_semispace_id; // id of current semispace // // Semispace where allocations will be made during and after garbage collect. // char *collection_addr_ptr = nullptr; // pointer to start of collection address space - size_t num_collection_blocks - = 0; // notional number of BLOCK_SIZE blocks in collection semispace }; inline char arena::get_arena_semispace_id_of_object(void *ptr) { @@ -202,16 +184,11 @@ inline void *arena::kore_arena_alloc(size_t requested) { } inline void arena::arena_swap_and_clear() { - update_num_blocks(); // so we save the correct number of touched blocks std::swap(current_addr_ptr, collection_addr_ptr); - std::swap(num_blocks, num_collection_blocks); allocation_semispace_id = ~allocation_semispace_id; - if (current_addr_ptr == nullptr) { - // - // The other semispace hasn't be initialized yet. - // - initialize_semispace(); - } else + if (current_addr_ptr == nullptr) + initialize_semispace(); // not yet initialized + else arena_clear(); } } diff --git a/runtime/alloc/arena.cpp b/runtime/alloc/arena.cpp index 8e3d496cc..22a64ae80 100644 --- a/runtime/alloc/arena.cpp +++ b/runtime/alloc/arena.cpp @@ -72,9 +72,7 @@ void arena::initialize_semispace() { current_addr_ptr[HYPERBLOCK_SIZE - 1] = allocation_semispace_id; allocation_ptr = current_addr_ptr; // - // We set the tripwire for this space so we get trigger a garbage collection when we pass MIN_SPACE of memory - // allocated from this space. + // Trigger a garbage collection for the first time when we pass MIN_SPACE of allocations // tripwire = current_addr_ptr + MIN_SPACE; - num_blocks = 1; } From afb08870ba7f9c3a82514e667cf888c92a04bc67 Mon Sep 17 00:00:00 2001 From: Steven Eker Date: Wed, 11 Dec 2024 19:40:07 +0100 Subject: [PATCH 4/6] only allow young arena to request collections --- include/runtime/arena.h | 10 ++++------ runtime/alloc/arena.cpp | 2 +- runtime/lto/alloc.cpp | 16 +++++++++++++--- 3 files changed, 18 insertions(+), 10 deletions(-) diff --git a/include/runtime/arena.h b/include/runtime/arena.h index f624848cd..56961cdb0 100644 --- a/include/runtime/arena.h +++ b/include/runtime/arena.h @@ -28,8 +28,9 @@ size_t const MIN_SPACE = 1024 * 1024; // once. class arena { public: - arena(char id) - : allocation_semispace_id(id) { } +<<<<<<< HEAD + arena(char id, bool trigger_collection) + : allocation_semispace_id(id), can_trigger_collection(trigger_collection) { } ~arena() { if (current_addr_ptr) @@ -118,6 +119,7 @@ class arena { char *allocation_ptr = nullptr; // next available location in current semispace char *tripwire = nullptr; // allocating past this triggers slow allocation + bool can_trigger_collection; // do we trigger collections char allocation_semispace_id; // id of current semispace // // Semispace where allocations will be made during and after garbage collect. @@ -139,10 +141,6 @@ inline char arena::get_arena_semispace_id_of_object(void *ptr) { return *reinterpret_cast(end_address); } -// Macro to define a new arena with the given ID. Supports IDs ranging from 0 to -// 127. -#define REGISTER_ARENA(name, id) thread_local arena name(id) - #ifdef __MACH__ // // thread_local disabled for Apple diff --git a/runtime/alloc/arena.cpp b/runtime/alloc/arena.cpp index 22a64ae80..ee1b84f1f 100644 --- a/runtime/alloc/arena.cpp +++ b/runtime/alloc/arena.cpp @@ -74,5 +74,5 @@ void arena::initialize_semispace() { // // Trigger a garbage collection for the first time when we pass MIN_SPACE of allocations // - tripwire = current_addr_ptr + MIN_SPACE; + tripwire = can_trigger_collection ? current_addr_ptr + MIN_SPACE : current_addr_ptr + HYPER_BLOCK_SIZE; } diff --git a/runtime/lto/alloc.cpp b/runtime/lto/alloc.cpp index 3d7f0663a..b0701c9a8 100644 --- a/runtime/lto/alloc.cpp +++ b/runtime/lto/alloc.cpp @@ -11,9 +11,19 @@ extern "C" { -REGISTER_ARENA(youngspace, YOUNGSPACE_ID); -REGISTER_ARENA(oldspace, OLDSPACE_ID); -REGISTER_ARENA(alwaysgcspace, ALWAYSGCSPACE_ID); +// class arena supports ID from 0 to 127 + +// New data in allocated in the youngspace, which requests a +// collection when is gets too full. +thread_local arena youngspace(YOUNGSPACE_ID, true); + +// Data that is old enough is migrated to the oldspace. The +// migrated data is always live at this point so it never +// requests a collection. +thread_local arena oldspace(OLDSPACE_ID, false); + +// Temporary data is doesn't use the garbage collector. +thread_local arena alwaysgcspace(ALWAYSGCSPACE_ID, false); char *youngspace_ptr() { return youngspace.start_ptr(); From 859c1e9161d71c0c96d5bd2316f7553936cba0c6 Mon Sep 17 00:00:00 2001 From: Steven Eker Date: Wed, 11 Dec 2024 20:05:43 +0100 Subject: [PATCH 5/6] fix trigger_collection for other semispace --- include/runtime/arena.h | 12 ++++++------ runtime/alloc/arena.cpp | 5 +++-- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/include/runtime/arena.h b/include/runtime/arena.h index 56961cdb0..4d8a46df3 100644 --- a/include/runtime/arena.h +++ b/include/runtime/arena.h @@ -28,9 +28,9 @@ size_t const MIN_SPACE = 1024 * 1024; // once. class arena { public: -<<<<<<< HEAD arena(char id, bool trigger_collection) - : allocation_semispace_id(id), can_trigger_collection(trigger_collection) { } + : allocation_semispace_id(id) + , trigger_collection(trigger_collection) { } ~arena() { if (current_addr_ptr) @@ -79,11 +79,11 @@ class arena { void arena_swap_and_clear(); // Decide how much space to use in arena before setting the flag for a collection. + // If an arena is going to request collections, updating this at the end of a + // collection is mandatory. void update_tripwire() { size_t space = EXPAND_FACTOR * (allocation_ptr - current_addr_ptr); - if (space < MIN_SPACE) - space = MIN_SPACE; - tripwire = current_addr_ptr + space; + tripwire = current_addr_ptr + ((space < MIN_SPACE) ? MIN_SPACE : space); } // Given two pointers to objects allocated in the same arena, return the number @@ -119,8 +119,8 @@ class arena { char *allocation_ptr = nullptr; // next available location in current semispace char *tripwire = nullptr; // allocating past this triggers slow allocation - bool can_trigger_collection; // do we trigger collections char allocation_semispace_id; // id of current semispace + const bool trigger_collection; // request collections? // // Semispace where allocations will be made during and after garbage collect. // diff --git a/runtime/alloc/arena.cpp b/runtime/alloc/arena.cpp index ee1b84f1f..33e68b560 100644 --- a/runtime/alloc/arena.cpp +++ b/runtime/alloc/arena.cpp @@ -72,7 +72,8 @@ void arena::initialize_semispace() { current_addr_ptr[HYPERBLOCK_SIZE - 1] = allocation_semispace_id; allocation_ptr = current_addr_ptr; // - // Trigger a garbage collection for the first time when we pass MIN_SPACE of allocations + // If we're set to trigger garbage collections, set the tripwire for MIN_SPACE of allocations otherwise + // set it out-of-bounds (but still legal for comparison). // - tripwire = can_trigger_collection ? current_addr_ptr + MIN_SPACE : current_addr_ptr + HYPER_BLOCK_SIZE; + tripwire = current_addr_ptr + (trigger_collection ? MIN_SPACE : HYPERBLOCK_SIZE); } From 590870ee5d8698528bcb9399d2d58bb8a871afd3 Mon Sep 17 00:00:00 2001 From: Steven Eker Date: Wed, 11 Dec 2024 20:52:39 +0100 Subject: [PATCH 6/6] fix formatting --- include/runtime/arena.h | 5 ++--- runtime/alloc/arena.cpp | 3 ++- runtime/lto/alloc.cpp | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/include/runtime/arena.h b/include/runtime/arena.h index 4d8a46df3..ea081766a 100644 --- a/include/runtime/arena.h +++ b/include/runtime/arena.h @@ -23,7 +23,6 @@ size_t const EXPAND_FACTOR = 2; // been allocated, to avoid collections near startup when there is little garbage. size_t const MIN_SPACE = 1024 * 1024; - // An arena can be used to allocate objects that can then be deallocated all at // once. class arena { @@ -120,7 +119,7 @@ class arena { = nullptr; // next available location in current semispace char *tripwire = nullptr; // allocating past this triggers slow allocation char allocation_semispace_id; // id of current semispace - const bool trigger_collection; // request collections? + bool const trigger_collection; // request collections? // // Semispace where allocations will be made during and after garbage collect. // @@ -185,7 +184,7 @@ inline void arena::arena_swap_and_clear() { std::swap(current_addr_ptr, collection_addr_ptr); allocation_semispace_id = ~allocation_semispace_id; if (current_addr_ptr == nullptr) - initialize_semispace(); // not yet initialized + initialize_semispace(); // not yet initialized else arena_clear(); } diff --git a/runtime/alloc/arena.cpp b/runtime/alloc/arena.cpp index 33e68b560..85171aed4 100644 --- a/runtime/alloc/arena.cpp +++ b/runtime/alloc/arena.cpp @@ -75,5 +75,6 @@ void arena::initialize_semispace() { // If we're set to trigger garbage collections, set the tripwire for MIN_SPACE of allocations otherwise // set it out-of-bounds (but still legal for comparison). // - tripwire = current_addr_ptr + (trigger_collection ? MIN_SPACE : HYPERBLOCK_SIZE); + tripwire + = current_addr_ptr + (trigger_collection ? MIN_SPACE : HYPERBLOCK_SIZE); } diff --git a/runtime/lto/alloc.cpp b/runtime/lto/alloc.cpp index b0701c9a8..e0d7a010b 100644 --- a/runtime/lto/alloc.cpp +++ b/runtime/lto/alloc.cpp @@ -16,7 +16,7 @@ extern "C" { // New data in allocated in the youngspace, which requests a // collection when is gets too full. thread_local arena youngspace(YOUNGSPACE_ID, true); - + // Data that is old enough is migrated to the oldspace. The // migrated data is always live at this point so it never // requests a collection.