diff --git a/include/runtime/arena.h b/include/runtime/arena.h index 826db686c..ea081766a 100644 --- a/include/runtime/arena.h +++ b/include/runtime/arena.h @@ -14,12 +14,22 @@ 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 { public: - arena(char id) - : allocation_semispace_id(id) { } + arena(char id, bool trigger_collection) + : allocation_semispace_id(id) + , trigger_collection(trigger_collection) { } ~arena() { if (current_addr_ptr) @@ -36,17 +46,17 @@ 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 - // 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. + // 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); + tripwire = current_addr_ptr + ((space < MIN_SPACE) ? MIN_SPACE : 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 @@ -92,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. @@ -114,16 +118,13 @@ 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 + bool const trigger_collection; // request collections? // // 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) { @@ -139,10 +140,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 @@ -183,32 +180,12 @@ 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 = arena_start_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); - 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 fbfd7e527..85171aed4 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 - // allocated from this space. + // 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 + BLOCK_SIZE; - num_blocks = 1; + tripwire + = current_addr_ptr + (trigger_collection ? MIN_SPACE : HYPERBLOCK_SIZE); } diff --git a/runtime/collect/collect.cpp b/runtime/collect/collect.cpp index f49123950..981b29955 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,12 +331,13 @@ 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]), stderr); #endif + youngspace.update_tripwire(); MEM_LOG("Finishing garbage collection\n"); is_gc = false; } diff --git a/runtime/lto/alloc.cpp b/runtime/lto/alloc.cpp index aaafc932d..e0d7a010b 100644 --- a/runtime/lto/alloc.cpp +++ b/runtime/lto/alloc.cpp @@ -11,16 +11,26 @@ 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.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 +83,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);