Implement Process.warmup

[Feature #18885]

For now, the optimizations performed are:

  - Run a major GC
  - Compact the heap
  - Promote all surviving objects to oldgen

Other optimizations may follow.
This commit is contained in:
Jean Boussier 2023-04-05 08:40:07 +02:00 committed by Jean Boussier
parent d3bcff0158
commit fa30b99c34
Notes: git 2023-07-17 09:20:35 +00:00
7 changed files with 99 additions and 0 deletions

View File

@ -49,6 +49,13 @@ Note: We're only listing outstanding class updates.
* `Module#set_temporary_name` added for setting a temporary name for a module. [[Feature #19521]]
* Process.warmup
* Notify the Ruby virtual machine that the boot sequence is finished,
and that now is a good time to optimize the application. This is useful
for long running applications. The actual optimizations performed are entirely
implementation specific and may change in the future without notice. [[Feature #18885]
## Stdlib updates
The following default gems are updated.

View File

@ -11649,7 +11649,9 @@ process.$(OBJEXT): {$(VPATH)}thread_$(THREAD_MODEL).h
process.$(OBJEXT): {$(VPATH)}thread_native.h
process.$(OBJEXT): {$(VPATH)}util.h
process.$(OBJEXT): {$(VPATH)}vm_core.h
process.$(OBJEXT): {$(VPATH)}vm_debug.h
process.$(OBJEXT): {$(VPATH)}vm_opts.h
process.$(OBJEXT): {$(VPATH)}vm_sync.h
ractor.$(OBJEXT): $(CCAN_DIR)/check_type/check_type.h
ractor.$(OBJEXT): $(CCAN_DIR)/container_of/container_of.h
ractor.$(OBJEXT): $(CCAN_DIR)/list/list.h

27
gc.c
View File

@ -9590,6 +9590,26 @@ garbage_collect_with_gvl(rb_objspace_t *objspace, unsigned int reason)
}
}
static int
gc_set_candidate_object_i(void *vstart, void *vend, size_t stride, void *data)
{
rb_objspace_t *objspace = &rb_objspace;
VALUE v = (VALUE)vstart;
for (; v != (VALUE)vend; v += stride) {
switch (BUILTIN_TYPE(v)) {
case T_NONE:
case T_ZOMBIE:
break;
default:
if (!RVALUE_OLD_P(v) && !RVALUE_WB_UNPROTECTED(v)) {
RVALUE_AGE_SET_CANDIDATE(objspace, v);
}
}
}
return 0;
}
static VALUE
gc_start_internal(rb_execution_context_t *ec, VALUE self, VALUE full_mark, VALUE immediate_mark, VALUE immediate_sweep, VALUE compact)
{
@ -9617,6 +9637,13 @@ gc_start_internal(rb_execution_context_t *ec, VALUE self, VALUE full_mark, VALUE
return Qnil;
}
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);
}
static int
gc_is_moveable_obj(rb_objspace_t *objspace, VALUE obj)
{

View File

@ -204,6 +204,7 @@ extern VALUE *ruby_initial_gc_stress_ptr;
extern int ruby_disable_gc;
RUBY_ATTR_MALLOC void *ruby_mimmalloc(size_t size);
void ruby_mimfree(void *ptr);
void rb_gc_prepare_heap(void);
void rb_objspace_set_event_hook(const rb_event_flag_t event);
VALUE rb_objspace_gc_enable(struct rb_objspace *);
VALUE rb_objspace_gc_disable(struct rb_objspace *);

View File

@ -116,6 +116,7 @@ int initgroups(const char *, rb_gid_t);
#include "ruby/thread.h"
#include "ruby/util.h"
#include "vm_core.h"
#include "vm_sync.h"
#include "ruby/ractor.h"
/* define system APIs */
@ -8534,6 +8535,37 @@ static VALUE rb_mProcUID;
static VALUE rb_mProcGID;
static VALUE rb_mProcID_Syscall;
/*
* call-seq:
* Process.warmup -> true
*
* Notify the Ruby virtual machine that the boot sequence is finished,
* and that now is a good time to optimize the application. This is useful
* for long running applications.
*
* This method is expected to be called at the end of the application boot.
* If the application is deployed using a pre-forking model, +Process.warmup+
* should be called in the original process before the first fork.
*
* The actual optimizations performed are entirely implementation specific
* and may change in the future without notice.
*
* On CRuby, +Process.warmup+:
*
* * Perform a major GC.
* * Compacts the heap.
* * Promotes all surviving objects to the old generation.
*/
static VALUE
proc_warmup(VALUE _)
{
RB_VM_LOCK_ENTER();
rb_gc_prepare_heap();
RB_VM_LOCK_LEAVE();
return Qtrue;
}
/*
* Document-module: Process
@ -8651,6 +8683,8 @@ InitVM_process(void)
rb_define_module_function(rb_mProcess, "getpriority", proc_getpriority, 2);
rb_define_module_function(rb_mProcess, "setpriority", proc_setpriority, 3);
rb_define_module_function(rb_mProcess, "warmup", proc_warmup, 0);
#ifdef HAVE_GETPRIORITY
/* see Process.setpriority */
rb_define_const(rb_mProcess, "PRIO_PROCESS", INT2FIX(PRIO_PROCESS));

View File

@ -0,0 +1,11 @@
require_relative '../../spec_helper'
describe "Process.warmup" do
ruby_version_is "3.3" do
# The behavior is entirely implementation specific.
# Other implementations are free to just make it a noop
it "is implemented" do
Process.warmup.should == true
end
end
end

View File

@ -4,6 +4,7 @@ require 'test/unit'
require 'tempfile'
require 'timeout'
require 'rbconfig'
require 'objspace'
class TestProcess < Test::Unit::TestCase
RUBY = EnvUtil.rubybin
@ -2686,4 +2687,20 @@ EOS
end
end;
end if Process.respond_to?(:_fork)
def test_warmup_promote_all_objects_to_oldgen
obj = Object.new
refute_includes(ObjectSpace.dump(obj), '"old":true')
Process.warmup
assert_includes(ObjectSpace.dump(obj), '"old":true')
end
def test_warmup_run_major_gc_and_compact
major_gc_count = GC.stat(:major_gc_count)
compact_count = GC.stat(:compact_count)
Process.warmup
assert_equal major_gc_count + 1, GC.stat(:major_gc_count)
assert_equal compact_count + 1, GC.stat(:compact_count)
end
end