diff --git a/xo-alloc/include/xo/alloc/ArenaAlloc.hpp b/xo-alloc/include/xo/alloc/ArenaAlloc.hpp index 66dd6a70..11fcdb72 100644 --- a/xo-alloc/include/xo/alloc/ArenaAlloc.hpp +++ b/xo-alloc/include/xo/alloc/ArenaAlloc.hpp @@ -54,13 +54,15 @@ namespace xo { return std::make_pair(lo_, free_ptr_); } - /** Reset to empty state **/ - void reset(std::size_t /*z_ignored*/) { this->clear(); } + /** Reset to empty state; provision at least @p need_z bytes of (committed) space **/ + void reset(std::size_t need_z); void capture_object_statistics(capture_phase phase, ObjectStatistics * p_dest) const; - /** expand available (i.e. committed) space to size @p z **/ + /** expand available (i.e. committed) space to size at least @p z + * In practice will round up to a multiple of @ref page_z_ + **/ bool expand(std::size_t z); // inherited from IAlloc... diff --git a/xo-alloc/include/xo/alloc/GC.hpp b/xo-alloc/include/xo/alloc/GC.hpp index 1e55c497..d145e2c7 100644 --- a/xo-alloc/include/xo/alloc/GC.hpp +++ b/xo-alloc/include/xo/alloc/GC.hpp @@ -33,12 +33,16 @@ namespace xo { struct Config { /** initial size in bytes for youngest (Nursery) generation. * GC allocates two nursery spaces of this size. - * Will allocate more space as needed + * This number represents reserved address space. + * pages are committed on demand. + * Initial committment will be up to @ref incr_gc_threshold_ **/ std::size_t initial_nursery_z_ = 0; /** initial size in bytes for oldest (Tenured) generation. - * GC allocates two tenured spaces of this size - * Will allocate more space as needed + * GC allocates two tenured spaces of this size. + * This number represents reserved address space. + * pages are committed on demand. + * Initial committment will be up to @ref full_gc_threshold_ **/ std::size_t initial_tenured_z_ = 0; /** trigger incremental GC after this many bytes allocated in nursery **/ @@ -158,6 +162,8 @@ namespace xo { bool is_gc_enabled() const { return gc_enabled_ == 0; } /** true iff GC has been requested **/ bool is_gc_pending() const { return incr_gc_pending_ || full_gc_pending_; } + /** true iff full GC pending **/ + bool is_full_gc_pending() const { return full_gc_pending_; } /** true during (and only during) a GC cycle **/ bool gc_in_progress() const { return runstate_.in_progress(); } /** @return reserved size of Nursery to-space **/ @@ -170,6 +176,10 @@ namespace xo { std::size_t nursery_after_checkpoint() const; /** @return allocated memory range for nursery **/ std::pair nursery_span(role role) const; + /** @return nursery bytes used in from-space + * (only interesting during GC copy phase, e.g. during scope of a GcCopyCallback call) + **/ + std::size_t nursery_from_allocated() const; /** @return reserved size of Tenured to-space **/ std::size_t tenured_to_reserved() const; /** @return committed size of Tenured to-space **/ @@ -186,19 +196,19 @@ namespace xo { * and allocated size of that generation * @p role chooses between to-space and from-space **/ - std::tuple location_of(role role, const void * x) const; + std::tuple location_of(role role, const void * x) const; /** @return generation to which object at @p x belongs, * location relative to base address for @p x, * and allocated size of generation **/ - std::tuple tospace_location_of(const void * x) const; + std::tuple tospace_location_of(const void * x) const; /** @return generation that contains @p x, given it's in from-space **/ generation_result fromspace_generation_of(const void * x) const; /** @return generation to which object at @p x belongs, * location relative to base address for @p x, * and allocated size of generation **/ - std::tuple fromspace_location_of(const void * x) const; + std::tuple fromspace_location_of(const void * x) const; /** true iff from-space contains @p x **/ bool fromspace_contains(const void * x) const; /** @return free pointer for generation @p gen, i.e. nursery or tenured space **/ @@ -215,7 +225,10 @@ namespace xo { * Intended for GC visualization. **/ CallbackId add_gc_copy_callback(up fn); - /** request garbage collection. **/ + /** request garbage collection. + * If GC currently disabled, collection will be deferred until the next time GC + * is in an enabled state. See @ref disable_gc and @ref enable_gc + **/ void request_gc(generation g); /** disable garbage collection until matching call to @ref enable_gc. * diff --git a/xo-alloc/src/alloc/ArenaAlloc.cpp b/xo-alloc/src/alloc/ArenaAlloc.cpp index f3145bb2..9703d103 100644 --- a/xo-alloc/src/alloc/ArenaAlloc.cpp +++ b/xo-alloc/src/alloc/ArenaAlloc.cpp @@ -144,6 +144,12 @@ namespace xo { } } + void + ArenaAlloc::reset(std::size_t need_z) { + this->clear(); + this->expand(need_z); + } + void ArenaAlloc::capture_object_statistics(capture_phase phase, ObjectStatistics * p_dest) const diff --git a/xo-alloc/src/alloc/GC.cpp b/xo-alloc/src/alloc/GC.cpp index 5f75c2a0..7d18da57 100644 --- a/xo-alloc/src/alloc/GC.cpp +++ b/xo-alloc/src/alloc/GC.cpp @@ -68,16 +68,23 @@ namespace xo { nursery_[role2int(role::from_space)] = ArenaAlloc::make("NA", nursery_size, config.debug_flag_); + nursery_[role2int(role::to_space) ] = ArenaAlloc::make("NB", nursery_size, config.debug_flag_); tenured_[role2int(role::from_space)] = ArenaAlloc::make("TA", tenured_size, config.debug_flag_); + tenured_[role2int(role::to_space) ] = ArenaAlloc::make("TB", tenured_size, config.debug_flag_); + nursery_[role2int(role::from_space)]->expand(config.incr_gc_threshold_); + nursery_[role2int(role::to_space) ]->expand(config.incr_gc_threshold_); + tenured_[role2int(role::from_space)]->expand(config.full_gc_threshold_); + tenured_[role2int(role::to_space) ]->expand(config.full_gc_threshold_); + mutation_log_[role2int(role::from_space)] = std::make_unique(); - mutation_log_[role2int(role::to_space)] = std::make_unique(); + mutation_log_[role2int(role::to_space )] = std::make_unique(); defer_mutation_log_ = std::make_unique(); this->gc_history_ = CircularBuffer(config.stats_history_z_); @@ -194,6 +201,12 @@ namespace xo { return retval; } + std::size_t + GC::nursery_from_allocated() const + { + return nursery_from()->allocated(); + } + std::size_t GC::nursery_to_reserved() const { @@ -259,7 +272,7 @@ namespace xo { return generation_result::not_found; } - std::tuple + std::tuple GC::location_of(role role, const void *x) const { { @@ -267,7 +280,7 @@ namespace xo { auto [is_tenured, offset] = space->location_of(x); if (is_tenured) - return std::make_tuple(generation_result::tenured, offset, space->allocated()); + return std::make_tuple(generation_result::tenured, offset, space->allocated(), space->committed()); } { @@ -275,19 +288,19 @@ namespace xo { auto [is_nursery, offset] = nursery(role)->location_of(x); if (is_nursery) - return std::make_tuple(generation_result::nursery, offset, space->allocated()); + return std::make_tuple(generation_result::nursery, offset, space->allocated(), space->committed()); } - return std::make_tuple(generation_result::not_found, 0, 0); + return std::make_tuple(generation_result::not_found, 0, 0, 0); } - std::tuple + std::tuple GC::tospace_location_of(const void * x) const { return location_of(role::to_space, x); } - std::tuple + std::tuple GC::fromspace_location_of(const void * x) const { return location_of(role::from_space, x); @@ -533,29 +546,26 @@ namespace xo { */ std::size_t max_promote_z = nursery_[role2int(role::to_space)]->before_checkpoint(); - log && log(xtag("max_promote_z", max_promote_z)); + ArenaAlloc * tenured_to = this->tenured_to(); + + /* tenured generation may need this much space */ + std::size_t need_tenured_z = (tenured_to->allocated() + + max_promote_z + + config_.full_gc_threshold_); + + log && log(xtag("alloc_tenured_z", tenured_to->allocated()), + xtag("max_promote_z", max_promote_z), + xtag("full_gc_threshold", config_.full_gc_threshold_), + xtag("need_tenured_z", need_tenured_z)); + + tenured_to->expand(tenured_to->allocated() + + max_promote_z + + config_.full_gc_threshold_); if (target == generation::tenured) { - /* gc on tenured generation may need this much space */ - std::size_t need_tenured_z = (tenured_[role2int(role::to_space)]->allocated() - + max_promote_z - + config_.full_gc_threshold_); - - log && log("need_tenured_z", need_tenured_z); - - tenured_from()->reset(need_tenured_z); + tenured_from()->clear(); this->swap_tenured(); - } else { - std::size_t avail_tenured_z = tenured_[role2int(role::to_space)]->available(); - - log && log(xtag("avail_tenured_z", avail_tenured_z)); - - if (avail_tenured_z < max_promote_z) { - ArenaAlloc * tenured_to = this->tenured_to(); - - tenured_to->expand(max_promote_z); - } } /* subtracting max_promote_z is correct here, since anything not promoted is garbage */ @@ -1220,24 +1230,22 @@ namespace xo { void GC::request_gc(generation target) { + /** full collection when >= @ref full_gc_threshold_ bytes added to tenured + * generation, since last full collection + **/ + bool need_full_gc + = ((target == generation::tenured) + || (this->tenured_to()->after_checkpoint() > config_.full_gc_threshold_) + || !config_.allow_incremental_gc_); + + if (need_full_gc) + target = generation::tenured; + if (!runstate_.in_progress() && (gc_enabled_ == 0)) { - if (!config_.allow_incremental_gc_) - target = generation::tenured; - - if ((target == generation::nursery) - && (this->tenured_to()->after_checkpoint() > config_.full_gc_threshold_)) - { - /** full collection when >= @ref full_gc_threshold_ bytes added to tenured - * generation, since last full collection - **/ - target = generation::tenured; - } - this->execute_gc(target); } else { this->incr_gc_pending_ = true; - if (target == generation::tenured) - this->full_gc_pending_ = true; + this->full_gc_pending_ |= need_full_gc; } }