зеркало из https://github.com/github/ruby.git
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:
Родитель
5c98ee02d2
Коммит
b7237e3bbd
44
gc.c
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
|
||||
|
|
Загрузка…
Ссылка в новой задаче