Fix growth in minor GC when we have initial slots

If initial slots is set, then during a minor GC, if we have allocatable
pages but the heap is mostly full, then we will set `grow_heap` to true
since `total_slots` does not count allocatable pages so it will be less
than `init_slots`. This can cause `allocatable_pages` to grow to much
higher than desired since it will appear that the heap is mostly full.
This commit is contained in:
Peter Zhu 2023-08-28 16:34:27 -04:00
parent 5485680244
commit fd0df1f8c6
Notes: git 2023-08-28 22:01:49 +00:00
2 changed files with 44 additions and 6 deletions

11
gc.c
View File

@ -5689,12 +5689,11 @@ gc_sweep_finish_size_pool(rb_objspace_t *objspace, rb_size_pool_t *size_pool)
if (swept_slots < min_free_slots) {
bool grow_heap = is_full_marking(objspace);
if (!is_full_marking(objspace)) {
/* The heap is a growth heap if it freed more slots than had empty
* slots and used up all of its allocatable pages. */
bool is_growth_heap = (size_pool->empty_slots == 0 ||
size_pool->freed_slots > size_pool->empty_slots) &&
size_pool->allocatable_pages == 0;
/* Consider growing or starting a major GC if we are not currently in a
* major GC and we can't allocate any more pages. */
if (!is_full_marking(objspace) && size_pool->allocatable_pages == 0) {
/* The heap is a growth heap if it freed more slots than had empty slots. */
bool is_growth_heap = size_pool->empty_slots == 0 || size_pool->freed_slots > size_pool->empty_slots;
/* Grow this heap if we haven't run at least RVALUE_OLD_AGE minor
* GC since the last major GC or if this heap is smaller than the

View File

@ -492,6 +492,45 @@ class TestGc < Test::Unit::TestCase
assert_in_epsilon(SIZES[i], total_slots, 0.01, s)
end
RUBY
# Check that we don't grow the heap in minor GC if we have alloctable pages.
env["RUBY_GC_HEAP_FREE_SLOTS_MIN_RATIO"] = "0.3"
env["RUBY_GC_HEAP_FREE_SLOTS_GOAL_RATIO"] = "0.99"
env["RUBY_GC_HEAP_FREE_SLOTS_MAX_RATIO"] = "1.0"
env["RUBY_GC_HEAP_OLDOBJECT_LIMIT_FACTOR"] = "100" # Large value to disable major GC
assert_separately([env, "-W0"], __FILE__, __LINE__, <<~RUBY)
SIZES = #{sizes}
# Run a major GC to clear out dead objects.
GC.start
# Disable GC so we can control when GC is ran.
GC.disable
# Run minor GC enough times so that we don't grow the heap because we
# haven't yet ran RVALUE_OLD_AGE minor GC cycles.
GC::INTERNAL_CONSTANTS[:RVALUE_OLD_AGE].times { GC.start(full_mark: false) }
# Fill size pool 0 to over 50% full so that the number of allocatable
# pages that will be created will be over the number in heap_allocatable_pages
# (calculated using RUBY_GC_HEAP_FREE_SLOTS_MIN_RATIO).
# 70% was chosen here to guarantee that.
ary = []
while GC.stat_heap(0, :heap_allocatable_pages) >
(GC.stat_heap(0, :heap_allocatable_pages) + GC.stat_heap(0, :heap_eden_pages)) * 0.3
ary << Object.new
end
GC.start(full_mark: false)
# Check that we still have the same number of slots as initially configured.
GC.stat_heap.each do |i, s|
# Sometimes pages will have 1 less slot due to alignment, so always increase slots_per_page by 1.
slots_per_page = (s[:heap_eden_slots] / s[:heap_eden_pages]) + 1
total_slots = s[:heap_eden_slots] + s[:heap_allocatable_pages] * slots_per_page
assert_in_epsilon(SIZES[i], total_slots, 0.01, s)
end
RUBY
end
def test_profiler_enabled