diff options
author | Peter Zhu <[email protected]> | 2025-04-03 16:43:09 -0400 |
---|---|---|
committer | Peter Zhu <[email protected]> | 2025-04-07 09:41:11 -0400 |
commit | d4406f0627c78af31e61f9e07dda9151e109dbc4 (patch) | |
tree | 4010a3107ca701b4b69e808f7a8736073498c0e7 /gc/default/default.c | |
parent | 432e5fa7e4ad57e0d8dcfcec29f0426aac7aedb4 (diff) |
Grow GC heaps independently
[Bug #21214]
If we allocate objects where one heap holds transient objects and another
holds long lived objects, then the heap with transient objects will grow
along the heap with long lived objects, causing higher memory usage.
For example, we can see this issue in this script:
def allocate_small_object = []
def allocate_large_object = Array.new(10)
arys = Array.new(1_000_000) do
# Allocate 10 small transient objects
10.times { allocate_small_object }
# Allocate 1 large object that is persistent
allocate_large_object
end
pp GC.stat
pp GC.stat_heap
Before this change:
heap_live_slots: 2837243
{0 =>
{slot_size: 40,
heap_eden_pages: 1123,
heap_eden_slots: 1838807},
2 =>
{slot_size: 160,
heap_eden_pages: 2449,
heap_eden_slots: 1001149},
}
After this change:
heap_live_slots: 1094474
{0 =>
{slot_size: 40,
heap_eden_pages: 58,
heap_eden_slots: 94973},
2 =>
{slot_size: 160,
heap_eden_pages: 2449,
heap_eden_slots: 1001149},
}
Notes
Notes:
Merged: https://github.com/ruby/ruby/pull/13061
Diffstat (limited to 'gc/default/default.c')
-rw-r--r-- | gc/default/default.c | 74 |
1 files changed, 35 insertions, 39 deletions
diff --git a/gc/default/default.c b/gc/default/default.c index 1bde4ad033..c7723a2275 100644 --- a/gc/default/default.c +++ b/gc/default/default.c @@ -804,8 +804,6 @@ heap_page_in_global_empty_pages_pool(rb_objspace_t *objspace, struct heap_page * #define GET_HEAP_WB_UNPROTECTED_BITS(x) (&GET_HEAP_PAGE(x)->wb_unprotected_bits[0]) #define GET_HEAP_MARKING_BITS(x) (&GET_HEAP_PAGE(x)->marking_bits[0]) -#define GC_SWEEP_PAGES_FREEABLE_PER_STEP 3 - #define RVALUE_AGE_BITMAP_INDEX(n) (NUM_IN_PAGE(n) / (BITS_BITLENGTH / RVALUE_AGE_BIT_COUNT)) #define RVALUE_AGE_BITMAP_OFFSET(n) ((NUM_IN_PAGE(n) % (BITS_BITLENGTH / RVALUE_AGE_BIT_COUNT)) * RVALUE_AGE_BIT_COUNT) @@ -1771,13 +1769,7 @@ heap_page_free(rb_objspace_t *objspace, struct heap_page *page) static void heap_pages_free_unused_pages(rb_objspace_t *objspace) { - size_t pages_to_keep_count = - // Get number of pages estimated for the smallest size pool - CEILDIV(objspace->heap_pages.allocatable_slots, HEAP_PAGE_OBJ_LIMIT) * - // Estimate the average slot size multiple - (1 << (HEAP_COUNT / 2)); - - if (objspace->empty_pages != NULL && objspace->empty_pages_count > pages_to_keep_count) { + if (objspace->empty_pages != NULL && heap_pages_freeable_pages > 0) { GC_ASSERT(objspace->empty_pages_count > 0); objspace->empty_pages = NULL; objspace->empty_pages_count = 0; @@ -1786,15 +1778,15 @@ heap_pages_free_unused_pages(rb_objspace_t *objspace) for (i = j = 0; i < rb_darray_size(objspace->heap_pages.sorted); i++) { struct heap_page *page = rb_darray_get(objspace->heap_pages.sorted, i); - if (heap_page_in_global_empty_pages_pool(objspace, page) && pages_to_keep_count == 0) { + if (heap_page_in_global_empty_pages_pool(objspace, page) && heap_pages_freeable_pages > 0) { heap_page_free(objspace, page); + heap_pages_freeable_pages--; } else { - if (heap_page_in_global_empty_pages_pool(objspace, page) && pages_to_keep_count > 0) { + if (heap_page_in_global_empty_pages_pool(objspace, page)) { page->free_next = objspace->empty_pages; objspace->empty_pages = page; objspace->empty_pages_count++; - pages_to_keep_count--; } if (i != j) { @@ -2026,29 +2018,33 @@ heap_add_page(rb_objspace_t *objspace, rb_heap_t *heap, struct heap_page *page) static int heap_page_allocate_and_initialize(rb_objspace_t *objspace, rb_heap_t *heap) { - if (objspace->heap_pages.allocatable_slots > 0) { - gc_report(1, objspace, "heap_page_allocate_and_initialize: rb_darray_size(objspace->heap_pages.sorted): %"PRIdSIZE", " + gc_report(1, objspace, "heap_page_allocate_and_initialize: rb_darray_size(objspace->heap_pages.sorted): %"PRIdSIZE", " "allocatable_slots: %"PRIdSIZE", heap->total_pages: %"PRIdSIZE"\n", rb_darray_size(objspace->heap_pages.sorted), objspace->heap_pages.allocatable_slots, heap->total_pages); - struct heap_page *page = heap_page_resurrect(objspace); - if (page == NULL) { - page = heap_page_allocate(objspace); - } + bool allocated = false; + struct heap_page *page = heap_page_resurrect(objspace); + + if (page == NULL && objspace->heap_pages.allocatable_slots > 0) { + page = heap_page_allocate(objspace); + allocated = true; + } + + if (page != NULL) { heap_add_page(objspace, heap, page); heap_add_freepage(heap, page); - if (objspace->heap_pages.allocatable_slots > (size_t)page->total_slots) { - objspace->heap_pages.allocatable_slots -= page->total_slots; - } - else { - objspace->heap_pages.allocatable_slots = 0; + if (allocated) { + if (objspace->heap_pages.allocatable_slots > (size_t)page->total_slots) { + objspace->heap_pages.allocatable_slots -= page->total_slots; + } + else { + objspace->heap_pages.allocatable_slots = 0; + } } - - return true; } - return false; + return page != NULL; } static void @@ -3781,7 +3777,6 @@ gc_sweep_start(rb_objspace_t *objspace) { gc_mode_transition(objspace, gc_mode_sweeping); objspace->rincgc.pooled_slots = 0; - objspace->heap_pages.allocatable_slots = 0; #if GC_CAN_COMPILE_COMPACTION if (objspace->flags.during_compacting) { @@ -3818,7 +3813,7 @@ gc_sweep_finish_heap(rb_objspace_t *objspace, rb_heap_t *heap) if (swept_slots < min_free_slots && /* The heap is a growth heap if it freed more slots than had empty slots. */ - (heap->empty_slots == 0 || heap->freed_slots > heap->empty_slots)) { + ((heap->empty_slots == 0 && total_slots > 0) || heap->freed_slots > heap->empty_slots)) { /* If we don't have enough slots and we have pages on the tomb heap, move * pages from the tomb heap to the eden heap. This may prevent page * creation thrashing (frequently allocating and deallocting pages) and @@ -3834,10 +3829,12 @@ gc_sweep_finish_heap(rb_objspace_t *objspace, rb_heap_t *heap) if (swept_slots < min_free_slots) { /* Grow this heap if we are in a major GC or if we haven't run at least - * RVALUE_OLD_AGE minor GC since the last major GC. */ + * RVALUE_OLD_AGE minor GC since the last major GC. */ if (is_full_marking(objspace) || objspace->profile.count - objspace->rgengc.last_major_gc < RVALUE_OLD_AGE) { - heap_allocatable_slots_expand(objspace, heap, swept_slots, heap->total_slots); + if (objspace->heap_pages.allocatable_slots < min_free_slots) { + heap_allocatable_slots_expand(objspace, heap, swept_slots, heap->total_slots); + } } else { gc_needs_major_flags |= GPR_FLAG_MAJOR_BY_NOFREE; @@ -3887,7 +3884,6 @@ static int gc_sweep_step(rb_objspace_t *objspace, rb_heap_t *heap) { struct heap_page *sweep_page = heap->sweeping_page; - int unlink_limit = GC_SWEEP_PAGES_FREEABLE_PER_STEP; int swept_slots = 0; int pooled_slots = 0; @@ -3911,11 +3907,7 @@ gc_sweep_step(rb_objspace_t *objspace, rb_heap_t *heap) heap->sweeping_page = ccan_list_next(&heap->pages, sweep_page, page_node); - if (free_slots == sweep_page->total_slots && - heap_pages_freeable_pages > 0 && - unlink_limit > 0) { - heap_pages_freeable_pages--; - unlink_limit--; + if (free_slots == sweep_page->total_slots) { /* There are no living objects, so move this page to the global empty pages. */ heap_unlink_page(objspace, heap, sweep_page); @@ -3994,9 +3986,7 @@ gc_sweep_continue(rb_objspace_t *objspace, rb_heap_t *sweep_heap) for (int i = 0; i < HEAP_COUNT; i++) { rb_heap_t *heap = &heaps[i]; if (!gc_sweep_step(objspace, heap)) { - /* sweep_heap requires a free slot but sweeping did not yield any - * and we cannot allocate a new page. */ - if (heap == sweep_heap && objspace->heap_pages.allocatable_slots == 0) { + if (heap == sweep_heap && objspace->empty_pages_count == 0 && objspace->heap_pages.allocatable_slots == 0) { /* Not allowed to create a new page so finish sweeping. */ gc_sweep_rest(objspace); break; @@ -5462,6 +5452,10 @@ gc_marks_finish(rb_objspace_t *objspace) gc_needs_major_flags |= GPR_FLAG_MAJOR_BY_NOFREE; } } + + if (full_marking) { + heap_allocatable_slots_expand(objspace, NULL, sweep_slots, total_slots); + } } if (full_marking) { @@ -6844,7 +6838,9 @@ rb_gc_impl_prepare_heap(void *objspace_ptr) gc_params.heap_free_slots_max_ratio = orig_max_free_slots; objspace->heap_pages.allocatable_slots = 0; + heap_pages_freeable_pages = objspace->empty_pages_count; heap_pages_free_unused_pages(objspace_ptr); + GC_ASSERT(heap_pages_freeable_pages == 0); GC_ASSERT(objspace->empty_pages_count == 0); objspace->heap_pages.allocatable_slots = orig_allocatable_slots; |