Free all empty heap pages in Process.warmup

This commit adds `free_empty_pages` which frees all empty heap pages and
moves the number of pages freed to the allocatable pages counter. This
is used in Process.warmup to improve performance because page
invalidation from copy-on-write is slower than allocating a new page.
This commit is contained in:
Peter Zhu 2023-08-21 14:13:24 -04:00
Родитель 5c98ee02d2
Коммит b7237e3bbd
3 изменённых файлов: 70 добавлений и 2 удалений

44
gc.c
Просмотреть файл

@ -9647,11 +9647,55 @@ gc_start_internal(rb_execution_context_t *ec, VALUE self, VALUE full_mark, VALUE
return Qnil;
}
static void
free_empty_pages(void)
{
rb_objspace_t *objspace = &rb_objspace;
for (int i = 0; i < SIZE_POOL_COUNT; i++) {
/* Move all empty pages to the tomb heap for freeing. */
rb_size_pool_t *size_pool = &size_pools[i];
rb_heap_t *heap = SIZE_POOL_EDEN_HEAP(size_pool);
rb_heap_t *tomb_heap = SIZE_POOL_TOMB_HEAP(size_pool);
size_t freed_pages = 0;
struct heap_page **next_page_ptr = &heap->free_pages;
struct heap_page *page = heap->free_pages;
while (page) {
/* All finalizers should have been ran in gc_start_internal, so there
* should be no objects that require finalization. */
GC_ASSERT(page->final_slots == 0);
struct heap_page *next_page = page->free_next;
if (page->free_slots == page->total_slots) {
heap_unlink_page(objspace, heap, page);
heap_add_page(objspace, size_pool, tomb_heap, page);
freed_pages++;
}
else {
*next_page_ptr = page;
next_page_ptr = &page->free_next;
}
page = next_page;
}
*next_page_ptr = NULL;
size_pool_allocatable_pages_set(objspace, size_pool, size_pool->allocatable_pages + freed_pages);
}
heap_pages_free_unused_pages(objspace);
}
void
rb_gc_prepare_heap(void)
{
rb_objspace_each_objects(gc_set_candidate_object_i, NULL);
gc_start_internal(NULL, Qtrue, Qtrue, Qtrue, Qtrue, Qtrue);
free_empty_pages();
}
static int

Просмотреть файл

@ -8671,10 +8671,12 @@ static VALUE rb_mProcID_Syscall;
*
* On CRuby, +Process.warmup+:
*
* * Perform a major GC.
* * Performs a major GC.
* * Compacts the heap.
* * Promotes all surviving objects to the old generation.
* * Precompute the coderange of all strings.
* * Precomputes the coderange of all strings.
* * Frees all empty heap pages and increments the allocatable pages counter
* by the number of pages freed.
*/
static VALUE

Просмотреть файл

@ -2725,4 +2725,26 @@ EOS
assert_include(ObjectSpace.dump(obj), '"coderange":"7bit"')
end;
end
def test_warmup_frees_pages
assert_separately([{"RUBY_GC_HEAP_FREE_SLOTS_MAX_RATIO" => "1.0"}, "-W0"], "#{<<~"begin;"}\n#{<<~'end;'}")
begin;
TIMES = 10_000
ary = Array.new(TIMES)
TIMES.times do |i|
ary[i] = Object.new
end
ary.clear
ary = nil
total_pages_before = GC.stat(:heap_eden_pages) + GC.stat(:heap_allocatable_pages)
Process.warmup
# Number of pages freed should cause equal increase in number of allocatable pages.
assert_equal(total_pages_before, GC.stat(:heap_eden_pages) + GC.stat(:heap_allocatable_pages))
assert_equal(0, GC.stat(:heap_tomb_pages))
assert_operator(GC.stat(:total_freed_pages), :>, 0)
end;
end
end